PageRenderTime 103ms CodeModel.GetById 38ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/msf/core/db.rb

https://bitbucket.org/technopunk2099/metasploit-framework
Ruby | 6413 lines | 5194 code | 481 blank | 738 comment | 493 complexity | b628b679e2f04ffd13c5ea362d7a9e0e MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, LGPL-2.1, GPL-2.0, MIT
  1. # -*- coding: binary -*-
  2. # Check Rex::Parser.nokogiri_loaded for status of the Nokogiri parsers
  3. require 'rex/parser/nmap_nokogiri'
  4. require 'rex/parser/nexpose_simple_nokogiri'
  5. require 'rex/parser/nexpose_raw_nokogiri'
  6. require 'rex/parser/foundstone_nokogiri'
  7. require 'rex/parser/mbsa_nokogiri'
  8. require 'rex/parser/acunetix_nokogiri'
  9. require 'rex/parser/appscan_nokogiri'
  10. require 'rex/parser/burp_session_nokogiri'
  11. require 'rex/parser/ci_nokogiri'
  12. require 'rex/parser/wapiti_nokogiri'
  13. require 'rex/parser/openvas_nokogiri'
  14. require 'rex/parser/fusionvm_nokogiri'
  15. # Legacy XML parsers -- these will be converted some day
  16. require 'rex/parser/nmap_xml'
  17. require 'rex/parser/nexpose_xml'
  18. require 'rex/parser/retina_xml'
  19. require 'rex/parser/netsparker_xml'
  20. require 'rex/parser/nessus_xml'
  21. require 'rex/parser/ip360_xml'
  22. require 'rex/parser/ip360_aspl_xml'
  23. require 'rex/socket'
  24. require 'zip'
  25. require 'packetfu'
  26. require 'uri'
  27. require 'tmpdir'
  28. require 'csv'
  29. module Msf
  30. ###
  31. #
  32. # The states that a host can be in.
  33. #
  34. ###
  35. module HostState
  36. #
  37. # The host is alive.
  38. #
  39. Alive = "alive"
  40. #
  41. # The host is dead.
  42. #
  43. Dead = "down"
  44. #
  45. # The host state is unknown.
  46. #
  47. Unknown = "unknown"
  48. end
  49. ###
  50. #
  51. # The states that a service can be in.
  52. #
  53. ###
  54. module ServiceState
  55. Open = "open"
  56. Closed = "closed"
  57. Filtered = "filtered"
  58. Unknown = "unknown"
  59. end
  60. ###
  61. #
  62. # Events that can occur in the host/service database.
  63. #
  64. ###
  65. module DatabaseEvent
  66. #
  67. # Called when an existing host's state changes
  68. #
  69. def on_db_host_state(host, ostate)
  70. end
  71. #
  72. # Called when an existing service's state changes
  73. #
  74. def on_db_service_state(host, port, ostate)
  75. end
  76. #
  77. # Called when a new host is added to the database. The host parameter is
  78. # of type Host.
  79. #
  80. def on_db_host(host)
  81. end
  82. #
  83. # Called when a new client is added to the database. The client
  84. # parameter is of type Client.
  85. #
  86. def on_db_client(client)
  87. end
  88. #
  89. # Called when a new service is added to the database. The service
  90. # parameter is of type Service.
  91. #
  92. def on_db_service(service)
  93. end
  94. #
  95. # Called when an applicable vulnerability is found for a service. The vuln
  96. # parameter is of type Vuln.
  97. #
  98. def on_db_vuln(vuln)
  99. end
  100. #
  101. # Called when a new reference is created.
  102. #
  103. def on_db_ref(ref)
  104. end
  105. end
  106. class DBImportError < RuntimeError
  107. end
  108. ###
  109. #
  110. # The DB module ActiveRecord definitions for the DBManager
  111. #
  112. ###
  113. class DBManager
  114. def rfc3330_reserved(ip)
  115. case ip.class.to_s
  116. when "PacketFu::Octets"
  117. ip_x = ip.to_x
  118. ip_i = ip.to_i
  119. when "String"
  120. if ipv46_validator(ip)
  121. ip_x = ip
  122. ip_i = Rex::Socket.addr_atoi(ip)
  123. else
  124. raise ArgumentError, "Invalid IP address: #{ip.inspect}"
  125. end
  126. when "Fixnum"
  127. if (0..2**32-1).include? ip
  128. ip_x = Rex::Socket.addr_itoa(ip)
  129. ip_i = ip
  130. else
  131. raise ArgumentError, "Invalid IP address: #{ip.inspect}"
  132. end
  133. else
  134. raise ArgumentError, "Invalid IP address: #{ip.inspect}"
  135. end
  136. return true if Rex::Socket::RangeWalker.new("0.0.0.0-0.255.255.255").include? ip_x
  137. return true if Rex::Socket::RangeWalker.new("127.0.0.0-127.255.255.255").include? ip_x
  138. return true if Rex::Socket::RangeWalker.new("169.254.0.0-169.254.255.255").include? ip_x
  139. return true if Rex::Socket::RangeWalker.new("224.0.0.0-239.255.255.255").include? ip_x
  140. return true if Rex::Socket::RangeWalker.new("255.255.255.255-255.255.255.255").include? ip_x
  141. return false
  142. end
  143. def ipv46_validator(addr)
  144. ipv4_validator(addr) or ipv6_validator(addr)
  145. end
  146. def ipv4_validator(addr)
  147. return false unless addr.kind_of? String
  148. Rex::Socket.is_ipv4?(addr)
  149. end
  150. def ipv6_validator(addr)
  151. Rex::Socket.is_ipv6?(addr)
  152. end
  153. # Takes a space-delimited set of ips and ranges, and subjects
  154. # them to RangeWalker for validation. Returns true or false.
  155. def validate_ips(ips)
  156. ret = true
  157. begin
  158. ips.split(/\s+/).each {|ip|
  159. unless Rex::Socket::RangeWalker.new(ip).ranges
  160. ret = false
  161. break
  162. end
  163. }
  164. rescue
  165. ret = false
  166. end
  167. return ret
  168. end
  169. #
  170. # Determines if the database is functional
  171. #
  172. def check
  173. ::ActiveRecord::Base.connection_pool.with_connection {
  174. res = ::Mdm::Host.find(:first)
  175. }
  176. end
  177. def default_workspace
  178. ::ActiveRecord::Base.connection_pool.with_connection {
  179. ::Mdm::Workspace.default
  180. }
  181. end
  182. def find_workspace(name)
  183. ::ActiveRecord::Base.connection_pool.with_connection {
  184. ::Mdm::Workspace.find_by_name(name)
  185. }
  186. end
  187. #
  188. # Creates a new workspace in the database
  189. #
  190. def add_workspace(name)
  191. ::ActiveRecord::Base.connection_pool.with_connection {
  192. ::Mdm::Workspace.find_or_create_by_name(name)
  193. }
  194. end
  195. def workspaces
  196. ::ActiveRecord::Base.connection_pool.with_connection {
  197. ::Mdm::Workspace.find(:all)
  198. }
  199. end
  200. #
  201. # Wait for all pending write to finish
  202. #
  203. def sync
  204. # There is no more queue.
  205. end
  206. #
  207. # Find a host. Performs no database writes.
  208. #
  209. def get_host(opts)
  210. if opts.kind_of? ::Mdm::Host
  211. return opts
  212. elsif opts.kind_of? String
  213. raise RuntimeError, "This invokation of get_host is no longer supported: #{caller}"
  214. else
  215. address = opts[:addr] || opts[:address] || opts[:host] || return
  216. return address if address.kind_of? ::Mdm::Host
  217. end
  218. ::ActiveRecord::Base.connection_pool.with_connection {
  219. wspace = opts.delete(:workspace) || workspace
  220. if wspace.kind_of? String
  221. wspace = find_workspace(wspace)
  222. end
  223. address = normalize_host(address)
  224. return wspace.hosts.find_by_address(address)
  225. }
  226. end
  227. #
  228. # Exactly like report_host but waits for the database to create a host and returns it.
  229. #
  230. def find_or_create_host(opts)
  231. report_host(opts)
  232. end
  233. #
  234. # Report a host's attributes such as operating system and service pack
  235. #
  236. # The opts parameter MUST contain
  237. # +:host+:: -- the host's ip address
  238. #
  239. # The opts parameter can contain:
  240. # +:state+:: -- one of the Msf::HostState constants
  241. # +:os_name+:: -- one of the Msf::OperatingSystems constants
  242. # +:os_flavor+:: -- something like "XP" or "Gentoo"
  243. # +:os_sp+:: -- something like "SP2"
  244. # +:os_lang+:: -- something like "English", "French", or "en-US"
  245. # +:arch+:: -- one of the ARCH_* constants
  246. # +:mac+:: -- the host's MAC address
  247. # +:scope+:: -- interface identifier for link-local IPv6
  248. # +:virtual_host+:: -- the name of the VM host software, eg "VMWare", "QEMU", "Xen", etc.
  249. #
  250. def report_host(opts)
  251. return if not active
  252. addr = opts.delete(:host) || return
  253. # Sometimes a host setup through a pivot will see the address as "Remote Pipe"
  254. if addr.eql? "Remote Pipe"
  255. return
  256. end
  257. ::ActiveRecord::Base.connection_pool.with_connection {
  258. wspace = opts.delete(:workspace) || workspace
  259. if wspace.kind_of? String
  260. wspace = find_workspace(wspace)
  261. end
  262. ret = { }
  263. if not addr.kind_of? ::Mdm::Host
  264. addr = normalize_host(addr)
  265. addr, scope = addr.split('%', 2)
  266. opts[:scope] = scope if scope
  267. unless ipv46_validator(addr)
  268. raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
  269. end
  270. if opts[:comm] and opts[:comm].length > 0
  271. host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm])
  272. else
  273. host = wspace.hosts.find_or_initialize_by_address(addr)
  274. end
  275. else
  276. host = addr
  277. end
  278. # Truncate the info field at the maximum field length
  279. if opts[:info]
  280. opts[:info] = opts[:info][0,65535]
  281. end
  282. # Truncate the name field at the maximum field length
  283. if opts[:name]
  284. opts[:name] = opts[:name][0,255]
  285. end
  286. opts.each { |k,v|
  287. if (host.attribute_names.include?(k.to_s))
  288. unless host.attribute_locked?(k.to_s)
  289. host[k] = v.to_s.gsub(/[\x00-\x1f]/, '')
  290. end
  291. else
  292. dlog("Unknown attribute for ::Mdm::Host: #{k}")
  293. end
  294. }
  295. host.info = host.info[0,::Mdm::Host.columns_hash["info"].limit] if host.info
  296. # Set default fields if needed
  297. host.state = HostState::Alive if not host.state
  298. host.comm = '' if not host.comm
  299. host.workspace = wspace if not host.workspace
  300. if host.changed?
  301. msf_import_timestamps(opts,host)
  302. host.save!
  303. end
  304. host
  305. }
  306. end
  307. #
  308. # Update a host's attributes via semi-standardized sysinfo hash (Meterpreter)
  309. #
  310. # The opts parameter MUST contain the following entries
  311. # +:host+:: -- the host's ip address
  312. # +:info+:: -- the information hash
  313. # * 'Computer' -- the host name
  314. # * 'OS' -- the operating system string
  315. # * 'Architecture' -- the hardware architecture
  316. # * 'System Language' -- the system language
  317. #
  318. # The opts parameter can contain:
  319. # +:workspace+:: -- the workspace for this host
  320. #
  321. def update_host_via_sysinfo(opts)
  322. return if not active
  323. addr = opts.delete(:host) || return
  324. info = opts.delete(:info) || return
  325. # Sometimes a host setup through a pivot will see the address as "Remote Pipe"
  326. if addr.eql? "Remote Pipe"
  327. return
  328. end
  329. ::ActiveRecord::Base.connection_pool.with_connection {
  330. wspace = opts.delete(:workspace) || workspace
  331. if wspace.kind_of? String
  332. wspace = find_workspace(wspace)
  333. end
  334. if not addr.kind_of? ::Mdm::Host
  335. addr = normalize_host(addr)
  336. addr, scope = addr.split('%', 2)
  337. opts[:scope] = scope if scope
  338. unless ipv46_validator(addr)
  339. raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
  340. end
  341. if opts[:comm] and opts[:comm].length > 0
  342. host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm])
  343. else
  344. host = wspace.hosts.find_or_initialize_by_address(addr)
  345. end
  346. else
  347. host = addr
  348. end
  349. res = {}
  350. if info['Computer']
  351. res[:name] = info['Computer']
  352. end
  353. if info['Architecture']
  354. res[:arch] = info['Architecture'].split(/\s+/).first
  355. end
  356. if info['OS'] =~ /^Windows\s*([^\(]+)\(([^\)]+)\)/i
  357. res[:os_name] = "Microsoft Windows"
  358. res[:os_flavor] = $1.strip
  359. build = $2.strip
  360. if build =~ /Service Pack (\d+)/
  361. res[:os_sp] = "SP" + $1
  362. else
  363. res[:os_sp] = "SP0"
  364. end
  365. end
  366. if info["System Language"]
  367. case info["System Language"]
  368. when /^en_/
  369. res[:os_lang] = "English"
  370. end
  371. end
  372. # Truncate the info field at the maximum field length
  373. if res[:info]
  374. res[:info] = res[:info][0,65535]
  375. end
  376. # Truncate the name field at the maximum field length
  377. if res[:name]
  378. res[:name] = res[:name][0,255]
  379. end
  380. res.each { |k,v|
  381. if (host.attribute_names.include?(k.to_s))
  382. unless host.attribute_locked?(k.to_s)
  383. host[k] = v.to_s.gsub(/[\x00-\x1f]/, '')
  384. end
  385. else
  386. dlog("Unknown attribute for Host: #{k}")
  387. end
  388. }
  389. # Set default fields if needed
  390. host.state = HostState::Alive if not host.state
  391. host.comm = '' if not host.comm
  392. host.workspace = wspace if not host.workspace
  393. if host.changed?
  394. host.save!
  395. end
  396. host
  397. }
  398. end
  399. #
  400. # Iterates over the hosts table calling the supplied block with the host
  401. # instance of each entry.
  402. #
  403. def each_host(wspace=workspace, &block)
  404. ::ActiveRecord::Base.connection_pool.with_connection {
  405. wspace.hosts.each do |host|
  406. block.call(host)
  407. end
  408. }
  409. end
  410. #
  411. # Returns a list of all hosts in the database
  412. #
  413. def hosts(wspace = workspace, only_up = false, addresses = nil)
  414. ::ActiveRecord::Base.connection_pool.with_connection {
  415. conditions = {}
  416. conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if only_up
  417. conditions[:address] = addresses if addresses
  418. wspace.hosts.where(conditions).order(:address)
  419. }
  420. end
  421. def find_or_create_service(opts)
  422. report_service(opts)
  423. end
  424. #
  425. # Record a service in the database.
  426. #
  427. # opts MUST contain
  428. # +:host+:: the host where this service is running
  429. # +:port+:: the port where this service listens
  430. # +:proto+:: the transport layer protocol (e.g. tcp, udp)
  431. #
  432. # opts may contain
  433. # +:name+:: the application layer protocol (e.g. ssh, mssql, smb)
  434. # +:sname+:: an alias for the above
  435. #
  436. def report_service(opts)
  437. return if not active
  438. ::ActiveRecord::Base.connection_pool.with_connection { |conn|
  439. addr = opts.delete(:host) || return
  440. hname = opts.delete(:host_name)
  441. hmac = opts.delete(:mac)
  442. host = nil
  443. wspace = opts.delete(:workspace) || workspace
  444. hopts = {:workspace => wspace, :host => addr}
  445. hopts[:name] = hname if hname
  446. hopts[:mac] = hmac if hmac
  447. # Other report_* methods take :sname to mean the service name, so we
  448. # map it here to ensure it ends up in the right place despite not being
  449. # a real column.
  450. if opts[:sname]
  451. opts[:name] = opts.delete(:sname)
  452. end
  453. if addr.kind_of? ::Mdm::Host
  454. host = addr
  455. addr = host.address
  456. else
  457. host = report_host(hopts)
  458. end
  459. if opts[:port].to_i.zero?
  460. dlog("Skipping port zero for service '%s' on host '%s'" % [opts[:name],host.address])
  461. return nil
  462. end
  463. ret = {}
  464. =begin
  465. host = get_host(:workspace => wspace, :address => addr)
  466. if host
  467. host.updated_at = host.created_at
  468. host.state = HostState::Alive
  469. host.save!
  470. end
  471. =end
  472. proto = opts[:proto] || 'tcp'
  473. service = host.services.find_or_initialize_by_port_and_proto(opts[:port].to_i, proto)
  474. opts.each { |k,v|
  475. if (service.attribute_names.include?(k.to_s))
  476. service[k] = ((v and k == :name) ? v.to_s.downcase : v)
  477. else
  478. dlog("Unknown attribute for Service: #{k}")
  479. end
  480. }
  481. service.state ||= ServiceState::Open
  482. service.info ||= ""
  483. if (service and service.changed?)
  484. msf_import_timestamps(opts,service)
  485. service.save!
  486. end
  487. ret[:service] = service
  488. }
  489. end
  490. def get_service(wspace, host, proto, port)
  491. ::ActiveRecord::Base.connection_pool.with_connection {
  492. host = get_host(:workspace => wspace, :address => host)
  493. return if not host
  494. return host.services.find_by_proto_and_port(proto, port)
  495. }
  496. end
  497. #
  498. # Iterates over the services table calling the supplied block with the
  499. # service instance of each entry.
  500. #
  501. def each_service(wspace=workspace, &block)
  502. ::ActiveRecord::Base.connection_pool.with_connection {
  503. services(wspace).each do |service|
  504. block.call(service)
  505. end
  506. }
  507. end
  508. #
  509. # Returns a list of all services in the database
  510. #
  511. def services(wspace = workspace, only_up = false, proto = nil, addresses = nil, ports = nil, names = nil)
  512. ::ActiveRecord::Base.connection_pool.with_connection {
  513. conditions = {}
  514. conditions[:state] = [ServiceState::Open] if only_up
  515. conditions[:proto] = proto if proto
  516. conditions["hosts.address"] = addresses if addresses
  517. conditions[:port] = ports if ports
  518. conditions[:name] = names if names
  519. wspace.services.includes(:host).where(conditions).order("hosts.address, port")
  520. }
  521. end
  522. # Returns a session based on opened_time, host address, and workspace
  523. # (or returns nil)
  524. def get_session(opts)
  525. return if not active
  526. ::ActiveRecord::Base.connection_pool.with_connection {
  527. wspace = opts[:workspace] || opts[:wspace] || workspace
  528. addr = opts[:addr] || opts[:address] || opts[:host] || return
  529. host = get_host(:workspace => wspace, :host => addr)
  530. time = opts[:opened_at] || opts[:created_at] || opts[:time] || return
  531. ::Mdm::Session.find_by_host_id_and_opened_at(host.id, time)
  532. }
  533. end
  534. # Record a new session in the database
  535. #
  536. # opts MUST contain either
  537. # +:session+:: the Msf::Session object we are reporting
  538. # +:host+:: the Host object we are reporting a session on.
  539. #
  540. def report_session(opts)
  541. return if not active
  542. ::ActiveRecord::Base.connection_pool.with_connection {
  543. if opts[:session]
  544. raise ArgumentError.new("Invalid :session, expected Msf::Session") unless opts[:session].kind_of? Msf::Session
  545. session = opts[:session]
  546. wspace = opts[:workspace] || find_workspace(session.workspace)
  547. h_opts = { }
  548. h_opts[:host] = normalize_host(session)
  549. h_opts[:arch] = session.arch if session.respond_to?(:arch) and session.arch
  550. h_opts[:workspace] = wspace
  551. host = find_or_create_host(h_opts)
  552. sess_data = {
  553. :host_id => host.id,
  554. :stype => session.type,
  555. :desc => session.info,
  556. :platform => session.platform,
  557. :via_payload => session.via_payload,
  558. :via_exploit => session.via_exploit,
  559. :routes => [],
  560. :datastore => session.exploit_datastore.to_h,
  561. :opened_at => Time.now.utc,
  562. :last_seen => Time.now.utc,
  563. :local_id => session.sid
  564. }
  565. elsif opts[:host]
  566. raise ArgumentError.new("Invalid :host, expected Host object") unless opts[:host].kind_of? ::Mdm::Host
  567. host = opts[:host]
  568. sess_data = {
  569. :host_id => host.id,
  570. :stype => opts[:stype],
  571. :desc => opts[:desc],
  572. :platform => opts[:platform],
  573. :via_payload => opts[:via_payload],
  574. :via_exploit => opts[:via_exploit],
  575. :routes => opts[:routes] || [],
  576. :datastore => opts[:datastore],
  577. :opened_at => opts[:opened_at],
  578. :closed_at => opts[:closed_at],
  579. :last_seen => opts[:last_seen] || opts[:closed_at],
  580. :close_reason => opts[:close_reason],
  581. }
  582. else
  583. raise ArgumentError.new("Missing option :session or :host")
  584. end
  585. ret = {}
  586. # Truncate the session data if necessary
  587. if sess_data[:desc]
  588. sess_data[:desc] = sess_data[:desc][0,255]
  589. end
  590. # In the case of multi handler we cannot yet determine the true
  591. # exploit responsible. But we can at least show the parent versus
  592. # just the generic handler:
  593. if session and session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
  594. sess_data[:via_exploit] = sess_data[:datastore]['ParentModule']
  595. end
  596. s = ::Mdm::Session.new(sess_data)
  597. s.save!
  598. if opts[:session]
  599. session.db_record = s
  600. end
  601. # If this is a live session, we know the host is vulnerable to something.
  602. if opts[:session] and session.via_exploit
  603. return unless host
  604. mod = framework.modules.create(session.via_exploit)
  605. if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
  606. mod_fullname = sess_data[:datastore]['ParentModule']
  607. mod_name = ::Mdm::ModuleDetail.find_by_fullname(mod_fullname).name
  608. else
  609. mod_name = mod.name
  610. mod_fullname = mod.fullname
  611. end
  612. vuln_info = {
  613. :host => host.address,
  614. :name => mod_name,
  615. :refs => mod.references,
  616. :workspace => wspace,
  617. :exploited_at => Time.now.utc,
  618. :info => "Exploited by #{mod_fullname} to create Session #{s.id}"
  619. }
  620. port = session.exploit_datastore["RPORT"]
  621. service = (port ? host.services.find_by_port(port.to_i) : nil)
  622. vuln_info[:service] = service if service
  623. vuln = framework.db.report_vuln(vuln_info)
  624. if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
  625. via_exploit = sess_data[:datastore]['ParentModule']
  626. else
  627. via_exploit = session.via_exploit
  628. end
  629. attempt_info = {
  630. :timestamp => Time.now.utc,
  631. :workspace => wspace,
  632. :module => via_exploit,
  633. :username => session.username,
  634. :refs => mod.references,
  635. :session_id => s.id,
  636. :host => host,
  637. :service => service,
  638. :vuln => vuln
  639. }
  640. framework.db.report_exploit_success(attempt_info)
  641. end
  642. s
  643. }
  644. end
  645. #
  646. # Record a session event in the database
  647. #
  648. # opts MUST contain one of:
  649. # +:session+:: the Msf::Session OR the ::Mdm::Session we are reporting
  650. # +:etype+:: event type, enum: command, output, upload, download, filedelete
  651. #
  652. # opts may contain
  653. # +:output+:: the data for an output event
  654. # +:command+:: the data for an command event
  655. # +:remote_path+:: path to the associated file for upload, download, and filedelete events
  656. # +:local_path+:: path to the associated file for upload, and download
  657. #
  658. def report_session_event(opts)
  659. return if not active
  660. raise ArgumentError.new("Missing required option :session") if opts[:session].nil?
  661. raise ArgumentError.new("Expected an :etype") unless opts[:etype]
  662. session = nil
  663. ::ActiveRecord::Base.connection_pool.with_connection {
  664. if opts[:session].respond_to? :db_record
  665. session = opts[:session].db_record
  666. if session.nil?
  667. # The session doesn't have a db_record which means
  668. # a) the database wasn't connected at session registration time
  669. # or
  670. # b) something awful happened and the report_session call failed
  671. #
  672. # Either way, we can't do anything with this session as is, so
  673. # log a warning and punt.
  674. wlog("Warning: trying to report a session_event for a session with no db_record (#{opts[:session].sid})")
  675. return
  676. end
  677. event_data = { :created_at => Time.now }
  678. else
  679. session = opts[:session]
  680. event_data = { :created_at => opts[:created_at] }
  681. end
  682. event_data[:session_id] = session.id
  683. [:remote_path, :local_path, :output, :command, :etype].each do |attr|
  684. event_data[attr] = opts[attr] if opts[attr]
  685. end
  686. s = ::Mdm::SessionEvent.create(event_data)
  687. }
  688. end
  689. def report_session_route(session, route)
  690. return if not active
  691. if session.respond_to? :db_record
  692. s = session.db_record
  693. else
  694. s = session
  695. end
  696. unless s.respond_to?(:routes)
  697. raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}")
  698. end
  699. ::ActiveRecord::Base.connection_pool.with_connection {
  700. subnet, netmask = route.split("/")
  701. s.routes.create(:subnet => subnet, :netmask => netmask)
  702. }
  703. end
  704. def report_session_route_remove(session, route)
  705. return if not active
  706. if session.respond_to? :db_record
  707. s = session.db_record
  708. else
  709. s = session
  710. end
  711. unless s.respond_to?(:routes)
  712. raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}")
  713. end
  714. ::ActiveRecord::Base.connection_pool.with_connection {
  715. subnet, netmask = route.split("/")
  716. r = s.routes.find_by_subnet_and_netmask(subnet, netmask)
  717. r.destroy if r
  718. }
  719. end
  720. def report_exploit_success(opts)
  721. ::ActiveRecord::Base.connection_pool.with_connection {
  722. wspace = opts.delete(:workspace) || workspace
  723. mrefs = opts.delete(:refs) || return
  724. host = opts.delete(:host)
  725. port = opts.delete(:port)
  726. prot = opts.delete(:proto)
  727. svc = opts.delete(:service)
  728. vuln = opts.delete(:vuln)
  729. timestamp = opts.delete(:timestamp)
  730. username = opts.delete(:username)
  731. mname = opts.delete(:module)
  732. # Look up or generate the host as appropriate
  733. if not (host and host.kind_of? ::Mdm::Host)
  734. if svc.kind_of? ::Mdm::Service
  735. host = svc.host
  736. else
  737. host = report_host(:workspace => wspace, :address => host )
  738. end
  739. end
  740. # Bail if we dont have a host object
  741. return if not host
  742. # Look up or generate the service as appropriate
  743. if port and svc.nil?
  744. svc = report_service(:workspace => wspace, :host => host, :port => port, :proto => prot ) if port
  745. end
  746. if not vuln
  747. # Create a references map from the module list
  748. ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref|
  749. if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val)
  750. "#{ref.ctx_id}-#{ref.ctx_val}"
  751. else
  752. ref.to_s
  753. end
  754. })
  755. # Try find a matching vulnerability
  756. vuln = find_vuln_by_refs(ref_objs, host, svc)
  757. end
  758. # We have match, lets create a vuln_attempt record
  759. if vuln
  760. attempt_info = {
  761. :vuln_id => vuln.id,
  762. :attempted_at => timestamp || Time.now.utc,
  763. :exploited => true,
  764. :username => username || "unknown",
  765. :module => mname
  766. }
  767. attempt_info[:session_id] = opts[:session_id] if opts[:session_id]
  768. attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id]
  769. vuln.vuln_attempts.create(attempt_info)
  770. # Correct the vuln's associated service if necessary
  771. if svc and vuln.service_id.nil?
  772. vuln.service = svc
  773. vuln.save
  774. end
  775. end
  776. # Report an exploit attempt all the same
  777. attempt_info = {
  778. :attempted_at => timestamp || Time.now.utc,
  779. :exploited => true,
  780. :username => username || "unknown",
  781. :module => mname
  782. }
  783. attempt_info[:vuln_id] = vuln.id if vuln
  784. attempt_info[:session_id] = opts[:session_id] if opts[:session_id]
  785. attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id]
  786. if svc
  787. attempt_info[:port] = svc.port
  788. attempt_info[:proto] = svc.proto
  789. end
  790. if port and svc.nil?
  791. attempt_info[:port] = port
  792. attempt_info[:proto] = prot || "tcp"
  793. end
  794. host.exploit_attempts.create(attempt_info)
  795. }
  796. end
  797. def report_exploit_failure(opts)
  798. ::ActiveRecord::Base.connection_pool.with_connection {
  799. wspace = opts.delete(:workspace) || workspace
  800. mrefs = opts.delete(:refs) || return
  801. host = opts.delete(:host)
  802. port = opts.delete(:port)
  803. prot = opts.delete(:proto)
  804. svc = opts.delete(:service)
  805. vuln = opts.delete(:vuln)
  806. timestamp = opts.delete(:timestamp)
  807. freason = opts.delete(:fail_reason)
  808. fdetail = opts.delete(:fail_detail)
  809. username = opts.delete(:username)
  810. mname = opts.delete(:module)
  811. # Look up the host as appropriate
  812. if not (host and host.kind_of? ::Mdm::Host)
  813. if svc.kind_of? ::Mdm::Service
  814. host = svc.host
  815. else
  816. host = get_host( :workspace => wspace, :address => host )
  817. end
  818. end
  819. # Bail if we dont have a host object
  820. return if not host
  821. # Look up the service as appropriate
  822. if port and svc.nil?
  823. prot ||= "tcp"
  824. svc = get_service(wspace, host, prot, port) if port
  825. end
  826. if not vuln
  827. # Create a references map from the module list
  828. ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref|
  829. if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val)
  830. "#{ref.ctx_id}-#{ref.ctx_val}"
  831. else
  832. ref.to_s
  833. end
  834. })
  835. # Try find a matching vulnerability
  836. vuln = find_vuln_by_refs(ref_objs, host, svc)
  837. end
  838. # Report a vuln_attempt if we found a match
  839. if vuln
  840. attempt_info = {
  841. :attempted_at => timestamp || Time.now.utc,
  842. :exploited => false,
  843. :fail_reason => freason,
  844. :fail_detail => fdetail,
  845. :username => username || "unknown",
  846. :module => mname
  847. }
  848. vuln.vuln_attempts.create(attempt_info)
  849. end
  850. # Report an exploit attempt all the same
  851. attempt_info = {
  852. :attempted_at => timestamp || Time.now.utc,
  853. :exploited => false,
  854. :username => username || "unknown",
  855. :module => mname,
  856. :fail_reason => freason,
  857. :fail_detail => fdetail
  858. }
  859. attempt_info[:vuln_id] = vuln.id if vuln
  860. if svc
  861. attempt_info[:port] = svc.port
  862. attempt_info[:proto] = svc.proto
  863. end
  864. if port and svc.nil?
  865. attempt_info[:port] = port
  866. attempt_info[:proto] = prot || "tcp"
  867. end
  868. host.exploit_attempts.create(attempt_info)
  869. }
  870. end
  871. def report_vuln_attempt(vuln, opts)
  872. ::ActiveRecord::Base.connection_pool.with_connection {
  873. return if not vuln
  874. info = {}
  875. # Opts can be keyed by strings or symbols
  876. ::Mdm::VulnAttempt.column_names.each do |kn|
  877. k = kn.to_sym
  878. next if ['id', 'vuln_id'].include?(kn)
  879. info[k] = opts[kn] if opts[kn]
  880. info[k] = opts[k] if opts[k]
  881. end
  882. return unless info[:attempted_at]
  883. vuln.vuln_attempts.create(info)
  884. }
  885. end
  886. def report_exploit_attempt(host, opts)
  887. ::ActiveRecord::Base.connection_pool.with_connection {
  888. return if not host
  889. info = {}
  890. # Opts can be keyed by strings or symbols
  891. ::Mdm::VulnAttempt.column_names.each do |kn|
  892. k = kn.to_sym
  893. next if ['id', 'host_id'].include?(kn)
  894. info[k] = opts[kn] if opts[kn]
  895. info[k] = opts[k] if opts[k]
  896. end
  897. host.exploit_attempts.create(info)
  898. }
  899. end
  900. def get_client(opts)
  901. ::ActiveRecord::Base.connection_pool.with_connection {
  902. wspace = opts.delete(:workspace) || workspace
  903. host = get_host(:workspace => wspace, :host => opts[:host]) || return
  904. client = host.clients.where({:ua_string => opts[:ua_string]}).first()
  905. return client
  906. }
  907. end
  908. def find_or_create_client(opts)
  909. report_client(opts)
  910. end
  911. #
  912. # Report a client running on a host.
  913. #
  914. # opts MUST contain
  915. # +:ua_string+:: the value of the User-Agent header
  916. # +:host+:: the host where this client connected from, can be an ip address or a Host object
  917. #
  918. # opts can contain
  919. # +:ua_name+:: one of the Msf::HttpClients constants
  920. # +:ua_ver+:: detected version of the given client
  921. # +:campaign+:: an id or Campaign object
  922. #
  923. # Returns a Client.
  924. #
  925. def report_client(opts)
  926. return if not active
  927. ::ActiveRecord::Base.connection_pool.with_connection {
  928. addr = opts.delete(:host) || return
  929. wspace = opts.delete(:workspace) || workspace
  930. report_host(:workspace => wspace, :host => addr)
  931. ret = {}
  932. host = get_host(:workspace => wspace, :host => addr)
  933. client = host.clients.find_or_initialize_by_ua_string(opts[:ua_string])
  934. opts[:ua_string] = opts[:ua_string].to_s
  935. campaign = opts.delete(:campaign)
  936. if campaign
  937. case campaign
  938. when Campaign
  939. opts[:campaign_id] = campaign.id
  940. else
  941. opts[:campaign_id] = campaign
  942. end
  943. end
  944. opts.each { |k,v|
  945. if (client.attribute_names.include?(k.to_s))
  946. client[k] = v
  947. else
  948. dlog("Unknown attribute for Client: #{k}")
  949. end
  950. }
  951. if (client and client.changed?)
  952. client.save!
  953. end
  954. ret[:client] = client
  955. }
  956. end
  957. #
  958. # This method iterates the vulns table calling the supplied block with the
  959. # vuln instance of each entry.
  960. #
  961. def each_vuln(wspace=workspace,&block)
  962. ::ActiveRecord::Base.connection_pool.with_connection {
  963. wspace.vulns.each do |vulns|
  964. block.call(vulns)
  965. end
  966. }
  967. end
  968. #
  969. # This methods returns a list of all vulnerabilities in the database
  970. #
  971. def vulns(wspace=workspace)
  972. ::ActiveRecord::Base.connection_pool.with_connection {
  973. wspace.vulns
  974. }
  975. end
  976. #
  977. # This methods returns a list of all credentials in the database
  978. #
  979. def creds(wspace=workspace)
  980. ::ActiveRecord::Base.connection_pool.with_connection {
  981. Mdm::Cred.includes({:service => :host}).where("hosts.workspace_id = ?", wspace.id)
  982. }
  983. end
  984. #
  985. # This method returns a list of all exploited hosts in the database.
  986. #
  987. def exploited_hosts(wspace=workspace)
  988. ::ActiveRecord::Base.connection_pool.with_connection {
  989. wspace.exploited_hosts
  990. }
  991. end
  992. #
  993. # This method iterates the notes table calling the supplied block with the
  994. # note instance of each entry.
  995. #
  996. def each_note(wspace=workspace, &block)
  997. ::ActiveRecord::Base.connection_pool.with_connection {
  998. wspace.notes.each do |note|
  999. block.call(note)
  1000. end
  1001. }
  1002. end
  1003. #
  1004. # Find or create a note matching this type/data
  1005. #
  1006. def find_or_create_note(opts)
  1007. report_note(opts)
  1008. end
  1009. #
  1010. # Report a Note to the database. Notes can be tied to a ::Mdm::Workspace, Host, or Service.
  1011. #
  1012. # opts MUST contain
  1013. # +:data+:: whatever it is you're making a note of
  1014. # +:type+:: The type of note, e.g. smb_peer_os
  1015. #
  1016. # opts can contain
  1017. # +:workspace+:: the workspace to associate with this Note
  1018. # +:host+:: an IP address or a Host object to associate with this Note
  1019. # +:service+:: a Service object to associate with this Note
  1020. # +:port+:: along with +:host+ and +:proto+, a service to associate with this Note
  1021. # +:proto+:: along with +:host+ and +:port+, a service to associate with this Note
  1022. # +:update+:: what to do in case a similar Note exists, see below
  1023. #
  1024. # The +:update+ option can have the following values:
  1025. # +:unique+:: allow only a single Note per +:host+/+:type+ pair
  1026. # +:unique_data+:: like +:uniqe+, but also compare +:data+
  1027. # +:insert+:: always insert a new Note even if one with identical values exists
  1028. #
  1029. # If the provided +:host+ is an IP address and does not exist in the
  1030. # database, it will be created. If +:workspace+, +:host+ and +:service+
  1031. # are all omitted, the new Note will be associated with the current
  1032. # workspace.
  1033. #
  1034. def report_note(opts)
  1035. return if not active
  1036. ::ActiveRecord::Base.connection_pool.with_connection {
  1037. wspace = opts.delete(:workspace) || workspace
  1038. if wspace.kind_of? String
  1039. wspace = find_workspace(wspace)
  1040. end
  1041. seen = opts.delete(:seen) || false
  1042. crit = opts.delete(:critical) || false
  1043. host = nil
  1044. addr = nil
  1045. # Report the host so it's there for the Proc to use below
  1046. if opts[:host]
  1047. if opts[:host].kind_of? ::Mdm::Host
  1048. host = opts[:host]
  1049. else
  1050. addr = normalize_host(opts[:host])
  1051. host = report_host({:workspace => wspace, :host => addr})
  1052. end
  1053. # Do the same for a service if that's also included.
  1054. if (opts[:port])
  1055. proto = nil
  1056. sname = nil
  1057. case opts[:proto].to_s.downcase # Catch incorrect usages
  1058. when 'tcp','udp'
  1059. proto = opts[:proto]
  1060. sname = opts[:sname] if opts[:sname]
  1061. when 'dns','snmp','dhcp'
  1062. proto = 'udp'
  1063. sname = opts[:proto]
  1064. else
  1065. proto = 'tcp'
  1066. sname = opts[:proto]
  1067. end
  1068. sopts = {
  1069. :workspace => wspace,
  1070. :host => host,
  1071. :port => opts[:port],
  1072. :proto => proto
  1073. }
  1074. sopts[:name] = sname if sname
  1075. report_service(sopts)
  1076. end
  1077. end
  1078. # Update Modes can be :unique, :unique_data, :insert
  1079. mode = opts[:update] || :unique
  1080. ret = {}
  1081. if addr and not host
  1082. host = get_host(:workspace => wspace, :host => addr)
  1083. end
  1084. if host and (opts[:port] and opts[:proto])
  1085. service = get_service(wspace, host, opts[:proto], opts[:port])
  1086. elsif opts[:service] and opts[:service].kind_of? ::Mdm::Service
  1087. service = opts[:service]
  1088. end
  1089. =begin
  1090. if host
  1091. host.updated_at = host.created_at
  1092. host.state = HostState::Alive
  1093. host.save!
  1094. end
  1095. =end
  1096. ntype = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required")
  1097. data = opts[:data] || (raise RuntimeError, "Note :data is required")
  1098. method = nil
  1099. args = []
  1100. note = nil
  1101. conditions = { :ntype => ntype }
  1102. conditions[:host_id] = host[:id] if host
  1103. conditions[:service_id] = service[:id] if service
  1104. case mode
  1105. when :unique
  1106. notes = wspace.notes.where(conditions)
  1107. # Only one note of this type should exist, make a new one if it
  1108. # isn't there. If it is, grab it and overwrite its data.
  1109. if notes.empty?
  1110. note = wspace.notes.new(conditions)
  1111. else
  1112. note = notes[0]
  1113. end
  1114. note.data = data
  1115. when :unique_data
  1116. notes = wspace.notes.where(conditions)
  1117. # Don't make a new Note with the same data as one that already
  1118. # exists for the given: type and (host or service)
  1119. notes.each do |n|
  1120. # Compare the deserialized data from the table to the raw
  1121. # data we're looking for. Because of the serialization we
  1122. # can't do this easily or reliably in SQL.
  1123. if n.data == data
  1124. note = n
  1125. break
  1126. end
  1127. end
  1128. if not note
  1129. # We didn't find one with the data we're looking for, make
  1130. # a new one.
  1131. note = wspace.notes.new(conditions.merge(:data => data))
  1132. end
  1133. else
  1134. # Otherwise, assume :insert, which means always make a new one
  1135. note = wspace.notes.new
  1136. if host
  1137. note.host_id = host[:id]
  1138. end
  1139. if opts[:service] and opts[:service].kind_of? ::Mdm::Service
  1140. note.service_id = opts[:service][:id]
  1141. end
  1142. note.seen = seen
  1143. note.critical = crit
  1144. note.ntype = ntype
  1145. note.data = data
  1146. end
  1147. msf_import_timestamps(opts,note)
  1148. note.save!
  1149. ret[:note] = note
  1150. }
  1151. end
  1152. #
  1153. # This methods returns a list of all notes in the database
  1154. #
  1155. def notes(wspace=workspace)
  1156. ::ActiveRecord::Base.connection_pool.with_connection {
  1157. wspace.notes
  1158. }
  1159. end
  1160. # This is only exercised by MSF3 XML importing for now. Needs the wait
  1161. # conditions and return hash as well.
  1162. def report_host_tag(opts)
  1163. name = opts.delete(:name)
  1164. raise DBImportError.new("Missing required option :name") unless name
  1165. addr = opts.delete(:addr)
  1166. raise DBImportError.new("Missing required option :addr") unless addr
  1167. wspace = opts.delete(:wspace)
  1168. raise DBImportError.new("Missing required option :wspace") unless wspace
  1169. ::ActiveRecord::Base.connection_pool.with_connection {
  1170. if wspace.kind_of? String
  1171. wspace = find_workspace(wspace)
  1172. end
  1173. host = nil
  1174. report_host(:workspace => wspace, :address => addr)
  1175. host = get_host(:workspace => wspace, :address => addr)
  1176. desc = opts.delete(:desc)
  1177. summary = opts.delete(:summary)
  1178. detail = opts.delete(:detail)
  1179. crit = opts.delete(:crit)
  1180. possible_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and tags.name = ?", wspace.id, name).order("tags.id DESC").limit(1)
  1181. tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first)
  1182. tag.name = name
  1183. tag.desc = desc
  1184. tag.report_summary = !!summary
  1185. tag.report_detail = !!detail
  1186. tag.critical = !!crit
  1187. tag.hosts = tag.hosts | [host]
  1188. tag.save! if tag.changed?
  1189. }
  1190. end
  1191. #
  1192. # Store a set of credentials in the database.
  1193. #
  1194. # report_auth_info used to create a note, now it creates
  1195. # an entry in the creds table. It's much more akin to
  1196. # report_vuln() now.
  1197. #
  1198. # opts MUST contain
  1199. # +:host+:: an IP address or Host object reference
  1200. # +:port+:: a port number
  1201. #
  1202. # opts can contain
  1203. # +:user+:: the username
  1204. # +:pass+:: the password, or path to ssh_key
  1205. # +:ptype+:: the type of password (password(ish), hash, or ssh_key)
  1206. # +:proto+:: a transport name for the port
  1207. # +:sname+:: service name
  1208. # +:active+:: by default, a cred is active, unless explicitly false
  1209. # +:proof+:: data used to prove the account is actually active.
  1210. #
  1211. # Sources: Credentials can be sourced from another credential, or from
  1212. # a vulnerability. For example, if an exploit was used to dump the
  1213. # smb_hashes, and this credential comes from there, the source_id would
  1214. # be the Vuln id (as reported by report_vuln) and the type would be "Vuln".
  1215. #
  1216. # +:source_id+:: The Vuln or Cred id of the source of this cred.
  1217. # +:source_type+:: Either Vuln or Cred
  1218. #
  1219. # TODO: This is written somewhat host-centric, when really the
  1220. # Service is the thing. Need to revisit someday.
  1221. def report_auth_info(opts={})
  1222. return if not active
  1223. raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
  1224. raise ArgumentError.new("Missing required option :port") if (opts[:port].nil? and opts[:service].nil?)
  1225. if (not opts[:host].kind_of?(::Mdm::Host)) and (not validate_ips(opts[:host]))
  1226. raise ArgumentError.new("Invalid address or object for :host (#{opts[:host].inspect})")
  1227. end
  1228. host = opts.delete(:host)
  1229. ptype = opts.delete(:type) || "password"
  1230. token = [opts.delete(:user), opts.delete(:pass)]
  1231. sname = opts.delete(:sname)
  1232. port = opts.delete(:port)
  1233. proto = opts.delete(:proto) || "tcp"
  1234. proof = opts.delete(:proof)
  1235. source_id = opts.delete(:source_id)
  1236. source_type = opts.delete(:source_type)
  1237. duplicate_ok = opts.delete(:duplicate_ok)
  1238. # Nil is true for active.
  1239. active = (opts[:active] || opts[:active].nil?) ? true : false
  1240. wspace = opts.delete(:workspace) || workspace
  1241. # Service management; assume the user knows what
  1242. # he's talking about.
  1243. service = opts.delete(:service) || report_service(:host => host, :port => port, :proto => proto, :name => sname, :workspace => wspace)
  1244. # Non-US-ASCII usernames are tripping up the database at the moment, this is a temporary fix until we update the tables
  1245. ( token[0] = token[0].gsub(/[\x00-\x1f\x7f-\xff]/){|m| "\\x%.2x" % m.unpack("C")[0] } ) if token[0]
  1246. ( token[1] = token[1].gsub(/[\x00-\x1f\x7f-\xff]/){|m| "\\x%.2x" % m.unpack("C")[0] } ) if token[1]
  1247. ret = {}
  1248. #Check to see if the creds already exist. We look also for a downcased username with the
  1249. #same password because we can fairly safely assume they are not in fact two seperate creds.
  1250. #this allows us to hedge against duplication of creds in the DB.
  1251. if duplicate_ok
  1252. # If duplicate usernames are okay, find by both user and password (allows
  1253. # for actual duplicates to get modified updated_at, sources, etc)
  1254. if token[0].nil? or token[0].empty?
  1255. cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
  1256. else
  1257. cred = service.creds.find_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
  1258. unless cred
  1259. dcu = token[0].downcase
  1260. cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "")
  1261. unless cred
  1262. cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
  1263. end
  1264. end
  1265. end
  1266. else
  1267. # Create the cred by username only (so we can change passwords)
  1268. if token[0].nil? or token[0].empty?
  1269. cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype)
  1270. else
  1271. cred = service.creds.find_by_user_and_ptype(token[0] || "", ptype)
  1272. unless cred
  1273. dcu = token[0].downcase
  1274. cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "")
  1275. unless cred
  1276. cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype)
  1277. end
  1278. end
  1279. end
  1280. end
  1281. # Update with the password
  1282. cred.pass = (token[1] || "")
  1283. # Annotate the credential
  1284. cred.ptype = ptype
  1285. cred.active = active
  1286. # Update the source ID only if there wasn't already one.
  1287. if source_id and !cred.source_id
  1288. cred.source_id = source_id
  1289. cred.source_type = source_type if source_type
  1290. end
  1291. # Safe proof (lazy way) -- doesn't chop expanded
  1292. # characters correctly, but shouldn't ever be a problem.
  1293. unless proof.nil?
  1294. proof = Rex::Text.to_hex_ascii(proof)
  1295. proof = proof[0,4096]
  1296. end
  1297. cred.proof = proof
  1298. # Update the timestamp
  1299. if cred.changed?
  1300. msf_import_timestamps(opts,cred)
  1301. cred.save!
  1302. end
  1303. # Ensure the updated_at is touched any time report_auth_info is called
  1304. # except when it's set explicitly (as it is for imports)
  1305. unless opts[:updated_at] || opts["updated_at"]
  1306. cred.updated_at = Time.now.utc
  1307. cred.save!
  1308. end
  1309. ret[:cred] = cred
  1310. end
  1311. alias :report_cred :report_auth_info
  1312. alias :report_auth :report_auth_info
  1313. #
  1314. # Find or create a credential matching this type/data
  1315. #
  1316. def find_or_create_cred(opts)
  1317. report_auth_info(opts)
  1318. end
  1319. #
  1320. # This method iterates the creds table calling the supplied block with the
  1321. # cred instance of each entry.
  1322. #
  1323. def each_cred(wspace=workspace,&block)
  1324. ::ActiveRecord::Base.connection_pool.with_connection {
  1325. wspace.creds.each do |cred|
  1326. block.call(cred)
  1327. end
  1328. }
  1329. end
  1330. def each_exploited_host(wspace=workspace,&block)
  1331. ::ActiveRecord::Base.connection_pool.with_connection {
  1332. wspace.exploited_hosts.each do |eh|
  1333. block.call(eh)
  1334. end
  1335. }
  1336. end
  1337. #
  1338. # Find or create a vuln matching this service/name
  1339. #
  1340. def find_or_create_vuln(opts)
  1341. report_vuln(opts)
  1342. end
  1343. #
  1344. # opts MUST contain
  1345. # +:host+:: the host where this vulnerability resides
  1346. # +:name+:: the friendly name for this vulnerability (title)
  1347. #
  1348. # opts can contain
  1349. # +:info+:: a human readable description of the vuln, free-form text
  1350. # +:refs+:: an array of Ref objects or string names of references
  1351. # +:details:: a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields
  1352. #
  1353. def report_vuln(opts)
  1354. return if not active
  1355. raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
  1356. raise ArgumentError.new("Deprecated data column for vuln, use .info instead") if opts[:data]
  1357. name = opts[:name] || return
  1358. info = opts[:info]
  1359. ::ActiveRecord::Base.connection_pool.with_connection {
  1360. wspace = opts.delete(:workspace) || workspace
  1361. exploited_at = opts[:exploited_at] || opts["exploited_at"]
  1362. details = opts.delete(:details)
  1363. rids = opts.delete(:ref_ids)
  1364. if opts[:refs]
  1365. rids ||= []
  1366. opts[:refs].each do |r|
  1367. if (r.respond_to?(:ctx_id)) and (r.respond_to?(:ctx_val))
  1368. r = "#{r.ctx_id}-#{r.ctx_val}"
  1369. end
  1370. rids << find_or_create_ref(:name => r)
  1371. end
  1372. end
  1373. host = nil
  1374. addr = nil
  1375. if opts[:host].kind_of? ::Mdm::Host
  1376. host = opts[:host]
  1377. else
  1378. host = report_host({:workspace => wspace, :host => opts[:host]})
  1379. addr = normalize_host(opts[:host])
  1380. end
  1381. ret = {}
  1382. # Truncate the info field at the maximum field length
  1383. if info
  1384. info = info[0,65535]
  1385. end
  1386. # Truncate the name field at the maximum field length
  1387. name = name[0,255]
  1388. # Placeholder for the vuln object
  1389. vuln = nil
  1390. # Identify the associated service
  1391. service = opts.delete(:service)
  1392. # Treat port zero as no service
  1393. if service or opts[:port].to_i > 0
  1394. if not service
  1395. proto = nil
  1396. case opts[:proto].to_s.downcase # Catch incorrect usages, as in report_note
  1397. when 'tcp','udp'
  1398. proto = opts[:proto]
  1399. when 'dns','snmp','dhcp'
  1400. proto = 'udp'
  1401. sname = opts[:proto]
  1402. else
  1403. proto = 'tcp'
  1404. sname = opts[:proto]
  1405. end
  1406. service = host.services.find_or_create_by_port_and_proto(opts[:port].to_i, proto)
  1407. end
  1408. # Try to find an existing vulnerability with the same service & references
  1409. # If there are multiple matches, choose the one with the most matches
  1410. # If a match is found on a vulnerability with no associated service,
  1411. # update that vulnerability with our service information. This helps
  1412. # prevent dupes of the same vuln found by both local patch and
  1413. # service detection.
  1414. if rids and rids.length > 0
  1415. vuln = find_vuln_by_refs(rids, host, service)
  1416. vuln.service = service if vuln
  1417. end
  1418. else
  1419. # Try to find an existing vulnerability with the same host & references
  1420. # If there are multiple matches, choose the one with the most matches
  1421. if rids and rids.length > 0
  1422. vuln = find_vuln_by_refs(rids, host)
  1423. end
  1424. end
  1425. # Try to match based on vuln_details records
  1426. if not vuln and opts[:details_match]
  1427. vuln = find_vuln_by_details(opts[:details_match], host, service)
  1428. if vuln and service and not vuln.service
  1429. vuln.service = service
  1430. end
  1431. end
  1432. # No matches, so create a new vuln record
  1433. unless vuln
  1434. if service
  1435. vuln = service.vulns.find_by_name(name)
  1436. else
  1437. vuln = host.vulns.find_by_name(name)
  1438. end
  1439. unless vuln
  1440. vinf = {
  1441. :host_id => host.id,
  1442. :name => name,
  1443. :info => info
  1444. }
  1445. vinf[:service_id] = service.id if service
  1446. vuln = Mdm::Vuln.create(vinf)
  1447. end
  1448. end
  1449. # Set the exploited_at value if provided
  1450. vuln.exploited_at = exploited_at if exploited_at
  1451. # Merge the references
  1452. if rids
  1453. vuln.refs << (rids - vuln.refs)
  1454. end
  1455. # Finalize
  1456. if vuln.changed?
  1457. msf_import_timestamps(opts,vuln)
  1458. vuln.save!
  1459. end
  1460. # Handle vuln_details parameters
  1461. report_vuln_details(vuln, details) if details
  1462. vuln
  1463. }
  1464. end
  1465. def find_vuln_by_refs(refs, host, service=nil)
  1466. vuln = nil
  1467. # Try to find an existing vulnerability with the same service & references
  1468. # If there are multiple matches, choose the one with the most matches
  1469. if service
  1470. refs_ids = refs.map{|x| x.id }
  1471. vuln = service.vulns.find(:all, :include => [:refs], :conditions => { 'refs.id' => refs_ids }).sort { |a,b|
  1472. ( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length
  1473. }.first
  1474. end
  1475. # Return if we matched based on service
  1476. return vuln if vuln
  1477. # Try to find an existing vulnerability with the same host & references
  1478. # If there are multiple matches, choose the one with the most matches
  1479. refs_ids = refs.map{|x| x.id }
  1480. vuln = host.vulns.find(:all, :include => [:refs], :conditions => { 'service_id' => nil, 'refs.id' => refs_ids }).sort { |a,b|
  1481. ( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length
  1482. }.first
  1483. return vuln
  1484. end
  1485. def find_vuln_by_details(details_map, host, service=nil)
  1486. # Create a modified version of the criteria in order to match against
  1487. # the joined version of the fields
  1488. crit = {}
  1489. details_map.each_pair do |k,v|
  1490. crit[ "vuln_details.#{k}" ] = v
  1491. end
  1492. vuln = nil
  1493. if service
  1494. vuln = service.vulns.find(:first, :include => [:vuln_details], :conditions => crit)
  1495. end
  1496. # Return if we matched based on service
  1497. return vuln if vuln
  1498. # Prevent matches against other services
  1499. crit["vulns.service_id"] = nil if service
  1500. vuln = host.vulns.find(:first, :include => [:vuln_details], :conditions => crit)
  1501. return vuln
  1502. end
  1503. def get_vuln(wspace, host, service, name, data='')
  1504. raise RuntimeError, "Not workspace safe: #{caller.inspect}"
  1505. ::ActiveRecord::Base.connection_pool.with_connection {
  1506. vuln = nil
  1507. if (service)
  1508. vuln = ::Mdm::Vuln.find.where("name = ? and service_id = ? and host_id = ?", name, service.id, host.id).order("vulns.id DESC").first()
  1509. else
  1510. vuln = ::Mdm::Vuln.find.where("name = ? and host_id = ?", name, host.id).first()
  1511. end
  1512. return vuln
  1513. }
  1514. end
  1515. #
  1516. # Find or create a reference matching this name
  1517. #
  1518. def find_or_create_ref(opts)
  1519. ret = {}
  1520. ret[:ref] = get_ref(opts[:name])
  1521. return ret[:ref] if ret[:ref]
  1522. ::ActiveRecord::Base.connection_pool.with_connection {
  1523. ref = ::Mdm::Ref.find_or_initialize_by_name(opts[:name])
  1524. if ref and ref.changed?
  1525. ref.save!
  1526. end
  1527. ret[:ref] = ref
  1528. }
  1529. end
  1530. def get_ref(name)
  1531. ::ActiveRecord::Base.connection_pool.with_connection {
  1532. ::Mdm::Ref.find_by_name(name)
  1533. }
  1534. end
  1535. #
  1536. # Populate the vuln_details table with additional
  1537. # information, matched by a specific criteria
  1538. #
  1539. def report_vuln_details(vuln, details)
  1540. ::ActiveRecord::Base.connection_pool.with_connection {
  1541. detail = ::Mdm::VulnDetail.where(( details.delete(:key) || {} ).merge(:vuln_id => vuln.id)).first
  1542. if detail
  1543. details.each_pair do |k,v|
  1544. detail[k] = v
  1545. end
  1546. detail.save! if detail.changed?
  1547. detail
  1548. else
  1549. detail = ::Mdm::VulnDetail.create(details.merge(:vuln_id => vuln.id))
  1550. end
  1551. }
  1552. end
  1553. #
  1554. # Update vuln_details records en-masse based on specific criteria
  1555. # Note that this *can* update data across workspaces
  1556. #
  1557. def update_vuln_details(details)
  1558. criteria = details.delete(:key) || {}
  1559. ::Mdm::VulnDetail.update(key, details)
  1560. end
  1561. #
  1562. # Populate the host_details table with additional
  1563. # information, matched by a specific criteria
  1564. #
  1565. def report_host_details(host, details)
  1566. ::ActiveRecord::Base.connection_pool.with_connection {
  1567. detail = ::Mdm::HostDetail.where(( details.delete(:key) || {} ).merge(:host_id => host.id)).first
  1568. if detail
  1569. details.each_pair do |k,v|
  1570. detail[k] = v
  1571. end
  1572. detail.save! if detail.changed?
  1573. detail
  1574. else
  1575. detail = ::Mdm::HostDetail.create(details.merge(:host_id => host.id))
  1576. end
  1577. }
  1578. end
  1579. # report_exploit() used to be used to track sessions and which modules
  1580. # opened them. That information is now available with the session table
  1581. # directly. TODO: kill this completely some day -- for now just warn if
  1582. # some other UI is actually using it.
  1583. def report_exploit(opts={})
  1584. wlog("Deprecated method call: report_exploit()\n" +
  1585. "report_exploit() options: #{opts.inspect}\n" +
  1586. "report_exploit() call stack:\n\t#{caller.join("\n\t")}"
  1587. )
  1588. end
  1589. #
  1590. # Deletes a host and associated data matching this address/comm
  1591. #
  1592. def del_host(wspace, address, comm='')
  1593. ::ActiveRecord::Base.connection_pool.with_connection {
  1594. address, scope = address.split('%', 2)
  1595. host = wspace.hosts.find_by_address_and_comm(address, comm)
  1596. host.destroy if host
  1597. }
  1598. end
  1599. #
  1600. # Deletes a port and associated vulns matching this port
  1601. #
  1602. def del_service(wspace, address, proto, port, comm='')
  1603. host = get_host(:workspace => wspace, :address => address)
  1604. return unless host
  1605. ::ActiveRecord::Base.connection_pool.with_connection {
  1606. host.services.where({:proto => proto, :port => port}).each { |s| s.destroy }
  1607. }
  1608. end
  1609. #
  1610. # Find a reference matching this name
  1611. #
  1612. def has_ref?(name)
  1613. ::ActiveRecord::Base.connection_pool.with_connection {
  1614. Mdm::Ref.find_by_name(name)
  1615. }
  1616. end
  1617. #
  1618. # Find a vulnerability matching this name
  1619. #
  1620. def has_vuln?(name)
  1621. ::ActiveRecord::Base.connection_pool.with_connection {
  1622. Mdm::Vuln.find_by_name(name)
  1623. }
  1624. end
  1625. #
  1626. # Look for an address across all comms
  1627. #
  1628. def has_host?(wspace,addr)
  1629. ::ActiveRecord::Base.connection_pool.with_connection {
  1630. address, scope = addr.split('%', 2)
  1631. wspace.hosts.find_by_address(addr)
  1632. }
  1633. end
  1634. def events(wspace=workspace)
  1635. ::ActiveRecord::Base.connection_pool.with_connection {
  1636. wspace.events.find :all, :order => 'created_at ASC'
  1637. }
  1638. end
  1639. def report_event(opts = {})
  1640. return if not active
  1641. ::ActiveRecord::Base.connection_pool.with_connection {
  1642. wspace = opts.delete(:workspace) || workspace
  1643. return if not wspace # Temp fix?
  1644. uname = opts.delete(:username)
  1645. if ! opts[:host].kind_of? ::Mdm::Host and opts[:host]
  1646. opts[:host] = report_host(:workspace => wspace, :host => opts[:host])
  1647. end
  1648. ::Mdm::Event.create(opts.merge(:workspace_id => wspace[:id], :username => uname))
  1649. }
  1650. end
  1651. #
  1652. # Loot collection
  1653. #
  1654. #
  1655. # This method iterates the loot table calling the supplied block with the
  1656. # instance of each entry.
  1657. #
  1658. def each_loot(wspace=workspace, &block)
  1659. ::ActiveRecord::Base.connection_pool.with_connection {
  1660. wspace.loots.each do |note|
  1661. block.call(note)
  1662. end
  1663. }
  1664. end
  1665. #
  1666. # Find or create a loot matching this type/data
  1667. #
  1668. def find_or_create_loot(opts)
  1669. report_loot(opts)
  1670. end
  1671. def report_loot(opts)
  1672. return if not active
  1673. ::ActiveRecord::Base.connection_pool.with_connection {
  1674. wspace = opts.delete(:workspace) || workspace
  1675. path = opts.delete(:path) || (raise RuntimeError, "A loot :path is required")
  1676. host = nil
  1677. addr = nil
  1678. # Report the host so it's there for the Proc to use below
  1679. if opts[:host]
  1680. if opts[:host].kind_of? ::Mdm::Host
  1681. host = opts[:host]
  1682. else
  1683. host = report_host({:workspace => wspace, :host => opts[:host]})
  1684. addr = normalize_host(opts[:host])
  1685. end
  1686. end
  1687. ret = {}
  1688. ltype = opts.delete(:type) || opts.delete(:ltype) || (raise RuntimeError, "A loot :type or :ltype is required")
  1689. ctype = opts.delete(:ctype) || opts.delete(:content_type) || 'text/plain'
  1690. name = opts.delete(:name)
  1691. info = opts.delete(:info)
  1692. data = opts[:data]
  1693. loot = wspace.loots.new
  1694. if host
  1695. loot.host_id = host[:id]
  1696. end
  1697. if opts[:service] and opts[:service].kind_of? ::Mdm::Service
  1698. loot.service_id = opts[:service][:id]
  1699. end
  1700. loot.path = path
  1701. loot.ltype = ltype
  1702. loot.content_type = ctype
  1703. loot.data = data
  1704. loot.name = name if name
  1705. loot.info = info if info
  1706. msf_import_timestamps(opts,loot)
  1707. loot.save!
  1708. if !opts[:created_at]
  1709. =begin
  1710. if host
  1711. host.updated_at = host.created_at
  1712. host.state = HostState::Alive
  1713. host.save!
  1714. end
  1715. =end
  1716. end
  1717. ret[:loot] = loot
  1718. }
  1719. end
  1720. #
  1721. # This methods returns a list of all loot in the database
  1722. #
  1723. def loots(wspace=workspace)
  1724. ::ActiveRecord::Base.connection_pool.with_connection {
  1725. wspace.loots
  1726. }
  1727. end
  1728. #
  1729. # Find or create a task matching this type/data
  1730. #
  1731. def find_or_create_task(opts)
  1732. report_task(opts)
  1733. end
  1734. def report_task(opts)
  1735. return if not active
  1736. ::ActiveRecord::Base.connection_pool.with_connection {
  1737. wspace = opts.delete(:workspace) || workspace
  1738. path = opts.delete(:path) || (raise RuntimeError, "A task :path is required")
  1739. ret = {}
  1740. user = opts.delete(:user)
  1741. desc = opts.delete(:desc)
  1742. error = opts.delete(:error)
  1743. info = opts.delete(:info)
  1744. mod = opts.delete(:mod)
  1745. options = opts.delete(:options)
  1746. prog = opts.delete(:prog)
  1747. result = opts.delete(:result)
  1748. completed_at = opts.delete(:completed_at)
  1749. task = wspace.tasks.new
  1750. task.created_by = user
  1751. task.description = desc
  1752. task.error = error if error
  1753. task.info = info
  1754. task.module = mod
  1755. task.options = options
  1756. task.path = path
  1757. task.progress = prog
  1758. task.result = result if result
  1759. msf_import_timestamps(opts,task)
  1760. # Having blank completed_ats, while accurate, will cause unstoppable tasks.
  1761. if completed_at.nil? || completed_at.empty?
  1762. task.completed_at = opts[:updated_at]
  1763. else
  1764. task.completed_at = completed_at
  1765. end
  1766. task.save!
  1767. ret[:task] = task
  1768. }
  1769. end
  1770. #
  1771. # This methods returns a list of all tasks in the database
  1772. #
  1773. def tasks(wspace=workspace)
  1774. ::ActiveRecord::Base.connection_pool.with_connection {
  1775. wspace.tasks
  1776. }
  1777. end
  1778. #
  1779. # Find or create a task matching this type/data
  1780. #
  1781. def find_or_create_report(opts)
  1782. report_report(opts)
  1783. end
  1784. def report_report(opts)
  1785. return if not active
  1786. ::ActiveRecord::Base.connection_pool.with_connection {
  1787. wspace = opts.delete(:workspace) || workspace
  1788. path = opts.delete(:path) || (raise RuntimeError, "A report :path is required")
  1789. ret = {}
  1790. user = opts.delete(:user)
  1791. options = opts.delete(:options)
  1792. rtype = opts.delete(:rtype)
  1793. report = wspace.reports.new
  1794. report.created_by = user
  1795. report.options = options
  1796. report.rtype = rtype
  1797. report.path = path
  1798. msf_import_timestamps(opts,report)
  1799. report.save!
  1800. ret[:task] = report
  1801. }
  1802. end
  1803. #
  1804. # This methods returns a list of all reports in the database
  1805. #
  1806. def reports(wspace=workspace)
  1807. ::ActiveRecord::Base.connection_pool.with_connection {
  1808. wspace.reports
  1809. }
  1810. end
  1811. #
  1812. # WMAP
  1813. # Support methods
  1814. #
  1815. #
  1816. # Report a Web Site to the database. WebSites must be tied to an existing Service
  1817. #
  1818. # opts MUST contain
  1819. # +:service+:: the service object this site should be associated with
  1820. # +:vhost+:: the virtual host name for this particular web site`
  1821. #
  1822. # If +:service+ is NOT specified, the following values are mandatory
  1823. # +:host+:: the ip address of the server hosting the web site
  1824. # +:port+:: the port number of the associated web site
  1825. # +:ssl+:: whether or not SSL is in use on this port
  1826. #
  1827. # These values will be used to create new host and service records
  1828. #
  1829. # opts can contain
  1830. # +:options+:: a hash of options for accessing this particular web site
  1831. # +:info+:: if present, report the service with this info
  1832. #
  1833. # Duplicate records for a given host, port, vhost combination will be overwritten
  1834. #
  1835. def report_web_site(opts)
  1836. return if not active
  1837. ::ActiveRecord::Base.connection_pool.with_connection { |conn|
  1838. wspace = opts.delete(:workspace) || workspace
  1839. vhost = opts.delete(:vhost)
  1840. addr = nil
  1841. port = nil
  1842. name = nil
  1843. serv = nil
  1844. info = nil
  1845. if opts[:service] and opts[:service].kind_of?(::Mdm::Service)
  1846. serv = opts[:service]
  1847. else
  1848. addr = opts[:host]
  1849. port = opts[:port]
  1850. name = opts[:ssl] ? 'https' : 'http'
  1851. info = opts[:info]
  1852. if not (addr and port)
  1853. raise ArgumentError, "report_web_site requires service OR host/port/ssl"
  1854. end
  1855. # Force addr to be the address and not hostname
  1856. addr = Rex::Socket.getaddress(addr, true)
  1857. end
  1858. ret = {}
  1859. host = serv ? serv.host : find_or_create_host(
  1860. :workspace => wspace,
  1861. :host => addr,
  1862. :state => Msf::HostState::Alive
  1863. )
  1864. if host.name.to_s.empty?
  1865. host.name = vhost
  1866. host.save!
  1867. end
  1868. serv = serv ? serv : find_or_create_service(
  1869. :workspace => wspace,
  1870. :host => host,
  1871. :port => port,
  1872. :proto => 'tcp',
  1873. :state => 'open'
  1874. )
  1875. # Change the service name if it is blank or it has
  1876. # been explicitly specified.
  1877. if opts.keys.include?(:ssl) or serv.name.to_s.empty?
  1878. name = opts[:ssl] ? 'https' : 'http'
  1879. serv.name = name
  1880. end
  1881. # Add the info if it's there.
  1882. unless info.to_s.empty?
  1883. serv.info = info
  1884. end
  1885. serv.save! if serv.changed?
  1886. =begin
  1887. host.updated_at = host.created_at
  1888. host.state = HostState::Alive
  1889. host.save!
  1890. =end
  1891. vhost ||= host.address
  1892. site = ::Mdm::WebSite.find_or_initialize_by_vhost_and_service_id(vhost, serv[:id])
  1893. site.options = opts[:options] if opts[:options]
  1894. # XXX:
  1895. msf_import_timestamps(opts, site)
  1896. site.save!
  1897. ret[:web_site] = site
  1898. }
  1899. end
  1900. #
  1901. # Report a Web Page to the database. WebPage must be tied to an existing Web Site
  1902. #
  1903. # opts MUST contain
  1904. # +:web_site+:: the web site object that this page should be associated with
  1905. # +:path+:: the virtual host name for this particular web site
  1906. # +:code+:: the http status code from requesting this page
  1907. # +:headers+:: this is a HASH of headers (lowercase name as key) of ARRAYs of values
  1908. # +:body+:: the document body of the server response
  1909. # +:query+:: the query string after the path
  1910. #
  1911. # If web_site is NOT specified, the following values are mandatory
  1912. # +:host+:: the ip address of the server hosting the web site
  1913. # +:port+:: the port number of the associated web site
  1914. # +:vhost+:: the virtual host for this particular web site
  1915. # +:ssl+:: whether or not SSL is in use on this port
  1916. #
  1917. # These values will be used to create new host, service, and web_site records
  1918. #
  1919. # opts can contain
  1920. # +:cookie+:: the Set-Cookie headers, merged into a string
  1921. # +:auth+:: the Authorization headers, merged into a string
  1922. # +:ctype+:: the Content-Type headers, merged into a string
  1923. # +:mtime+:: the timestamp returned from the server of the last modification time
  1924. # +:location+:: the URL that a redirect points to
  1925. #
  1926. # Duplicate records for a given web_site, path, and query combination will be overwritten
  1927. #
  1928. def report_web_page(opts)
  1929. return if not active
  1930. ::ActiveRecord::Base.connection_pool.with_connection {
  1931. wspace = opts.delete(:workspace) || workspace
  1932. path = opts[:path]
  1933. code = opts[:code].to_i
  1934. body = opts[:body].to_s
  1935. query = opts[:query].to_s
  1936. headers = opts[:headers]
  1937. site = nil
  1938. if not (path and code and body and headers)
  1939. raise ArgumentError, "report_web_page requires the path, query, code, body, and headers parameters"
  1940. end
  1941. if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite)
  1942. site = opts.delete(:web_site)
  1943. else
  1944. site = report_web_site(
  1945. :workspace => wspace,
  1946. :host => opts[:host], :port => opts[:port],
  1947. :vhost => opts[:host], :ssl => opts[:ssl]
  1948. )
  1949. if not site
  1950. raise ArgumentError, "report_web_page was unable to create the associated web site"
  1951. end
  1952. end
  1953. ret = {}
  1954. page = ::Mdm::WebPage.find_or_initialize_by_web_site_id_and_path_and_query(site[:id], path, query)
  1955. page.code = code
  1956. page.body = body
  1957. page.headers = headers
  1958. page.cookie = opts[:cookie] if opts[:cookie]
  1959. page.auth = opts[:auth] if opts[:auth]
  1960. page.mtime = opts[:mtime] if opts[:mtime]
  1961. page.ctype = opts[:ctype] if opts[:ctype]
  1962. page.location = opts[:location] if opts[:location]
  1963. msf_import_timestamps(opts, page)
  1964. page.save!
  1965. ret[:web_page] = page
  1966. }
  1967. end
  1968. #
  1969. # Report a Web Form to the database. WebForm must be tied to an existing Web Site
  1970. #
  1971. # opts MUST contain
  1972. # +:web_site+:: the web site object that this page should be associated with
  1973. # +:path+:: the virtual host name for this particular web site
  1974. # +:query+:: the query string that is appended to the path (not valid for GET)
  1975. # +:method+:: the form method, one of GET, POST, or PATH
  1976. # +:params+:: an ARRAY of all parameters and values specified in the form
  1977. #
  1978. # If web_site is NOT specified, the following values are mandatory
  1979. # +:host+:: the ip address of the server hosting the web site
  1980. # +:port+:: the port number of the associated web site
  1981. # +:vhost+:: the virtual host for this particular web site
  1982. # +:ssl+:: whether or not SSL is in use on this port
  1983. #
  1984. # Duplicate records for a given web_site, path, method, and params combination will be overwritten
  1985. #
  1986. def report_web_form(opts)
  1987. return if not active
  1988. ::ActiveRecord::Base.connection_pool.with_connection {
  1989. wspace = opts.delete(:workspace) || workspace
  1990. path = opts[:path]
  1991. meth = opts[:method].to_s.upcase
  1992. para = opts[:params]
  1993. quer = opts[:query].to_s
  1994. site = nil
  1995. if not (path and meth)
  1996. raise ArgumentError, "report_web_form requires the path and method parameters"
  1997. end
  1998. if not %W{GET POST PATH}.include?(meth)
  1999. raise ArgumentError, "report_web_form requires the method to be one of GET, POST, PATH"
  2000. end
  2001. if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite)
  2002. site = opts.delete(:web_site)
  2003. else
  2004. site = report_web_site(
  2005. :workspace => wspace,
  2006. :host => opts[:host], :port => opts[:port],
  2007. :vhost => opts[:host], :ssl => opts[:ssl]
  2008. )
  2009. if not site
  2010. raise ArgumentError, "report_web_form was unable to create the associated web site"
  2011. end
  2012. end
  2013. ret = {}
  2014. # Since one of our serialized fields is used as a unique parameter, we must do the final
  2015. # comparisons through ruby and not SQL.
  2016. form = nil
  2017. ::Mdm::WebForm.find_all_by_web_site_id_and_path_and_method_and_query(site[:id], path, meth, quer).each do |xform|
  2018. if xform.params == para
  2019. form = xform
  2020. break
  2021. end
  2022. end
  2023. if not form
  2024. form = ::Mdm::WebForm.new
  2025. form.web_site_id = site[:id]
  2026. form.path = path
  2027. form.method = meth
  2028. form.params = para
  2029. form.query = quer
  2030. end
  2031. msf_import_timestamps(opts, form)
  2032. form.save!
  2033. ret[:web_form] = form
  2034. }
  2035. end
  2036. #
  2037. # Report a Web Vuln to the database. WebVuln must be tied to an existing Web Site
  2038. #
  2039. # opts MUST contain
  2040. # +:web_site+:: the web site object that this page should be associated with
  2041. # +:path+:: the virtual host name for this particular web site
  2042. # +:query+:: the query string appended to the path (not valid for GET method flaws)
  2043. # +:method+:: the form method, one of GET, POST, or PATH
  2044. # +:params+:: an ARRAY of all parameters and values specified in the form
  2045. # +:pname+:: the specific field where the vulnerability occurs
  2046. # +:proof+:: the string showing proof of the vulnerability
  2047. # +:risk+:: an INTEGER value from 0 to 5 indicating the risk (5 is highest)
  2048. # +:name+:: the string indicating the type of vulnerability
  2049. #
  2050. # If web_site is NOT specified, the following values are mandatory
  2051. # +:host+:: the ip address of the server hosting the web site
  2052. # +:port+:: the port number of the associated web site
  2053. # +:vhost+:: the virtual host for this particular web site
  2054. # +:ssl+:: whether or not SSL is in use on this port
  2055. #
  2056. #
  2057. # Duplicate records for a given web_site, path, method, pname, and name
  2058. # combination will be overwritten
  2059. #
  2060. def report_web_vuln(opts)
  2061. return if not active
  2062. ::ActiveRecord::Base.connection_pool.with_connection {
  2063. wspace = opts.delete(:workspace) || workspace
  2064. path = opts[:path]
  2065. meth = opts[:method]
  2066. para = opts[:params] || []
  2067. quer = opts[:query].to_s
  2068. pname = opts[:pname]
  2069. proof = opts[:proof]
  2070. risk = opts[:risk].to_i
  2071. name = opts[:name].to_s.strip
  2072. blame = opts[:blame].to_s.strip
  2073. desc = opts[:description].to_s.strip
  2074. conf = opts[:confidence].to_i
  2075. cat = opts[:category].to_s.strip
  2076. payload = opts[:payload].to_s
  2077. owner = opts[:owner] ? opts[:owner].shortname : nil
  2078. site = nil
  2079. if not (path and meth and proof and pname)
  2080. raise ArgumentError, "report_web_vuln requires the path, method, proof, risk, name, params, and pname parameters. Received #{opts.inspect}"
  2081. end
  2082. if not %W{GET POST PATH}.include?(meth)
  2083. raise ArgumentError, "report_web_vuln requires the method to be one of GET, POST, PATH. Received '#{meth}'"
  2084. end
  2085. if risk < 0 or risk > 5
  2086. raise ArgumentError, "report_web_vuln requires the risk to be between 0 and 5 (inclusive). Received '#{risk}'"
  2087. end
  2088. if conf < 0 or conf > 100
  2089. raise ArgumentError, "report_web_vuln requires the confidence to be between 1 and 100 (inclusive). Received '#{conf}'"
  2090. end
  2091. if cat.empty?
  2092. raise ArgumentError, "report_web_vuln requires the category to be a valid string"
  2093. end
  2094. if name.empty?
  2095. raise ArgumentError, "report_web_vuln requires the name to be a valid string"
  2096. end
  2097. if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite)
  2098. site = opts.delete(:web_site)
  2099. else
  2100. site = report_web_site(
  2101. :workspace => wspace,
  2102. :host => opts[:host], :port => opts[:port],
  2103. :vhost => opts[:host], :ssl => opts[:ssl]
  2104. )
  2105. if not site
  2106. raise ArgumentError, "report_web_form was unable to create the associated web site"
  2107. end
  2108. end
  2109. ret = {}
  2110. meth = meth.to_s.upcase
  2111. vuln = ::Mdm::WebVuln.find_or_initialize_by_web_site_id_and_path_and_method_and_pname_and_name_and_category_and_query(site[:id], path, meth, pname, name, cat, quer)
  2112. vuln.name = name
  2113. vuln.risk = risk
  2114. vuln.params = para
  2115. vuln.proof = proof.to_s
  2116. vuln.category = cat
  2117. vuln.blame = blame
  2118. vuln.description = desc
  2119. vuln.confidence = conf
  2120. vuln.payload = payload
  2121. vuln.owner = owner
  2122. msf_import_timestamps(opts, vuln)
  2123. vuln.save!
  2124. ret[:web_vuln] = vuln
  2125. }
  2126. end
  2127. #
  2128. # WMAP
  2129. # Selected host
  2130. #
  2131. def selected_host
  2132. ::ActiveRecord::Base.connection_pool.with_connection {
  2133. selhost = ::Mdm::WmapTarget.where("selected != 0").first()
  2134. if selhost
  2135. return selhost.host
  2136. else
  2137. return
  2138. end
  2139. }
  2140. end
  2141. #
  2142. # WMAP
  2143. # Selected target
  2144. #
  2145. def selected_wmap_target
  2146. ::ActiveRecord::Base.connection_pool.with_connection {
  2147. ::Mdm::WmapTarget.find.where("selected != 0")
  2148. }
  2149. end
  2150. #
  2151. # WMAP
  2152. # Selected port
  2153. #
  2154. def selected_port
  2155. selected_wmap_target.port
  2156. end
  2157. #
  2158. # WMAP
  2159. # Selected ssl
  2160. #
  2161. def selected_ssl
  2162. selected_wmap_target.ssl
  2163. end
  2164. #
  2165. # WMAP
  2166. # Selected id
  2167. #
  2168. def selected_id
  2169. selected_wmap_target.object_id
  2170. end
  2171. #
  2172. # WMAP
  2173. # This method iterates the requests table identifiying possible targets
  2174. # This method wiil be remove on second phase of db merging.
  2175. #
  2176. def each_distinct_target(&block)
  2177. request_distinct_targets.each do |target|
  2178. block.call(target)
  2179. end
  2180. end
  2181. #
  2182. # WMAP
  2183. # This method returns a list of all possible targets available in requests
  2184. # This method wiil be remove on second phase of db merging.
  2185. #
  2186. def request_distinct_targets
  2187. ::ActiveRecord::Base.connection_pool.with_connection {
  2188. ::Mdm::WmapRequest.select('DISTINCT host,address,port,ssl')
  2189. }
  2190. end
  2191. #
  2192. # WMAP
  2193. # This method iterates the requests table returning a list of all requests of a specific target
  2194. #
  2195. def each_request_target_with_path(&block)
  2196. target_requests('AND wmap_requests.path IS NOT NULL').each do |req|
  2197. block.call(req)
  2198. end
  2199. end
  2200. #
  2201. # WMAP
  2202. # This method iterates the requests table returning a list of all requests of a specific target
  2203. #
  2204. def each_request_target_with_query(&block)
  2205. target_requests('AND wmap_requests.query IS NOT NULL').each do |req|
  2206. block.call(req)
  2207. end
  2208. end
  2209. #
  2210. # WMAP
  2211. # This method iterates the requests table returning a list of all requests of a specific target
  2212. #
  2213. def each_request_target_with_body(&block)
  2214. target_requests('AND wmap_requests.body IS NOT NULL').each do |req|
  2215. block.call(req)
  2216. end
  2217. end
  2218. #
  2219. # WMAP
  2220. # This method iterates the requests table returning a list of all requests of a specific target
  2221. #
  2222. def each_request_target_with_headers(&block)
  2223. target_requests('AND wmap_requests.headers IS NOT NULL').each do |req|
  2224. block.call(req)
  2225. end
  2226. end
  2227. #
  2228. # WMAP
  2229. # This method iterates the requests table returning a list of all requests of a specific target
  2230. #
  2231. def each_request_target(&block)
  2232. target_requests('').each do |req|
  2233. block.call(req)
  2234. end
  2235. end
  2236. #
  2237. # WMAP
  2238. # This method returns a list of all requests from target
  2239. #
  2240. def target_requests(extra_condition)
  2241. ::ActiveRecord::Base.connection_pool.with_connection {
  2242. ::Mdm::WmapRequest.where("wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}",selected_host,selected_port)
  2243. }
  2244. end
  2245. #
  2246. # WMAP
  2247. # This method iterates the requests table calling the supplied block with the
  2248. # request instance of each entry.
  2249. #
  2250. def each_request(&block)
  2251. requests.each do |request|
  2252. block.call(request)
  2253. end
  2254. end
  2255. #
  2256. # WMAP
  2257. # This method allows to query directly the requests table. To be used mainly by modules
  2258. #
  2259. def request_sql(host,port,extra_condition)
  2260. ::ActiveRecord::Base.connection_pool.with_connection {
  2261. ::Mdm::WmapRequest.where("wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}", host , port)
  2262. }
  2263. end
  2264. #
  2265. # WMAP
  2266. # This methods returns a list of all targets in the database
  2267. #
  2268. def requests
  2269. ::ActiveRecord::Base.connection_pool.with_connection {
  2270. ::Mdm::WmapRequest.find(:all)
  2271. }
  2272. end
  2273. #
  2274. # WMAP
  2275. # This method iterates the targets table calling the supplied block with the
  2276. # target instance of each entry.
  2277. #
  2278. def each_target(&block)
  2279. targets.each do |target|
  2280. block.call(target)
  2281. end
  2282. end
  2283. #
  2284. # WMAP
  2285. # This methods returns a list of all targets in the database
  2286. #
  2287. def targets
  2288. ::ActiveRecord::Base.connection_pool.with_connection {
  2289. ::Mdm::WmapTarget.find(:all)
  2290. }
  2291. end
  2292. #
  2293. # WMAP
  2294. # This methods deletes all targets from targets table in the database
  2295. #
  2296. def delete_all_targets
  2297. ::ActiveRecord::Base.connection_pool.with_connection {
  2298. ::Mdm::WmapTarget.delete_all
  2299. }
  2300. end
  2301. #
  2302. # WMAP
  2303. # Find a target matching this id
  2304. #
  2305. def get_target(id)
  2306. ::ActiveRecord::Base.connection_pool.with_connection {
  2307. target = ::Mdm::WmapTarget.where("id = ?", id).first()
  2308. return target
  2309. }
  2310. end
  2311. #
  2312. # WMAP
  2313. # Create a target
  2314. #
  2315. def create_target(host,port,ssl,sel)
  2316. ::ActiveRecord::Base.connection_pool.with_connection {
  2317. tar = ::Mdm::WmapTarget.create(
  2318. :host => host,
  2319. :address => host,
  2320. :port => port,
  2321. :ssl => ssl,
  2322. :selected => sel
  2323. )
  2324. #framework.events.on_db_target(rec)
  2325. }
  2326. end
  2327. #
  2328. # WMAP
  2329. # Create a request (by hand)
  2330. #
  2331. def create_request(host,port,ssl,meth,path,headers,query,body,respcode,resphead,response)
  2332. ::ActiveRecord::Base.connection_pool.with_connection {
  2333. req = ::Mdm::WmapRequest.create(
  2334. :host => host,
  2335. :address => host,
  2336. :port => port,
  2337. :ssl => ssl,
  2338. :meth => meth,
  2339. :path => path,
  2340. :headers => headers,
  2341. :query => query,
  2342. :body => body,
  2343. :respcode => respcode,
  2344. :resphead => resphead,
  2345. :response => response
  2346. )
  2347. #framework.events.on_db_request(rec)
  2348. }
  2349. end
  2350. #
  2351. # WMAP
  2352. # Quick way to query the database (used by wmap_sql)
  2353. #
  2354. def sql_query(sqlquery)
  2355. ::ActiveRecord::Base.connection_pool.with_connection {
  2356. ActiveRecord::Base.connection.select_all(sqlquery)
  2357. }
  2358. end
  2359. # Returns a REXML::Document from the given data.
  2360. def rexmlify(data)
  2361. if data.kind_of?(REXML::Document)
  2362. return data
  2363. else
  2364. # Make an attempt to recover from a REXML import fail, since
  2365. # it's better than dying outright.
  2366. begin
  2367. return REXML::Document.new(data)
  2368. rescue REXML::ParseException => e
  2369. dlog("REXML error: Badly formatted XML, attempting to recover. Error was: #{e.inspect}")
  2370. return REXML::Document.new(data.gsub(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xff])/){ |x| "\\x%.2x" % x.unpack("C*")[0] })
  2371. end
  2372. end
  2373. end
  2374. # Handles timestamps from Metasploit Express/Pro imports.
  2375. def msf_import_timestamps(opts,obj)
  2376. obj.created_at = opts["created_at"] if opts["created_at"]
  2377. obj.created_at = opts[:created_at] if opts[:created_at]
  2378. obj.updated_at = opts["updated_at"] ? opts["updated_at"] : obj.created_at
  2379. obj.updated_at = opts[:updated_at] ? opts[:updated_at] : obj.created_at
  2380. return obj
  2381. end
  2382. ##
  2383. #
  2384. # Import methods
  2385. #
  2386. ##
  2387. #
  2388. # Generic importer that automatically determines the file type being
  2389. # imported. Since this looks for vendor-specific strings in the given
  2390. # file, there shouldn't be any false detections, but no guarantees.
  2391. #
  2392. def import_file(args={}, &block)
  2393. filename = args[:filename] || args['filename']
  2394. wspace = args[:wspace] || args['wspace'] || workspace
  2395. @import_filedata = {}
  2396. @import_filedata[:filename] = filename
  2397. data = ""
  2398. ::File.open(filename, 'rb') do |f|
  2399. data = f.read(4)
  2400. end
  2401. case data[0,4]
  2402. when "PK\x03\x04"
  2403. data = Zip::ZipFile.open(filename)
  2404. when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4"
  2405. data = PacketFu::PcapFile.new(:filename => filename)
  2406. else
  2407. ::File.open(filename, 'rb') do |f|
  2408. sz = f.stat.size
  2409. data = f.read(sz)
  2410. end
  2411. end
  2412. if block
  2413. import(args.merge(:data => data)) { |type,data| yield type,data }
  2414. else
  2415. import(args.merge(:data => data))
  2416. end
  2417. end
  2418. # A dispatcher method that figures out the data's file type,
  2419. # and sends it off to the appropriate importer. Note that
  2420. # import_file_detect will raise an error if the filetype
  2421. # is unknown.
  2422. def import(args={}, &block)
  2423. data = args[:data] || args['data']
  2424. wspace = args[:wspace] || args['wspace'] || workspace
  2425. ftype = import_filetype_detect(data)
  2426. yield(:filetype, @import_filedata[:type]) if block
  2427. self.send "import_#{ftype}".to_sym, args, &block
  2428. end
  2429. # Returns one of: :nexpose_simplexml :nexpose_rawxml :nmap_xml :openvas_xml
  2430. # :nessus_xml :nessus_xml_v2 :qualys_scan_xml, :qualys_asset_xml, :msf_xml :nessus_nbe :amap_mlog
  2431. # :amap_log :ip_list, :msf_zip, :libpcap, :foundstone_xml, :acunetix_xml, :appscan_xml
  2432. # :burp_session, :ip360_xml_v3, :ip360_aspl_xml, :nikto_xml
  2433. # If there is no match, an error is raised instead.
  2434. def import_filetype_detect(data)
  2435. if data and data.kind_of? Zip::ZipFile
  2436. raise DBImportError.new("The zip file provided is empty.") if data.entries.empty?
  2437. @import_filedata ||= {}
  2438. @import_filedata[:zip_filename] = File.split(data.to_s).last
  2439. @import_filedata[:zip_basename] = @import_filedata[:zip_filename].gsub(/\.zip$/,"")
  2440. @import_filedata[:zip_entry_names] = data.entries.map {|x| x.name}
  2441. begin
  2442. @import_filedata[:zip_xml] = @import_filedata[:zip_entry_names].grep(/^(.*)_[0-9]+\.xml$/).first || raise
  2443. @import_filedata[:zip_wspace] = @import_filedata[:zip_xml].to_s.match(/^(.*)_[0-9]+\.xml$/)[1]
  2444. @import_filedata[:type] = "Metasploit ZIP Report"
  2445. return :msf_zip
  2446. rescue ::Interrupt
  2447. raise $!
  2448. rescue ::Exception
  2449. raise DBImportError.new("The zip file provided is not a Metasploit ZIP report")
  2450. end
  2451. end
  2452. if data and data.kind_of? PacketFu::PcapFile
  2453. # Don't check for emptiness here because unlike other formats, we
  2454. # haven't read any actual data in yet, only magic bytes to discover
  2455. # that this is indeed a pcap file.
  2456. #raise DBImportError.new("The pcap file provided is empty.") if data.body.empty?
  2457. @import_filedata ||= {}
  2458. @import_filedata[:type] = "Libpcap Packet Capture"
  2459. return :libpcap
  2460. end
  2461. # This is a text string, lets make sure its treated as binary
  2462. data = data.unpack("C*").pack("C*")
  2463. if data and data.to_s.strip.length == 0
  2464. raise DBImportError.new("The data provided to the import function was empty")
  2465. end
  2466. # Parse the first line or 4k of data from the file
  2467. di = data.index("\n") || 4096
  2468. firstline = data[0, di]
  2469. @import_filedata ||= {}
  2470. if (firstline.index("<NeXposeSimpleXML"))
  2471. @import_filedata[:type] = "NeXpose Simple XML"
  2472. return :nexpose_simplexml
  2473. elsif (firstline.index("<FusionVM"))
  2474. @import_filedata[:type] = "FusionVM XML"
  2475. return :fusionvm_xml
  2476. elsif (firstline.index("<NexposeReport"))
  2477. @import_filedata[:type] = "NeXpose XML Report"
  2478. return :nexpose_rawxml
  2479. elsif (firstline.index("Name,Manufacturer,Device Type,Model,IP Address,Serial Number,Location,Operating System"))
  2480. @import_filedata[:type] = "Spiceworks CSV Export"
  2481. return :spiceworks_csv
  2482. elsif (firstline.index("<scanJob>"))
  2483. @import_filedata[:type] = "Retina XML"
  2484. return :retina_xml
  2485. elsif (firstline.index("<get_reports_response status=\"200\" status_text=\"OK\">"))
  2486. @import_filedata[:type] = "OpenVAS XML"
  2487. return :openvas_new_xml
  2488. elsif (firstline.index("<report id=\""))
  2489. @import_filedata[:type] = "OpenVAS XML"
  2490. return :openvas_new_xml
  2491. elsif (firstline.index("<NessusClientData>"))
  2492. @import_filedata[:type] = "Nessus XML (v1)"
  2493. return :nessus_xml
  2494. elsif (firstline.index("<SecScan ID="))
  2495. @import_filedata[:type] = "Microsoft Baseline Security Analyzer"
  2496. return :mbsa_xml
  2497. elsif (data[0,1024] =~ /<!ATTLIST\s+items\s+burpVersion/)
  2498. @import_filedata[:type] = "Burp Session XML"
  2499. return :burp_session_xml
  2500. elsif (firstline.index("<?xml"))
  2501. # it's xml, check for root tags we can handle
  2502. line_count = 0
  2503. data.each_line { |line|
  2504. line =~ /<([a-zA-Z0-9\-\_]+)[ >]/
  2505. case $1
  2506. when "niktoscan"
  2507. @import_filedata[:type] = "Nikto XML"
  2508. return :nikto_xml
  2509. when "nmaprun"
  2510. @import_filedata[:type] = "Nmap XML"
  2511. return :nmap_xml
  2512. when "openvas-report"
  2513. @import_filedata[:type] = "OpenVAS Report"
  2514. return :openvas_xml
  2515. when "NessusClientData"
  2516. @import_filedata[:type] = "Nessus XML (v1)"
  2517. return :nessus_xml
  2518. when "NessusClientData_v2"
  2519. @import_filedata[:type] = "Nessus XML (v2)"
  2520. return :nessus_xml_v2
  2521. when "SCAN"
  2522. @import_filedata[:type] = "Qualys Scan XML"
  2523. return :qualys_scan_xml
  2524. when "report"
  2525. @import_filedata[:type] = "Wapiti XML"
  2526. return :wapiti_xml
  2527. when "ASSET_DATA_REPORT"
  2528. @import_filedata[:type] = "Qualys Asset XML"
  2529. return :qualys_asset_xml
  2530. when /MetasploitExpressV[1234]/
  2531. @import_filedata[:type] = "Metasploit XML"
  2532. return :msf_xml
  2533. when /MetasploitV4/
  2534. @import_filedata[:type] = "Metasploit XML"
  2535. return :msf_xml
  2536. when /netsparker/
  2537. @import_filedata[:type] = "NetSparker XML"
  2538. return :netsparker_xml
  2539. when /audits?/ # <audit> and <audits> are both valid for nCircle. wtfmate.
  2540. @import_filedata[:type] = "IP360 XML v3"
  2541. return :ip360_xml_v3
  2542. when /ontology/
  2543. @import_filedata[:type] = "IP360 ASPL"
  2544. return :ip360_aspl_xml
  2545. when /ReportInfo/
  2546. @import_filedata[:type] = "Foundstone"
  2547. return :foundstone_xml
  2548. when /ScanGroup/
  2549. @import_filedata[:type] = "Acunetix"
  2550. return :acunetix_xml
  2551. when /AppScanInfo/ # Actually the second line
  2552. @import_filedata[:type] = "Appscan"
  2553. return :appscan_xml
  2554. when "entities"
  2555. if line =~ /creator.*\x43\x4f\x52\x45\x20\x49\x4d\x50\x41\x43\x54/i
  2556. @import_filedata[:type] = "CI"
  2557. return :ci_xml
  2558. end
  2559. else
  2560. # Give up if we haven't hit the root tag in the first few lines
  2561. break if line_count > 10
  2562. end
  2563. line_count += 1
  2564. }
  2565. elsif (firstline.index("timestamps|||scan_start"))
  2566. @import_filedata[:type] = "Nessus NBE Report"
  2567. # then it's a nessus nbe
  2568. return :nessus_nbe
  2569. elsif (firstline.index("# amap v"))
  2570. # then it's an amap mlog
  2571. @import_filedata[:type] = "Amap Log -m"
  2572. return :amap_mlog
  2573. elsif (firstline.index("amap v"))
  2574. # then it's an amap log
  2575. @import_filedata[:type] = "Amap Log"
  2576. return :amap_log
  2577. elsif ipv46_validator(firstline)
  2578. # then its an IP list
  2579. @import_filedata[:type] = "IP Address List"
  2580. return :ip_list
  2581. elsif (data[0,1024].index("<netsparker"))
  2582. @import_filedata[:type] = "NetSparker XML"
  2583. return :netsparker_xml
  2584. elsif (firstline.index("# Metasploit PWDump Export"))
  2585. # then it's a Metasploit PWDump export
  2586. @import_filedata[:type] = "msf_pwdump"
  2587. return :msf_pwdump
  2588. end
  2589. raise DBImportError.new("Could not automatically determine file type")
  2590. end
  2591. # Boils down the validate_import_file to a boolean
  2592. def validate_import_file(data)
  2593. begin
  2594. import_filetype_detect(data)
  2595. rescue DBImportError
  2596. return false
  2597. end
  2598. return true
  2599. end
  2600. #
  2601. # Imports Nikto scan data from -Format xml as notes.
  2602. #
  2603. def import_nikto_xml(args={}, &block)
  2604. data = args[:data]
  2605. wspace = args[:wspace] || workspace
  2606. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2607. doc = rexmlify(data)
  2608. doc.elements.each do |f|
  2609. f.elements.each('scandetails') do |host|
  2610. # Get host information
  2611. addr = host.attributes['targetip']
  2612. next if not addr
  2613. if bl.include? addr
  2614. next
  2615. else
  2616. yield(:address,addr) if block
  2617. end
  2618. # Get service information
  2619. port = host.attributes['targetport']
  2620. next if port.to_i == 0
  2621. uri = URI.parse(host.attributes['sitename']) rescue nil
  2622. next unless uri and uri.scheme
  2623. # Collect and report scan descriptions.
  2624. host.elements.each do |item|
  2625. if item.elements['description']
  2626. desc_text = item.elements['description'].text
  2627. next if desc_text.nil? or desc_text.empty?
  2628. desc_data = {
  2629. :workspace => wspace,
  2630. :host => addr,
  2631. :type => "service.nikto.scan.description",
  2632. :data => desc_text,
  2633. :proto => "tcp",
  2634. :port => port.to_i,
  2635. :sname => uri.scheme,
  2636. :update => :unique_data
  2637. }
  2638. # Always report it as a note.
  2639. report_note(desc_data)
  2640. # Sometimes report it as a vuln, too.
  2641. # XXX: There's a Vuln.info field but nothing reads from it? See Bug #5837
  2642. if item.attributes['osvdbid'].to_i != 0
  2643. desc_data[:refs] = ["OSVDB-#{item.attributes['osvdbid']}"]
  2644. desc_data[:name] = "NIKTO-#{item.attributes['id']}"
  2645. desc_data.delete(:data)
  2646. desc_data.delete(:type)
  2647. desc_data.delete(:update)
  2648. report_vuln(desc_data)
  2649. end
  2650. end
  2651. end
  2652. end
  2653. end
  2654. end
  2655. def import_wapiti_xml_file(args={})
  2656. filename = args[:filename]
  2657. wspace = args[:wspace] || workspace
  2658. data = ""
  2659. ::File.open(filename, 'rb') do |f|
  2660. data = f.read(f.stat.size)
  2661. end
  2662. import_wapiti_xml(args.merge(:data => data))
  2663. end
  2664. def import_wapiti_xml(args={}, &block)
  2665. if block
  2666. doc = Rex::Parser::WapitiDocument.new(args,framework.db) {|type, data| yield type,data }
  2667. else
  2668. doc = Rex::Parser::WapitiDocument.new(args,self)
  2669. end
  2670. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  2671. parser.parse(args[:data])
  2672. end
  2673. def import_openvas_new_xml_file(args={})
  2674. filename = args[:filename]
  2675. wspace = args[:wspace] || workspace
  2676. data = ""
  2677. ::File.open(filename, 'rb') do |f|
  2678. data = f.read(f.stat.size)
  2679. end
  2680. import_wapiti_xml(args.merge(:data => data))
  2681. end
  2682. def import_openvas_new_xml(args={}, &block)
  2683. if block
  2684. doc = Rex::Parser::OpenVASDocument.new(args,framework.db) {|type, data| yield type,data }
  2685. else
  2686. doc = Rex::Parser::OpenVASDocument.new(args,self)
  2687. end
  2688. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  2689. parser.parse(args[:data])
  2690. end
  2691. def import_libpcap_file(args={})
  2692. filename = args[:filename]
  2693. wspace = args[:wspace] || workspace
  2694. data = PacketFu::PcapFile.new(:filename => filename)
  2695. import_libpcap(args.merge(:data => data))
  2696. end
  2697. # The libpcap file format is handled by PacketFu for data
  2698. # extraction. TODO: Make this its own mixin, and possibly
  2699. # extend PacketFu to do better stream analysis on the fly.
  2700. def import_libpcap(args={}, &block)
  2701. data = args[:data]
  2702. wspace = args[:wspace] || workspace
  2703. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2704. # seen_hosts is only used for determining when to yield an address. Once we get
  2705. # some packet analysis going, the values will have all sorts of info. The plan
  2706. # is to ru through all the packets as a first pass and report host and service,
  2707. # then, once we have everything parsed, we can reconstruct sessions and ngrep
  2708. # out things like authentication sequences, examine ttl's and window sizes, all
  2709. # kinds of crazy awesome stuff like that.
  2710. seen_hosts = {}
  2711. decoded_packets = 0
  2712. last_count = 0
  2713. data.read_packet_bytes do |p|
  2714. if (decoded_packets >= last_count + 1000) and block
  2715. yield(:pcap_count, decoded_packets)
  2716. last_count = decoded_packets
  2717. end
  2718. decoded_packets += 1
  2719. pkt = PacketFu::Packet.parse(p) rescue next # Just silently skip bad packets
  2720. next unless pkt.is_ip? # Skip anything that's not IP. Technically, not Ethernet::Ip
  2721. next if pkt.is_tcp? && (pkt.tcp_src == 0 || pkt.tcp_dst == 0) # Skip port 0
  2722. next if pkt.is_udp? && (pkt.udp_src == 0 || pkt.udp_dst == 0) # Skip port 0
  2723. saddr = pkt.ip_saddr
  2724. daddr = pkt.ip_daddr
  2725. # Handle blacklists and obviously useless IP addresses, and report the host.
  2726. next if (bl | [saddr,daddr]).size == bl.size # Both hosts are blacklisted, skip everything.
  2727. unless( bl.include?(saddr) || rfc3330_reserved(saddr))
  2728. yield(:address,saddr) if block and !seen_hosts.keys.include?(saddr)
  2729. report_host(:workspace => wspace, :host => saddr, :state => Msf::HostState::Alive) unless seen_hosts[saddr]
  2730. seen_hosts[saddr] ||= []
  2731. end
  2732. unless( bl.include?(daddr) || rfc3330_reserved(daddr))
  2733. yield(:address,daddr) if block and !seen_hosts.keys.include?(daddr)
  2734. report_host(:workspace => wspace, :host => daddr, :state => Msf::HostState::Alive) unless seen_hosts[daddr]
  2735. seen_hosts[daddr] ||= []
  2736. end
  2737. if pkt.is_tcp? # First pass on TCP packets
  2738. if (pkt.tcp_flags.syn == 1 and pkt.tcp_flags.ack == 1) or # Oh, this kills me
  2739. pkt.tcp_src < 1024 # If it's a low port, assume it's a proper service.
  2740. if seen_hosts[saddr]
  2741. unless seen_hosts[saddr].include? [pkt.tcp_src,"tcp"]
  2742. report_service(
  2743. :workspace => wspace, :host => saddr,
  2744. :proto => "tcp", :port => pkt.tcp_src,
  2745. :state => Msf::ServiceState::Open
  2746. )
  2747. seen_hosts[saddr] << [pkt.tcp_src,"tcp"]
  2748. yield(:service,"%s:%d/%s" % [saddr,pkt.tcp_src,"tcp"])
  2749. end
  2750. end
  2751. end
  2752. elsif pkt.is_udp? # First pass on UDP packets
  2753. if pkt.udp_src == pkt.udp_dst # Very basic p2p detection.
  2754. [saddr,daddr].each do |xaddr|
  2755. if seen_hosts[xaddr]
  2756. unless seen_hosts[xaddr].include? [pkt.udp_src,"udp"]
  2757. report_service(
  2758. :workspace => wspace, :host => xaddr,
  2759. :proto => "udp", :port => pkt.udp_src,
  2760. :state => Msf::ServiceState::Open
  2761. )
  2762. seen_hosts[xaddr] << [pkt.udp_src,"udp"]
  2763. yield(:service,"%s:%d/%s" % [xaddr,pkt.udp_src,"udp"])
  2764. end
  2765. end
  2766. end
  2767. elsif pkt.udp_src < 1024 # Probably a service
  2768. if seen_hosts[saddr]
  2769. unless seen_hosts[saddr].include? [pkt.udp_src,"udp"]
  2770. report_service(
  2771. :workspace => wspace, :host => saddr,
  2772. :proto => "udp", :port => pkt.udp_src,
  2773. :state => Msf::ServiceState::Open
  2774. )
  2775. seen_hosts[saddr] << [pkt.udp_src,"udp"]
  2776. yield(:service,"%s:%d/%s" % [saddr,pkt.udp_src,"udp"])
  2777. end
  2778. end
  2779. end
  2780. end # tcp or udp
  2781. inspect_single_packet(pkt,wspace)
  2782. end # data.body.map
  2783. # Right about here, we should have built up some streams for some stream analysis.
  2784. # Not sure what form that will take, but people like shoving many hundreds of
  2785. # thousands of packets through this thing, so it'll need to be memory efficient.
  2786. end
  2787. # Do all the single packet analysis we can while churning through the pcap
  2788. # the first time. Multiple packet inspection will come later, where we can
  2789. # do stream analysis, compare requests and responses, etc.
  2790. def inspect_single_packet(pkt,wspace)
  2791. if pkt.is_tcp? or pkt.is_udp?
  2792. inspect_single_packet_http(pkt,wspace)
  2793. end
  2794. end
  2795. # Checks for packets that are headed towards port 80, are tcp, contain an HTTP/1.0
  2796. # line, contains an Authorization line, contains a b64-encoded credential, and
  2797. # extracts it. Reports this credential and solidifies the service as HTTP.
  2798. def inspect_single_packet_http(pkt,wspace)
  2799. # First, check the server side (data from port 80).
  2800. if pkt.is_tcp? and pkt.tcp_src == 80 and !pkt.payload.nil? and !pkt.payload.empty?
  2801. if pkt.payload =~ /^HTTP\x2f1\x2e[01]/
  2802. http_server_match = pkt.payload.match(/\nServer:\s+([^\r\n]+)[\r\n]/)
  2803. if http_server_match.kind_of?(MatchData) and http_server_match[1]
  2804. report_service(
  2805. :workspace => wspace,
  2806. :host => pkt.ip_saddr,
  2807. :port => pkt.tcp_src,
  2808. :proto => "tcp",
  2809. :name => "http",
  2810. :info => http_server_match[1],
  2811. :state => Msf::ServiceState::Open
  2812. )
  2813. # That's all we want to know from this service.
  2814. return :something_significant
  2815. end
  2816. end
  2817. end
  2818. # Next, check the client side (data to port 80)
  2819. if pkt.is_tcp? and pkt.tcp_dst == 80 and !pkt.payload.nil? and !pkt.payload.empty?
  2820. if pkt.payload.match(/[\x00-\x20]HTTP\x2f1\x2e[10]/)
  2821. auth_match = pkt.payload.match(/\nAuthorization:\s+Basic\s+([A-Za-z0-9=\x2b]+)/)
  2822. if auth_match.kind_of?(MatchData) and auth_match[1]
  2823. b64_cred = auth_match[1]
  2824. else
  2825. return false
  2826. end
  2827. # If we're this far, we can surmise that at least the client is a web browser,
  2828. # he thinks the server is HTTP and he just made an authentication attempt. At
  2829. # this point, we'll just believe everything the packet says -- validation ought
  2830. # to come later.
  2831. user,pass = b64_cred.unpack("m*").first.split(/:/,2)
  2832. report_service(
  2833. :workspace => wspace,
  2834. :host => pkt.ip_daddr,
  2835. :port => pkt.tcp_dst,
  2836. :proto => "tcp",
  2837. :name => "http"
  2838. )
  2839. report_auth_info(
  2840. :workspace => wspace,
  2841. :host => pkt.ip_daddr,
  2842. :port => pkt.tcp_dst,
  2843. :proto => "tcp",
  2844. :type => "password",
  2845. :active => true, # Once we can build a stream, determine if the auth was successful. For now, assume it is.
  2846. :user => user,
  2847. :pass => pass
  2848. )
  2849. # That's all we want to know from this service.
  2850. return :something_significant
  2851. end
  2852. end
  2853. end
  2854. def import_spiceworks_csv(args={}, &block)
  2855. data = args[:data]
  2856. wspace = args[:wspace] || workspace
  2857. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2858. CSV.parse(data) do |row|
  2859. next unless (["Name", "Manufacturer", "Device Type"] & row).empty? #header
  2860. name = row[0]
  2861. manufacturer = row[1]
  2862. device = row[2]
  2863. model = row[3]
  2864. ip = row[4]
  2865. serialno = row[5]
  2866. location = row[6]
  2867. os = row[7]
  2868. next unless ip
  2869. next if bl.include? ip
  2870. conf = {
  2871. :workspace => wspace,
  2872. :host => ip,
  2873. :name => name
  2874. }
  2875. conf[:os_name] = os if os
  2876. info = []
  2877. info << "Serial Number: #{serialno}" unless (serialno.blank? or serialno == name)
  2878. info << "Location: #{location}" unless location.blank?
  2879. conf[:info] = info.join(", ") unless info.empty?
  2880. host = report_host(conf)
  2881. report_import_note(wspace, host)
  2882. end
  2883. end
  2884. #
  2885. # Metasploit PWDump Export
  2886. #
  2887. # This file format is generated by the db_export -f pwdump and
  2888. # the Metasploit Express and Pro report types of "PWDump."
  2889. #
  2890. # This particular block scheme is temporary, since someone is
  2891. # bound to want to import gigantic lists, so we'll want a
  2892. # stream parser eventually (just like the other non-nmap formats).
  2893. #
  2894. # The file format is:
  2895. # # 1.2.3.4:23/tcp (telnet)
  2896. # username password
  2897. # user2 p\x01a\x02ss2
  2898. # <BLANK> pass3
  2899. # user3 <BLANK>
  2900. # smbuser:sid:lmhash:nthash:::
  2901. #
  2902. # Note the leading hash for the host:port line. Note also all usernames
  2903. # and passwords must be in 7-bit ASCII (character sequences of "\x01"
  2904. # will be interpolated -- this includes spaces, which must be notated
  2905. # as "\x20". Blank usernames or passwords should be <BLANK>.
  2906. #
  2907. def import_msf_pwdump(args={}, &block)
  2908. data = args[:data]
  2909. wspace = args[:wspace] || workspace
  2910. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2911. last_host = nil
  2912. addr = nil
  2913. port = nil
  2914. proto = nil
  2915. sname = nil
  2916. ptype = nil
  2917. active = false # Are there cases where imported creds are good? I just hate trusting the import right away.
  2918. data.each_line do |line|
  2919. case line
  2920. when /^[\s]*#/ # Comment lines
  2921. if line[/^#[\s]*([0-9.]+):([0-9]+)(\x2f(tcp|udp))?[\s]*(\x28([^\x29]*)\x29)?/]
  2922. addr = $1
  2923. port = $2
  2924. proto = $4
  2925. sname = $6
  2926. end
  2927. when /^[\s]*Warning:/
  2928. next # Discard warning messages.
  2929. when /^[\s]*([^\s:]+):[0-9]+:([A-Fa-f0-9]+:[A-Fa-f0-9]+):[^\s]*$/ # SMB Hash
  2930. user = ([nil, "<BLANK>"].include?($1)) ? "" : $1
  2931. pass = ([nil, "<BLANK>"].include?($2)) ? "" : $2
  2932. ptype = "smb_hash"
  2933. when /^[\s]*([^\s:]+):([0-9]+):NO PASSWORD\*+:NO PASSWORD\*+[^\s]*$/ # SMB Hash
  2934. user = ([nil, "<BLANK>"].include?($1)) ? "" : $1
  2935. pass = ""
  2936. ptype = "smb_hash"
  2937. when /^[\s]*([\x21-\x7f]+)[\s]+([\x21-\x7f]+)?/ # Must be a user pass
  2938. user = ([nil, "<BLANK>"].include?($1)) ? "" : dehex($1)
  2939. pass = ([nil, "<BLANK>"].include?($2)) ? "" : dehex($2)
  2940. ptype = "password"
  2941. else # Some unknown line not broken by a space.
  2942. next
  2943. end
  2944. next unless [addr,port,user,pass].compact.size == 4
  2945. next unless ipv46_validator(addr) # Skip Malformed addrs
  2946. next unless port[/^[0-9]+$/] # Skip malformed ports
  2947. if bl.include? addr
  2948. next
  2949. else
  2950. yield(:address,addr) if block and addr != last_host
  2951. last_host = addr
  2952. end
  2953. cred_info = {
  2954. :host => addr,
  2955. :port => port,
  2956. :user => user,
  2957. :pass => pass,
  2958. :type => ptype,
  2959. :workspace => wspace
  2960. }
  2961. cred_info[:proto] = proto if proto
  2962. cred_info[:sname] = sname if sname
  2963. cred_info[:active] = active
  2964. report_auth_info(cred_info)
  2965. user = pass = ptype = nil
  2966. end
  2967. end
  2968. # If hex notation is present, turn them into a character.
  2969. def dehex(str)
  2970. hexen = str.scan(/\x5cx[0-9a-fA-F]{2}/)
  2971. hexen.each { |h|
  2972. str.gsub!(h,h[2,2].to_i(16).chr)
  2973. }
  2974. return str
  2975. end
  2976. #
  2977. # Nexpose Simple XML
  2978. #
  2979. # XXX At some point we'll want to make this a stream parser for dealing
  2980. # with large results files
  2981. #
  2982. def import_nexpose_simplexml_file(args={})
  2983. filename = args[:filename]
  2984. wspace = args[:wspace] || workspace
  2985. data = ""
  2986. ::File.open(filename, 'rb') do |f|
  2987. data = f.read(f.stat.size)
  2988. end
  2989. import_nexpose_simplexml(args.merge(:data => data))
  2990. end
  2991. # Import a Metasploit XML file.
  2992. def import_msf_file(args={})
  2993. filename = args[:filename]
  2994. wspace = args[:wspace] || workspace
  2995. data = ""
  2996. ::File.open(filename, 'rb') do |f|
  2997. data = f.read(f.stat.size)
  2998. end
  2999. import_msf_xml(args.merge(:data => data))
  3000. end
  3001. # Import a Metasploit Express ZIP file. Note that this requires
  3002. # a fair bit of filesystem manipulation, and is very much tied
  3003. # up with the Metasploit Express ZIP file format export (for
  3004. # obvious reasons). In the event directories exist, they will
  3005. # be reused. If target files exist, they will be overwritten.
  3006. #
  3007. # XXX: Refactor so it's not quite as sanity-blasting.
  3008. def import_msf_zip(args={}, &block)
  3009. data = args[:data]
  3010. wpsace = args[:wspace] || workspace
  3011. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3012. new_tmp = ::File.join(Dir::tmpdir,"msf","imp_#{Rex::Text::rand_text_alphanumeric(4)}",@import_filedata[:zip_basename])
  3013. if ::File.exists? new_tmp
  3014. unless (::File.directory?(new_tmp) && ::File.writable?(new_tmp))
  3015. raise DBImportError.new("Could not extract zip file to #{new_tmp}")
  3016. end
  3017. else
  3018. FileUtils.mkdir_p(new_tmp)
  3019. end
  3020. @import_filedata[:zip_tmp] = new_tmp
  3021. # Grab the list of unique basedirs over all entries.
  3022. @import_filedata[:zip_tmp_subdirs] = @import_filedata[:zip_entry_names].map {|x| ::File.split(x)}.map {|x| x[0]}.uniq.reject {|x| x == "."}
  3023. # mkdir all of the base directores we just pulled out, if they don't
  3024. # already exist
  3025. @import_filedata[:zip_tmp_subdirs].each {|sub|
  3026. tmp_subdirs = ::File.join(@import_filedata[:zip_tmp],sub)
  3027. if File.exists? tmp_subdirs
  3028. unless (::File.directory?(tmp_subdirs) && File.writable?(tmp_subdirs))
  3029. # if it exists but we can't write to it, give up
  3030. raise DBImportError.new("Could not extract zip file to #{tmp_subdirs}")
  3031. end
  3032. else
  3033. ::FileUtils.mkdir(tmp_subdirs)
  3034. end
  3035. }
  3036. data.entries.each do |e|
  3037. target = ::File.join(@import_filedata[:zip_tmp],e.name)
  3038. ::File.unlink target if ::File.exists?(target) # Yep. Deleted.
  3039. data.extract(e,target)
  3040. if target =~ /^.*.xml$/
  3041. target_data = ::File.open(target, "rb") {|f| f.read 1024}
  3042. if import_filetype_detect(target_data) == :msf_xml
  3043. @import_filedata[:zip_extracted_xml] = target
  3044. #break
  3045. end
  3046. end
  3047. end
  3048. # This will kick the newly-extracted XML file through
  3049. # the import_file process all over again.
  3050. if @import_filedata[:zip_extracted_xml]
  3051. new_args = args.dup
  3052. new_args[:filename] = @import_filedata[:zip_extracted_xml]
  3053. new_args[:data] = nil
  3054. new_args[:ifd] = @import_filedata.dup
  3055. if block
  3056. import_file(new_args, &block)
  3057. else
  3058. import_file(new_args)
  3059. end
  3060. end
  3061. # Kick down to all the MSFX ZIP specific items
  3062. if block
  3063. import_msf_collateral(new_args, &block)
  3064. else
  3065. import_msf_collateral(new_args)
  3066. end
  3067. end
  3068. # Imports loot, tasks, and reports from an MSF ZIP report.
  3069. # XXX: This function is stupidly long. It needs to be refactored.
  3070. def import_msf_collateral(args={}, &block)
  3071. data = ::File.open(args[:filename], "rb") {|f| f.read(f.stat.size)}
  3072. wspace = args[:wspace] || args['wspace'] || workspace
  3073. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3074. basedir = args[:basedir] || args['basedir'] || ::File.join(Msf::Config.install_root, "data", "msf")
  3075. allow_yaml = false
  3076. btag = nil
  3077. doc = rexmlify(data)
  3078. if doc.elements["MetasploitExpressV1"]
  3079. m_ver = 1
  3080. allow_yaml = true
  3081. btag = "MetasploitExpressV1"
  3082. elsif doc.elements["MetasploitExpressV2"]
  3083. m_ver = 2
  3084. allow_yaml = true
  3085. btag = "MetasploitExpressV2"
  3086. elsif doc.elements["MetasploitExpressV3"]
  3087. m_ver = 3
  3088. btag = "MetasploitExpressV3"
  3089. elsif doc.elements["MetasploitExpressV4"]
  3090. m_ver = 4
  3091. btag = "MetasploitExpressV4"
  3092. elsif doc.elements["MetasploitV4"]
  3093. m_ver = 4
  3094. btag = "MetasploitV4"
  3095. else
  3096. m_ver = nil
  3097. end
  3098. unless m_ver and btag
  3099. raise DBImportError.new("Unsupported Metasploit XML document format")
  3100. end
  3101. host_info = {}
  3102. doc.elements.each("/#{btag}/hosts/host") do |host|
  3103. host_info[host.elements["id"].text.to_s.strip] = nils_for_nulls(host.elements["address"].text.to_s.strip)
  3104. end
  3105. # Import Loot
  3106. doc.elements.each("/#{btag}/loots/loot") do |loot|
  3107. next if bl.include? host_info[loot.elements["host-id"].text.to_s.strip]
  3108. loot_info = {}
  3109. loot_info[:host] = host_info[loot.elements["host-id"].text.to_s.strip]
  3110. loot_info[:workspace] = args[:wspace]
  3111. loot_info[:ctype] = nils_for_nulls(loot.elements["content-type"].text.to_s.strip)
  3112. loot_info[:info] = nils_for_nulls(unserialize_object(loot.elements["info"], allow_yaml))
  3113. loot_info[:ltype] = nils_for_nulls(loot.elements["ltype"].text.to_s.strip)
  3114. loot_info[:name] = nils_for_nulls(loot.elements["name"].text.to_s.strip)
  3115. loot_info[:created_at] = nils_for_nulls(loot.elements["created-at"].text.to_s.strip)
  3116. loot_info[:updated_at] = nils_for_nulls(loot.elements["updated-at"].text.to_s.strip)
  3117. loot_info[:name] = nils_for_nulls(loot.elements["name"].text.to_s.strip)
  3118. loot_info[:orig_path] = nils_for_nulls(loot.elements["path"].text.to_s.strip)
  3119. tmp = args[:ifd][:zip_tmp]
  3120. loot_info[:orig_path].gsub!(/^\./,tmp) if loot_info[:orig_path]
  3121. if !loot.elements["service-id"].text.to_s.strip.empty?
  3122. unless loot.elements["service-id"].text.to_s.strip == "NULL"
  3123. loot_info[:service] = loot.elements["service-id"].text.to_s.strip
  3124. end
  3125. end
  3126. # Only report loot if we actually have it.
  3127. # TODO: Copypasta. Seperate this out.
  3128. if ::File.exists? loot_info[:orig_path]
  3129. loot_dir = ::File.join(basedir,"loot")
  3130. loot_file = ::File.split(loot_info[:orig_path]).last
  3131. if ::File.exists? loot_dir
  3132. unless (::File.directory?(loot_dir) && ::File.writable?(loot_dir))
  3133. raise DBImportError.new("Could not move files to #{loot_dir}")
  3134. end
  3135. else
  3136. ::FileUtils.mkdir_p(loot_dir)
  3137. end
  3138. new_loot = ::File.join(loot_dir,loot_file)
  3139. loot_info[:path] = new_loot
  3140. if ::File.exists?(new_loot)
  3141. ::File.unlink new_loot # Delete it, and don't report it.
  3142. else
  3143. report_loot(loot_info) # It's new, so report it.
  3144. end
  3145. ::FileUtils.copy(loot_info[:orig_path], new_loot)
  3146. yield(:msf_loot, new_loot) if block
  3147. end
  3148. end
  3149. # Import Tasks
  3150. doc.elements.each("/#{btag}/tasks/task") do |task|
  3151. task_info = {}
  3152. task_info[:workspace] = args[:wspace]
  3153. # Should user be imported (original) or declared (the importing user)?
  3154. task_info[:user] = nils_for_nulls(task.elements["created-by"].text.to_s.strip)
  3155. task_info[:desc] = nils_for_nulls(task.elements["description"].text.to_s.strip)
  3156. task_info[:info] = nils_for_nulls(unserialize_object(task.elements["info"], allow_yaml))
  3157. task_info[:mod] = nils_for_nulls(task.elements["module"].text.to_s.strip)
  3158. task_info[:options] = nils_for_nulls(task.elements["options"].text.to_s.strip)
  3159. task_info[:prog] = nils_for_nulls(task.elements["progress"].text.to_s.strip).to_i
  3160. task_info[:created_at] = nils_for_nulls(task.elements["created-at"].text.to_s.strip)
  3161. task_info[:updated_at] = nils_for_nulls(task.elements["updated-at"].text.to_s.strip)
  3162. if !task.elements["completed-at"].text.to_s.empty?
  3163. task_info[:completed_at] = nils_for_nulls(task.elements["completed-at"].text.to_s.strip)
  3164. end
  3165. if !task.elements["error"].text.to_s.empty?
  3166. task_info[:error] = nils_for_nulls(task.elements["error"].text.to_s.strip)
  3167. end
  3168. if !task.elements["result"].text.to_s.empty?
  3169. task_info[:result] = nils_for_nulls(task.elements["result"].text.to_s.strip)
  3170. end
  3171. task_info[:orig_path] = nils_for_nulls(task.elements["path"].text.to_s.strip)
  3172. tmp = args[:ifd][:zip_tmp]
  3173. task_info[:orig_path].gsub!(/^\./,tmp) if task_info[:orig_path]
  3174. # Only report a task if we actually have it.
  3175. # TODO: Copypasta. Seperate this out.
  3176. if ::File.exists? task_info[:orig_path]
  3177. tasks_dir = ::File.join(basedir,"tasks")
  3178. task_file = ::File.split(task_info[:orig_path]).last
  3179. if ::File.exists? tasks_dir
  3180. unless (::File.directory?(tasks_dir) && ::File.writable?(tasks_dir))
  3181. raise DBImportError.new("Could not move files to #{tasks_dir}")
  3182. end
  3183. else
  3184. ::FileUtils.mkdir_p(tasks_dir)
  3185. end
  3186. new_task = ::File.join(tasks_dir,task_file)
  3187. task_info[:path] = new_task
  3188. if ::File.exists?(new_task)
  3189. ::File.unlink new_task # Delete it, and don't report it.
  3190. else
  3191. report_task(task_info) # It's new, so report it.
  3192. end
  3193. ::FileUtils.copy(task_info[:orig_path], new_task)
  3194. yield(:msf_task, new_task) if block
  3195. end
  3196. end
  3197. # Import Reports
  3198. doc.elements.each("/#{btag}/reports/report") do |report|
  3199. report_info = {}
  3200. report_info[:workspace] = args[:wspace]
  3201. # Should user be imported (original) or declared (the importing user)?
  3202. report_info[:user] = nils_for_nulls(report.elements["created-by"].text.to_s.strip)
  3203. report_info[:options] = nils_for_nulls(report.elements["options"].text.to_s.strip)
  3204. report_info[:rtype] = nils_for_nulls(report.elements["rtype"].text.to_s.strip)
  3205. report_info[:created_at] = nils_for_nulls(report.elements["created-at"].text.to_s.strip)
  3206. report_info[:updated_at] = nils_for_nulls(report.elements["updated-at"].text.to_s.strip)
  3207. report_info[:orig_path] = nils_for_nulls(report.elements["path"].text.to_s.strip)
  3208. tmp = args[:ifd][:zip_tmp]
  3209. report_info[:orig_path].gsub!(/^\./,tmp) if report_info[:orig_path]
  3210. # Only report a report if we actually have it.
  3211. # TODO: Copypasta. Seperate this out.
  3212. if ::File.exists? report_info[:orig_path]
  3213. reports_dir = ::File.join(basedir,"reports")
  3214. report_file = ::File.split(report_info[:orig_path]).last
  3215. if ::File.exists? reports_dir
  3216. unless (::File.directory?(reports_dir) && ::File.writable?(reports_dir))
  3217. raise DBImportError.new("Could not move files to #{reports_dir}")
  3218. end
  3219. else
  3220. ::FileUtils.mkdir_p(reports_dir)
  3221. end
  3222. new_report = ::File.join(reports_dir,report_file)
  3223. report_info[:path] = new_report
  3224. if ::File.exists?(new_report)
  3225. ::File.unlink new_report
  3226. else
  3227. report_report(report_info)
  3228. end
  3229. ::FileUtils.copy(report_info[:orig_path], new_report)
  3230. yield(:msf_report, new_report) if block
  3231. end
  3232. end
  3233. end
  3234. # For each host, step through services, notes, and vulns, and import
  3235. # them.
  3236. # TODO: loot, tasks, and reports
  3237. def import_msf_xml(args={}, &block)
  3238. data = args[:data]
  3239. wspace = args[:wspace] || workspace
  3240. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3241. allow_yaml = false
  3242. btag = nil
  3243. doc = rexmlify(data)
  3244. if doc.elements["MetasploitExpressV1"]
  3245. m_ver = 1
  3246. allow_yaml = true
  3247. btag = "MetasploitExpressV1"
  3248. elsif doc.elements["MetasploitExpressV2"]
  3249. m_ver = 2
  3250. allow_yaml = true
  3251. btag = "MetasploitExpressV2"
  3252. elsif doc.elements["MetasploitExpressV3"]
  3253. m_ver = 3
  3254. btag = "MetasploitExpressV3"
  3255. elsif doc.elements["MetasploitExpressV4"]
  3256. m_ver = 4
  3257. btag = "MetasploitExpressV4"
  3258. elsif doc.elements["MetasploitV4"]
  3259. m_ver = 4
  3260. btag = "MetasploitV4"
  3261. else
  3262. m_ver = nil
  3263. end
  3264. unless m_ver and btag
  3265. raise DBImportError.new("Unsupported Metasploit XML document format")
  3266. end
  3267. doc.elements.each("/#{btag}/hosts/host") do |host|
  3268. host_data = {}
  3269. host_data[:workspace] = wspace
  3270. host_data[:host] = nils_for_nulls(host.elements["address"].text.to_s.strip)
  3271. if bl.include? host_data[:host]
  3272. next
  3273. else
  3274. yield(:address,host_data[:host]) if block
  3275. end
  3276. host_data[:mac] = nils_for_nulls(host.elements["mac"].text.to_s.strip)
  3277. if host.elements["comm"].text
  3278. host_data[:comm] = nils_for_nulls(host.elements["comm"].text.to_s.strip)
  3279. end
  3280. %W{created-at updated-at name state os-flavor os-lang os-name os-sp purpose}.each { |datum|
  3281. if host.elements[datum].text
  3282. host_data[datum.gsub('-','_')] = nils_for_nulls(host.elements[datum].text.to_s.strip)
  3283. end
  3284. }
  3285. host_address = host_data[:host].dup # Preserve after report_host() deletes
  3286. hobj = report_host(host_data)
  3287. host.elements.each("host_details/host_detail") do |hdet|
  3288. hdet_data = {}
  3289. hdet.elements.each do |det|
  3290. next if ["id", "host-id"].include?(det.name)
  3291. if det.text
  3292. hdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip)
  3293. end
  3294. end
  3295. report_host_details(hobj, hdet_data)
  3296. end
  3297. host.elements.each("exploit_attempts/exploit_attempt") do |hdet|
  3298. hdet_data = {}
  3299. hdet.elements.each do |det|
  3300. next if ["id", "host-id", "session-id", "vuln-id", "service-id", "loot-id"].include?(det.name)
  3301. if det.text
  3302. hdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip)
  3303. end
  3304. end
  3305. report_exploit_attempt(hobj, hdet_data)
  3306. end
  3307. host.elements.each('services/service') do |service|
  3308. service_data = {}
  3309. service_data[:workspace] = wspace
  3310. service_data[:host] = hobj
  3311. service_data[:port] = nils_for_nulls(service.elements["port"].text.to_s.strip).to_i
  3312. service_data[:proto] = nils_for_nulls(service.elements["proto"].text.to_s.strip)
  3313. %W{created-at updated-at name state info}.each { |datum|
  3314. if service.elements[datum].text
  3315. if datum == "info"
  3316. service_data["info"] = nils_for_nulls(unserialize_object(service.elements[datum], false))
  3317. else
  3318. service_data[datum.gsub("-","_")] = nils_for_nulls(service.elements[datum].text.to_s.strip)
  3319. end
  3320. end
  3321. }
  3322. report_service(service_data)
  3323. end
  3324. host.elements.each('notes/note') do |note|
  3325. note_data = {}
  3326. note_data[:workspace] = wspace
  3327. note_data[:host] = hobj
  3328. note_data[:type] = nils_for_nulls(note.elements["ntype"].text.to_s.strip)
  3329. note_data[:data] = nils_for_nulls(unserialize_object(note.elements["data"], allow_yaml))
  3330. if note.elements["critical"].text
  3331. note_data[:critical] = true unless note.elements["critical"].text.to_s.strip == "NULL"
  3332. end
  3333. if note.elements["seen"].text
  3334. note_data[:seen] = true unless note.elements["critical"].text.to_s.strip == "NULL"
  3335. end
  3336. %W{created-at updated-at}.each { |datum|
  3337. if note.elements[datum].text
  3338. note_data[datum.gsub("-","_")] = nils_for_nulls(note.elements[datum].text.to_s.strip)
  3339. end
  3340. }
  3341. report_note(note_data)
  3342. end
  3343. host.elements.each('tags/tag') do |tag|
  3344. tag_data = {}
  3345. tag_data[:addr] = host_address
  3346. tag_data[:wspace] = wspace
  3347. tag_data[:name] = tag.elements["name"].text.to_s.strip
  3348. tag_data[:desc] = tag.elements["desc"].text.to_s.strip
  3349. if tag.elements["report-summary"].text
  3350. tag_data[:summary] = tag.elements["report-summary"].text.to_s.strip
  3351. end
  3352. if tag.elements["report-detail"].text
  3353. tag_data[:detail] = tag.elements["report-detail"].text.to_s.strip
  3354. end
  3355. if tag.elements["critical"].text
  3356. tag_data[:crit] = true unless tag.elements["critical"].text.to_s.strip == "NULL"
  3357. end
  3358. report_host_tag(tag_data)
  3359. end
  3360. host.elements.each('vulns/vuln') do |vuln|
  3361. vuln_data = {}
  3362. vuln_data[:workspace] = wspace
  3363. vuln_data[:host] = hobj
  3364. vuln_data[:info] = nils_for_nulls(unserialize_object(vuln.elements["info"], allow_yaml))
  3365. vuln_data[:name] = nils_for_nulls(vuln.elements["name"].text.to_s.strip)
  3366. %W{created-at updated-at exploited-at}.each { |datum|
  3367. if vuln.elements[datum] and vuln.elements[datum].text
  3368. vuln_data[datum.gsub("-","_")] = nils_for_nulls(vuln.elements[datum].text.to_s.strip)
  3369. end
  3370. }
  3371. if vuln.elements["refs"]
  3372. vuln_data[:refs] = []
  3373. vuln.elements.each("refs/ref") do |ref|
  3374. vuln_data[:refs] << nils_for_nulls(ref.text.to_s.strip)
  3375. end
  3376. end
  3377. vobj = report_vuln(vuln_data)
  3378. vuln.elements.each("vuln_details/vuln_detail") do |vdet|
  3379. vdet_data = {}
  3380. vdet.elements.each do |det|
  3381. next if ["id", "vuln-id"].include?(det.name)
  3382. if det.text
  3383. vdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip)
  3384. end
  3385. end
  3386. report_vuln_details(vobj, vdet_data)
  3387. end
  3388. vuln.elements.each("vuln_attempts/vuln_attempt") do |vdet|
  3389. vdet_data = {}
  3390. vdet.elements.each do |det|
  3391. next if ["id", "vuln-id", "loot-id", "session-id"].include?(det.name)
  3392. if det.text
  3393. vdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip)
  3394. end
  3395. end
  3396. report_vuln_attempt(vobj, vdet_data)
  3397. end
  3398. end
  3399. host.elements.each('creds/cred') do |cred|
  3400. cred_data = {}
  3401. cred_data[:workspace] = wspace
  3402. cred_data[:host] = hobj
  3403. %W{port ptype sname proto proof active user pass}.each {|datum|
  3404. if cred.elements[datum].respond_to? :text
  3405. cred_data[datum.intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
  3406. end
  3407. }
  3408. %W{created-at updated-at}.each { |datum|
  3409. if cred.elements[datum].respond_to? :text
  3410. cred_data[datum.gsub("-","_")] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
  3411. end
  3412. }
  3413. %W{source-type source-id}.each { |datum|
  3414. if cred.elements[datum].respond_to? :text
  3415. cred_data[datum.gsub("-","_").intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
  3416. end
  3417. }
  3418. if cred_data[:pass] == "<masked>"
  3419. cred_data[:pass] = ""
  3420. cred_data[:active] = false
  3421. elsif cred_data[:pass] == "*BLANK PASSWORD*"
  3422. cred_data[:pass] = ""
  3423. end
  3424. report_cred(cred_data)
  3425. end
  3426. host.elements.each('sessions/session') do |sess|
  3427. sess_id = nils_for_nulls(sess.elements["id"].text.to_s.strip.to_i)
  3428. sess_data = {}
  3429. sess_data[:host] = hobj
  3430. %W{desc platform port stype}.each {|datum|
  3431. if sess.elements[datum].respond_to? :text
  3432. sess_data[datum.intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip)
  3433. end
  3434. }
  3435. %W{opened-at close-reason closed-at via-exploit via-payload}.each {|datum|
  3436. if sess.elements[datum].respond_to? :text
  3437. sess_data[datum.gsub("-","_").intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip)
  3438. end
  3439. }
  3440. sess_data[:datastore] = nils_for_nulls(unserialize_object(sess.elements["datastore"], allow_yaml))
  3441. if sess.elements["routes"]
  3442. sess_data[:routes] = nils_for_nulls(unserialize_object(sess.elements["routes"], allow_yaml)) || []
  3443. end
  3444. if not sess_data[:closed_at] # Fake a close if we don't already have one
  3445. sess_data[:closed_at] = Time.now.utc
  3446. sess_data[:close_reason] = "Imported at #{Time.now.utc}"
  3447. end
  3448. existing_session = get_session(
  3449. :workspace => sess_data[:host].workspace,
  3450. :addr => sess_data[:host].address,
  3451. :time => sess_data[:opened_at]
  3452. )
  3453. this_session = existing_session || report_session(sess_data)
  3454. next if existing_session
  3455. sess.elements.each('events/event') do |sess_event|
  3456. sess_event_data = {}
  3457. sess_event_data[:session] = this_session
  3458. %W{created-at etype local-path remote-path}.each {|datum|
  3459. if sess_event.elements[datum].respond_to? :text
  3460. sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(sess_event.elements[datum].text.to_s.strip)
  3461. end
  3462. }
  3463. %W{command output}.each {|datum|
  3464. if sess_event.elements[datum].respond_to? :text
  3465. sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(unserialize_object(sess_event.elements[datum], allow_yaml))
  3466. end
  3467. }
  3468. report_session_event(sess_event_data)
  3469. end
  3470. end
  3471. end
  3472. # Import web sites
  3473. doc.elements.each("/#{btag}/web_sites/web_site") do |web|
  3474. info = {}
  3475. info[:workspace] = wspace
  3476. %W{host port vhost ssl comments}.each do |datum|
  3477. if web.elements[datum].respond_to? :text
  3478. info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  3479. end
  3480. end
  3481. info[:options] = nils_for_nulls(unserialize_object(web.elements["options"], allow_yaml)) if web.elements["options"].respond_to?(:text)
  3482. info[:ssl] = (info[:ssl] and info[:ssl].to_s.strip.downcase == "true") ? true : false
  3483. %W{created-at updated-at}.each { |datum|
  3484. if web.elements[datum].text
  3485. info[datum.gsub("-","_")] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  3486. end
  3487. }
  3488. report_web_site(info)
  3489. yield(:web_site, "#{info[:host]}:#{info[:port]} (#{info[:vhost]})") if block
  3490. end
  3491. %W{page form vuln}.each do |wtype|
  3492. doc.elements.each("/#{btag}/web_#{wtype}s/web_#{wtype}") do |web|
  3493. info = {}
  3494. info[:workspace] = wspace
  3495. info[:host] = nils_for_nulls(web.elements["host"].text.to_s.strip) if web.elements["host"].respond_to?(:text)
  3496. info[:port] = nils_for_nulls(web.elements["port"].text.to_s.strip) if web.elements["port"].respond_to?(:text)
  3497. info[:ssl] = nils_for_nulls(web.elements["ssl"].text.to_s.strip) if web.elements["ssl"].respond_to?(:text)
  3498. info[:vhost] = nils_for_nulls(web.elements["vhost"].text.to_s.strip) if web.elements["vhost"].respond_to?(:text)
  3499. info[:ssl] = (info[:ssl] and info[:ssl].to_s.strip.downcase == "true") ? true : false
  3500. case wtype
  3501. when "page"
  3502. %W{path code body query cookie auth ctype mtime location}.each do |datum|
  3503. if web.elements[datum].respond_to? :text
  3504. info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  3505. end
  3506. end
  3507. info[:headers] = nils_for_nulls(unserialize_object(web.elements["headers"], allow_yaml))
  3508. when "form"
  3509. %W{path query method}.each do |datum|
  3510. if web.elements[datum].respond_to? :text
  3511. info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  3512. end
  3513. end
  3514. info[:params] = nils_for_nulls(unserialize_object(web.elements["params"], allow_yaml))
  3515. when "vuln"
  3516. %W{path query method pname proof risk name blame description category confidence}.each do |datum|
  3517. if web.elements[datum].respond_to? :text
  3518. info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  3519. end
  3520. end
  3521. info[:params] = nils_for_nulls(unserialize_object(web.elements["params"], allow_yaml))
  3522. info[:risk] = info[:risk].to_i
  3523. info[:confidence] = info[:confidence].to_i
  3524. end
  3525. %W{created-at updated-at}.each { |datum|
  3526. if web.elements[datum].text
  3527. info[datum.gsub("-","_")] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  3528. end
  3529. }
  3530. self.send("report_web_#{wtype}", info)
  3531. yield("web_#{wtype}".intern, info[:path]) if block
  3532. end
  3533. end
  3534. end
  3535. # Convert the string "NULL" to actual nil
  3536. def nils_for_nulls(str)
  3537. str == "NULL" ? nil : str
  3538. end
  3539. def import_nexpose_simplexml(args={}, &block)
  3540. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3541. wspace = args[:wspace] || workspace
  3542. if Rex::Parser.nokogiri_loaded
  3543. parser = "Nokogiri v#{::Nokogiri::VERSION}"
  3544. noko_args = args.dup
  3545. noko_args[:blacklist] = bl
  3546. noko_args[:wspace] = wspace
  3547. if block
  3548. yield(:parser, parser)
  3549. import_nexpose_noko_stream(noko_args) {|type, data| yield type,data}
  3550. else
  3551. import_nexpose_noko_stream(noko_args)
  3552. end
  3553. return true
  3554. end
  3555. data = args[:data]
  3556. doc = rexmlify(data)
  3557. doc.elements.each('/NeXposeSimpleXML/devices/device') do |dev|
  3558. addr = dev.attributes['address'].to_s
  3559. if bl.include? addr
  3560. next
  3561. else
  3562. yield(:address,addr) if block
  3563. end
  3564. fprint = {}
  3565. dev.elements.each('fingerprint/description') do |str|
  3566. fprint[:desc] = str.text.to_s.strip
  3567. end
  3568. dev.elements.each('fingerprint/vendor') do |str|
  3569. fprint[:vendor] = str.text.to_s.strip
  3570. end
  3571. dev.elements.each('fingerprint/family') do |str|
  3572. fprint[:family] = str.text.to_s.strip
  3573. end
  3574. dev.elements.each('fingerprint/product') do |str|
  3575. fprint[:product] = str.text.to_s.strip
  3576. end
  3577. dev.elements.each('fingerprint/version') do |str|
  3578. fprint[:version] = str.text.to_s.strip
  3579. end
  3580. dev.elements.each('fingerprint/architecture') do |str|
  3581. fprint[:arch] = str.text.to_s.upcase.strip
  3582. end
  3583. conf = {
  3584. :workspace => wspace,
  3585. :host => addr,
  3586. :state => Msf::HostState::Alive
  3587. }
  3588. host = report_host(conf)
  3589. report_import_note(wspace, host)
  3590. report_note(
  3591. :workspace => wspace,
  3592. :host => host,
  3593. :type => 'host.os.nexpose_fingerprint',
  3594. :data => fprint
  3595. )
  3596. # Load vulnerabilities not associated with a service
  3597. dev.elements.each('vulnerabilities/vulnerability') do |vuln|
  3598. vid = vuln.attributes['id'].to_s.downcase
  3599. refs = process_nexpose_data_sxml_refs(vuln)
  3600. next if not refs
  3601. report_vuln(
  3602. :workspace => wspace,
  3603. :host => host,
  3604. :name => 'NEXPOSE-' + vid,
  3605. :info => vid,
  3606. :refs => refs
  3607. )
  3608. end
  3609. # Load the services
  3610. dev.elements.each('services/service') do |svc|
  3611. sname = svc.attributes['name'].to_s
  3612. sprot = svc.attributes['protocol'].to_s.downcase
  3613. sport = svc.attributes['port'].to_s.to_i
  3614. next if sport == 0
  3615. name = sname.split('(')[0].strip
  3616. info = ''
  3617. svc.elements.each('fingerprint/description') do |str|
  3618. info = str.text.to_s.strip
  3619. end
  3620. if(sname.downcase != '<unknown>')
  3621. report_service(:workspace => wspace, :host => host, :proto => sprot, :port => sport, :name => name, :info => info)
  3622. else
  3623. report_service(:workspace => wspace, :host => host, :proto => sprot, :port => sport, :info => info)
  3624. end
  3625. # Load vulnerabilities associated with this service
  3626. svc.elements.each('vulnerabilities/vulnerability') do |vuln|
  3627. vid = vuln.attributes['id'].to_s.downcase
  3628. refs = process_nexpose_data_sxml_refs(vuln)
  3629. next if not refs
  3630. report_vuln(
  3631. :workspace => wspace,
  3632. :host => host,
  3633. :port => sport,
  3634. :proto => sprot,
  3635. :name => 'NEXPOSE-' + vid,
  3636. :info => vid,
  3637. :refs => refs
  3638. )
  3639. end
  3640. end
  3641. end
  3642. end
  3643. #
  3644. # Nexpose Raw XML
  3645. #
  3646. def import_nexpose_rawxml_file(args={})
  3647. filename = args[:filename]
  3648. wspace = args[:wspace] || workspace
  3649. data = ""
  3650. ::File.open(filename, 'rb') do |f|
  3651. data = f.read(f.stat.size)
  3652. end
  3653. import_nexpose_rawxml(args.merge(:data => data))
  3654. end
  3655. def import_nexpose_rawxml(args={}, &block)
  3656. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3657. wspace = args[:wspace] || workspace
  3658. if Rex::Parser.nokogiri_loaded
  3659. parser = "Nokogiri v#{::Nokogiri::VERSION}"
  3660. noko_args = args.dup
  3661. noko_args[:blacklist] = bl
  3662. noko_args[:wspace] = wspace
  3663. if block
  3664. yield(:parser, parser)
  3665. import_nexpose_raw_noko_stream(noko_args) {|type, data| yield type,data}
  3666. else
  3667. import_nexpose_raw_noko_stream(noko_args)
  3668. end
  3669. return true
  3670. end
  3671. data = args[:data]
  3672. # Use a stream parser instead of a tree parser so we can deal with
  3673. # huge results files without running out of memory.
  3674. parser = Rex::Parser::NexposeXMLStreamParser.new
  3675. # Since all the Refs have to be in the database before we can use them
  3676. # in a Vuln, we store all the hosts until we finish parsing and only
  3677. # then put everything in the database. This is memory-intensive for
  3678. # large files, but should be much less so than a tree parser.
  3679. #
  3680. # This method is also considerably faster than parsing through the tree
  3681. # looking for references every time we hit a vuln.
  3682. hosts = []
  3683. vulns = []
  3684. # The callback merely populates our in-memory table of hosts and vulns
  3685. parser.callback = Proc.new { |type, value|
  3686. case type
  3687. when :host
  3688. # XXX: Blacklist should be checked here instead of saving a
  3689. # host we're just going to throw away later
  3690. hosts.push(value)
  3691. when :vuln
  3692. value["id"] = value["id"].downcase if value["id"]
  3693. vulns.push(value)
  3694. end
  3695. }
  3696. REXML::Document.parse_stream(data, parser)
  3697. vuln_refs = nexpose_refs_to_struct(vulns)
  3698. hosts.each do |host|
  3699. if bl.include? host["addr"]
  3700. next
  3701. else
  3702. yield(:address,host["addr"]) if block
  3703. end
  3704. nexpose_host_from_rawxml(host, vuln_refs, wspace)
  3705. end
  3706. end
  3707. #
  3708. # Takes an array of vuln hashes, as returned by the NeXpose rawxml stream
  3709. # parser, like:
  3710. # [
  3711. # {"id"=>"winreg-notes-protocol-handler", severity="8", "refs"=>[{"source"=>"BID", "value"=>"10600"}, ...]}
  3712. # {"id"=>"windows-zotob-c", severity="8", "refs"=>[{"source"=>"BID", "value"=>"14513"}, ...]}
  3713. # ]
  3714. # and transforms it into a struct, containing :id, :refs, :title, and :severity
  3715. #
  3716. # Other attributes can be added later, as needed.
  3717. def nexpose_refs_to_struct(vulns)
  3718. ret = []
  3719. vulns.each do |vuln|
  3720. next if ret.map {|v| v.id}.include? vuln["id"]
  3721. vstruct = Struct.new(:id, :refs, :title, :severity).new
  3722. vstruct.id = vuln["id"]
  3723. vstruct.title = vuln["title"]
  3724. vstruct.severity = vuln["severity"]
  3725. vstruct.refs = []
  3726. vuln["refs"].each do |ref|
  3727. if ref['source'] == 'BID'
  3728. vstruct.refs.push('BID-' + ref["value"])
  3729. elsif ref['source'] == 'CVE'
  3730. # value is CVE-$ID
  3731. vstruct.refs.push(ref["value"])
  3732. elsif ref['source'] == 'MS'
  3733. vstruct.refs.push('MSB-' + ref["value"])
  3734. elsif ref['source'] == 'URL'
  3735. vstruct.refs.push('URL-' + ref["value"])
  3736. end
  3737. end
  3738. ret.push vstruct
  3739. end
  3740. return ret
  3741. end
  3742. # Takes a Host object, an array of vuln structs (generated by nexpose_refs_to_struct()),
  3743. # and a workspace, and reports the vulns on that host.
  3744. def nexpose_host_from_rawxml(h, vstructs, wspace)
  3745. hobj = nil
  3746. data = {:workspace => wspace}
  3747. if h["addr"]
  3748. addr = h["addr"]
  3749. else
  3750. # Can't report it if it doesn't have an IP
  3751. return
  3752. end
  3753. data[:host] = addr
  3754. if (h["hardware-address"])
  3755. # Put colons between each octet of the MAC address
  3756. data[:mac] = h["hardware-address"].gsub(':', '').scan(/../).join(':')
  3757. end
  3758. data[:state] = (h["status"] == "alive") ? Msf::HostState::Alive : Msf::HostState::Dead
  3759. # Since we only have one name field per host in the database, just
  3760. # take the first one.
  3761. if (h["names"] and h["names"].first)
  3762. data[:name] = h["names"].first
  3763. end
  3764. if (data[:state] != Msf::HostState::Dead)
  3765. hobj = report_host(data)
  3766. report_import_note(wspace, hobj)
  3767. end
  3768. if h["notes"]
  3769. note = {
  3770. :workspace => wspace,
  3771. :host => (hobj || addr),
  3772. :type => "host.vuln.nexpose_keys",
  3773. :data => {},
  3774. :mode => :unique_data
  3775. }
  3776. h["notes"].each do |v,k|
  3777. note[:data][v] ||= []
  3778. next if note[:data][v].include? k
  3779. note[:data][v] << k
  3780. end
  3781. report_note(note)
  3782. end
  3783. if h["os_family"]
  3784. note = {
  3785. :workspace => wspace,
  3786. :host => hobj || addr,
  3787. :type => 'host.os.nexpose_fingerprint',
  3788. :data => {
  3789. :family => h["os_family"],
  3790. :certainty => h["os_certainty"]
  3791. }
  3792. }
  3793. note[:data][:vendor] = h["os_vendor"] if h["os_vendor"]
  3794. note[:data][:product] = h["os_product"] if h["os_product"]
  3795. note[:data][:version] = h["os_version"] if h["os_version"]
  3796. note[:data][:arch] = h["arch"] if h["arch"]
  3797. report_note(note)
  3798. end
  3799. h["endpoints"].each { |p|
  3800. extra = ""
  3801. extra << p["product"] + " " if p["product"]
  3802. extra << p["version"] + " " if p["version"]
  3803. # Skip port-0 endpoints
  3804. next if p["port"].to_i == 0
  3805. # XXX This should probably be handled in a more standard way
  3806. # extra << "(" + p["certainty"] + " certainty) " if p["certainty"]
  3807. data = {}
  3808. data[:workspace] = wspace
  3809. data[:proto] = p["protocol"].downcase
  3810. data[:port] = p["port"].to_i
  3811. data[:state] = p["status"]
  3812. data[:host] = hobj || addr
  3813. data[:info] = extra if not extra.empty?
  3814. if p["name"] != "<unknown>"
  3815. data[:name] = p["name"]
  3816. end
  3817. report_service(data)
  3818. }
  3819. h["vulns"].each_pair { |k,v|
  3820. next if v["status"] !~ /^vulnerable/
  3821. vstruct = vstructs.select {|vs| vs.id.to_s.downcase == v["id"].to_s.downcase}.first
  3822. next unless vstruct
  3823. data = {}
  3824. data[:workspace] = wspace
  3825. data[:host] = hobj || addr
  3826. data[:proto] = v["protocol"].downcase if v["protocol"]
  3827. data[:port] = v["port"].to_i if v["port"]
  3828. data[:name] = "NEXPOSE-" + v["id"]
  3829. data[:info] = vstruct.title
  3830. data[:refs] = vstruct.refs
  3831. report_vuln(data)
  3832. }
  3833. end
  3834. #
  3835. # Retina XML
  3836. #
  3837. # Process a Retina XML file
  3838. def import_retina_xml_file(args={})
  3839. filename = args[:filename]
  3840. wspace = args[:wspace] || workspace
  3841. data = ""
  3842. ::File.open(filename, 'rb') do |f|
  3843. data = f.read(f.stat.size)
  3844. end
  3845. import_retina_xml(args.merge(:data => data))
  3846. end
  3847. # Process Retina XML
  3848. def import_retina_xml(args={}, &block)
  3849. data = args[:data]
  3850. wspace = args[:wspace] || workspace
  3851. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3852. msg = "Warning: The Retina XML format does not associate vulnerabilities with the\n"
  3853. msg << "specific service on which they were found.\n"
  3854. msg << "This makes it impossible to correlate exploits to discovered vulnerabilities\n"
  3855. msg << "in a reliable fashion."
  3856. yield(:warning,msg) if block
  3857. parser = Rex::Parser::RetinaXMLStreamParser.new
  3858. parser.on_found_host = Proc.new do |host|
  3859. hobj = nil
  3860. data = {:workspace => wspace}
  3861. addr = host['address']
  3862. next if not addr
  3863. next if bl.include? addr
  3864. data[:host] = addr
  3865. if host['mac']
  3866. data[:mac] = host['mac']
  3867. end
  3868. data[:state] = Msf::HostState::Alive
  3869. if host['hostname']
  3870. data[:name] = host['hostname']
  3871. end
  3872. if host['netbios']
  3873. data[:name] = host['netbios']
  3874. end
  3875. yield(:address, data[:host]) if block
  3876. # Import Host
  3877. hobj = report_host(data)
  3878. report_import_note(wspace, hobj)
  3879. # Import OS fingerprint
  3880. if host["os"]
  3881. note = {
  3882. :workspace => wspace,
  3883. :host => addr,
  3884. :type => 'host.os.retina_fingerprint',
  3885. :data => {
  3886. :os => host["os"]
  3887. }
  3888. }
  3889. report_note(note)
  3890. end
  3891. # Import vulnerabilities
  3892. host['vulns'].each do |vuln|
  3893. refs = vuln['refs'].map{|v| v.join("-")}
  3894. refs << "RETINA-#{vuln['rthid']}" if vuln['rthid']
  3895. vuln_info = {
  3896. :workspace => wspace,
  3897. :host => addr,
  3898. :name => vuln['name'],
  3899. :info => vuln['description'],
  3900. :refs => refs
  3901. }
  3902. report_vuln(vuln_info)
  3903. end
  3904. end
  3905. REXML::Document.parse_stream(data, parser)
  3906. end
  3907. #
  3908. # NetSparker XML
  3909. #
  3910. # Process a NetSparker XML file
  3911. def import_netsparker_xml_file(args={})
  3912. filename = args[:filename]
  3913. wspace = args[:wspace] || workspace
  3914. data = ""
  3915. ::File.open(filename, 'rb') do |f|
  3916. data = f.read(f.stat.size)
  3917. end
  3918. import_netsparker_xml(args.merge(:data => data))
  3919. end
  3920. # Process NetSparker XML
  3921. def import_netsparker_xml(args={}, &block)
  3922. data = args[:data]
  3923. wspace = args[:wspace] || workspace
  3924. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3925. addr = nil
  3926. parser = Rex::Parser::NetSparkerXMLStreamParser.new
  3927. parser.on_found_vuln = Proc.new do |vuln|
  3928. data = {:workspace => wspace}
  3929. # Parse the URL
  3930. url = vuln['url']
  3931. return if not url
  3932. # Crack the URL into a URI
  3933. uri = URI(url) rescue nil
  3934. return if not uri
  3935. # Resolve the host and cache the IP
  3936. if not addr
  3937. baddr = Rex::Socket.addr_aton(uri.host) rescue nil
  3938. if baddr
  3939. addr = Rex::Socket.addr_ntoa(baddr)
  3940. yield(:address, addr) if block
  3941. end
  3942. end
  3943. # Bail early if we have no IP address
  3944. if not addr
  3945. raise Interrupt, "Not a valid IP address"
  3946. end
  3947. if bl.include?(addr)
  3948. raise Interrupt, "IP address is on the blacklist"
  3949. end
  3950. data[:host] = addr
  3951. data[:vhost] = uri.host
  3952. data[:port] = uri.port
  3953. data[:ssl] = (uri.scheme == "ssl")
  3954. body = nil
  3955. # First report a web page
  3956. if vuln['response']
  3957. headers = {}
  3958. code = 200
  3959. head,body = vuln['response'].to_s.split(/\r?\n\r?\n/, 2)
  3960. if body
  3961. if head =~ /^HTTP\d+\.\d+\s+(\d+)\s*/
  3962. code = $1.to_i
  3963. end
  3964. headers = {}
  3965. head.split(/\r?\n/).each do |line|
  3966. hname,hval = line.strip.split(/\s*:\s*/, 2)
  3967. next if hval.to_s.strip.empty?
  3968. headers[hname.downcase] ||= []
  3969. headers[hname.downcase] << hval
  3970. end
  3971. info = {
  3972. :path => uri.path,
  3973. :query => uri.query,
  3974. :code => code,
  3975. :body => body,
  3976. :headers => headers
  3977. }
  3978. info.merge!(data)
  3979. if headers['content-type']
  3980. info[:ctype] = headers['content-type'][0]
  3981. end
  3982. if headers['set-cookie']
  3983. info[:cookie] = headers['set-cookie'].join("\n")
  3984. end
  3985. if headers['authorization']
  3986. info[:auth] = headers['authorization'].join("\n")
  3987. end
  3988. if headers['location']
  3989. info[:location] = headers['location'][0]
  3990. end
  3991. if headers['last-modified']
  3992. info[:mtime] = headers['last-modified'][0]
  3993. end
  3994. # Report the web page to the database
  3995. report_web_page(info)
  3996. yield(:web_page, url) if block
  3997. end
  3998. end # End web_page reporting
  3999. details = netsparker_vulnerability_map(vuln)
  4000. method = netsparker_method_map(vuln)
  4001. pname = netsparker_pname_map(vuln)
  4002. params = netsparker_params_map(vuln)
  4003. proof = ''
  4004. if vuln['info'] and vuln['info'].length > 0
  4005. proof << vuln['info'].map{|x| "#{x[0]}: #{x[1]}\n" }.join + "\n"
  4006. end
  4007. if proof.empty?
  4008. if body
  4009. proof << body + "\n"
  4010. else
  4011. proof << vuln['response'].to_s + "\n"
  4012. end
  4013. end
  4014. if params.empty? and pname
  4015. params = [[pname, vuln['vparam_name'].to_s]]
  4016. end
  4017. info = {
  4018. # XXX: There is a :request attr in the model, but report_web_vuln
  4019. # doesn't seem to know about it, so this gets ignored.
  4020. #:request => vuln['request'],
  4021. :path => uri.path,
  4022. :query => uri.query,
  4023. :method => method,
  4024. :params => params,
  4025. :pname => pname.to_s,
  4026. :proof => proof,
  4027. :risk => details[:risk],
  4028. :name => details[:name],
  4029. :blame => details[:blame],
  4030. :category => details[:category],
  4031. :description => details[:description],
  4032. :confidence => details[:confidence],
  4033. }
  4034. info.merge!(data)
  4035. next if vuln['type'].to_s.empty?
  4036. report_web_vuln(info)
  4037. yield(:web_vuln, url) if block
  4038. end
  4039. # We throw interrupts in our parser when the job is hopeless
  4040. begin
  4041. REXML::Document.parse_stream(data, parser)
  4042. rescue ::Interrupt => e
  4043. wlog("The netsparker_xml_import() job was interrupted: #{e}")
  4044. end
  4045. end
  4046. def netsparker_method_map(vuln)
  4047. case vuln['vparam_type']
  4048. when "FullQueryString"
  4049. "GET"
  4050. when "Querystring"
  4051. "GET"
  4052. when "Post"
  4053. "POST"
  4054. when "RawUrlInjection"
  4055. "GET"
  4056. else
  4057. "GET"
  4058. end
  4059. end
  4060. def netsparker_pname_map(vuln)
  4061. case vuln['vparam_name']
  4062. when "URI-BASED", "Query Based"
  4063. "PATH"
  4064. else
  4065. vuln['vparam_name']
  4066. end
  4067. end
  4068. def netsparker_params_map(vuln)
  4069. []
  4070. end
  4071. def netsparker_vulnerability_map(vuln)
  4072. res = {
  4073. :risk => 1,
  4074. :name => 'Information Disclosure',
  4075. :blame => 'System Administrator',
  4076. :category => 'info',
  4077. :description => "This is an information leak",
  4078. :confidence => 100
  4079. }
  4080. # Risk is a value from 1-5 indicating the severity of the issue
  4081. # Examples: 1, 4, 5
  4082. # Name is a descriptive name for this vulnerability.
  4083. # Examples: XSS, ReflectiveXSS, PersistentXSS
  4084. # Blame indicates who is at fault for the vulnerability
  4085. # Examples: App Developer, Server Developer, System Administrator
  4086. # Category indicates the general class of vulnerability
  4087. # Examples: info, xss, sql, rfi, lfi, cmd
  4088. # Description is a textual summary of the vulnerability
  4089. # Examples: "A reflective cross-site scripting attack"
  4090. # "The web server leaks the internal IP address"
  4091. # "The cookie is not set to HTTP-only"
  4092. #
  4093. # Confidence is a value from 1 to 100 indicating how confident the
  4094. # software is that the results are valid.
  4095. # Examples: 100, 90, 75, 15, 10, 0
  4096. case vuln['type'].to_s
  4097. when "ApacheDirectoryListing"
  4098. res = {
  4099. :risk => 1,
  4100. :name => 'Directory Listing',
  4101. :blame => 'System Administrator',
  4102. :category => 'info',
  4103. :description => "",
  4104. :confidence => 100
  4105. }
  4106. when "ApacheMultiViewsEnabled"
  4107. res = {
  4108. :risk => 1,
  4109. :name => 'Apache MultiViews Enabled',
  4110. :blame => 'System Administrator',
  4111. :category => 'info',
  4112. :description => "",
  4113. :confidence => 100
  4114. }
  4115. when "ApacheVersion"
  4116. res = {
  4117. :risk => 1,
  4118. :name => 'Web Server Version',
  4119. :blame => 'System Administrator',
  4120. :category => 'info',
  4121. :description => "",
  4122. :confidence => 100
  4123. }
  4124. when "PHPVersion"
  4125. res = {
  4126. :risk => 1,
  4127. :name => 'PHP Module Version',
  4128. :blame => 'System Administrator',
  4129. :category => 'info',
  4130. :description => "",
  4131. :confidence => 100
  4132. }
  4133. when "AutoCompleteEnabled"
  4134. res = {
  4135. :risk => 1,
  4136. :name => 'Form AutoComplete Enabled',
  4137. :blame => 'App Developer',
  4138. :category => 'info',
  4139. :description => "",
  4140. :confidence => 100
  4141. }
  4142. when "CookieNotMarkedAsHttpOnly"
  4143. res = {
  4144. :risk => 1,
  4145. :name => 'Cookie Not HttpOnly',
  4146. :blame => 'App Developer',
  4147. :category => 'info',
  4148. :description => "",
  4149. :confidence => 100
  4150. }
  4151. when "EmailDisclosure"
  4152. res = {
  4153. :risk => 1,
  4154. :name => 'Email Address Disclosure',
  4155. :blame => 'App Developer',
  4156. :category => 'info',
  4157. :description => "",
  4158. :confidence => 100
  4159. }
  4160. when "ForbiddenResource"
  4161. res = {
  4162. :risk => 1,
  4163. :name => 'Forbidden Resource',
  4164. :blame => 'App Developer',
  4165. :category => 'info',
  4166. :description => "",
  4167. :confidence => 100
  4168. }
  4169. when "FileUploadFound"
  4170. res = {
  4171. :risk => 1,
  4172. :name => 'File Upload Form',
  4173. :blame => 'App Developer',
  4174. :category => 'info',
  4175. :description => "",
  4176. :confidence => 100
  4177. }
  4178. when "PasswordOverHTTP"
  4179. res = {
  4180. :risk => 2,
  4181. :name => 'Password Over HTTP',
  4182. :blame => 'App Developer',
  4183. :category => 'info',
  4184. :description => "",
  4185. :confidence => 100
  4186. }
  4187. when "MySQL5Identified"
  4188. res = {
  4189. :risk => 1,
  4190. :name => 'MySQL 5 Identified',
  4191. :blame => 'App Developer',
  4192. :category => 'info',
  4193. :description => "",
  4194. :confidence => 100
  4195. }
  4196. when "PossibleInternalWindowsPathLeakage"
  4197. res = {
  4198. :risk => 1,
  4199. :name => 'Path Leakage - Windows',
  4200. :blame => 'App Developer',
  4201. :category => 'info',
  4202. :description => "",
  4203. :confidence => 100
  4204. }
  4205. when "PossibleInternalUnixPathLeakage"
  4206. res = {
  4207. :risk => 1,
  4208. :name => 'Path Leakage - Unix',
  4209. :blame => 'App Developer',
  4210. :category => 'info',
  4211. :description => "",
  4212. :confidence => 100
  4213. }
  4214. when "PossibleXSS", "LowPossibilityPermanentXSS", "XSS", "PermanentXSS"
  4215. conf = 100
  4216. conf = 25 if vuln['type'].to_s == "LowPossibilityPermanentXSS"
  4217. conf = 50 if vuln['type'].to_s == "PossibleXSS"
  4218. res = {
  4219. :risk => 3,
  4220. :name => 'Cross-Site Scripting',
  4221. :blame => 'App Developer',
  4222. :category => 'xss',
  4223. :description => "",
  4224. :confidence => conf
  4225. }
  4226. when "ConfirmedBlindSQLInjection", "ConfirmedSQLInjection", "HighlyPossibleSqlInjection", "DatabaseErrorMessages"
  4227. conf = 100
  4228. conf = 90 if vuln['type'].to_s == "HighlyPossibleSqlInjection"
  4229. conf = 25 if vuln['type'].to_s == "DatabaseErrorMessages"
  4230. res = {
  4231. :risk => 5,
  4232. :name => 'SQL Injection',
  4233. :blame => 'App Developer',
  4234. :category => 'sql',
  4235. :description => "",
  4236. :confidence => conf
  4237. }
  4238. else
  4239. conf = 100
  4240. res = {
  4241. :risk => 1,
  4242. :name => vuln['type'].to_s,
  4243. :blame => 'App Developer',
  4244. :category => 'info',
  4245. :description => "",
  4246. :confidence => conf
  4247. }
  4248. end
  4249. res
  4250. end
  4251. def import_fusionvm_xml(args={})
  4252. args[:wspace] ||= workspace
  4253. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4254. doc = Rex::Parser::FusionVMDocument.new(args,self)
  4255. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  4256. parser.parse(args[:data])
  4257. end
  4258. #
  4259. # Import Nmap's -oX xml output
  4260. #
  4261. def import_nmap_xml_file(args={})
  4262. filename = args[:filename]
  4263. wspace = args[:wspace] || workspace
  4264. data = ""
  4265. ::File.open(filename, 'rb') do |f|
  4266. data = f.read(f.stat.size)
  4267. end
  4268. import_nmap_xml(args.merge(:data => data))
  4269. end
  4270. def import_nexpose_raw_noko_stream(args, &block)
  4271. if block
  4272. doc = Rex::Parser::NexposeRawDocument.new(args,framework.db) {|type, data| yield type,data }
  4273. else
  4274. doc = Rex::Parser::NexposeRawDocument.new(args,self)
  4275. end
  4276. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  4277. parser.parse(args[:data])
  4278. end
  4279. def import_nexpose_noko_stream(args, &block)
  4280. if block
  4281. doc = Rex::Parser::NexposeSimpleDocument.new(args,framework.db) {|type, data| yield type,data }
  4282. else
  4283. doc = Rex::Parser::NexposeSimpleDocument.new(args,self)
  4284. end
  4285. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  4286. parser.parse(args[:data])
  4287. end
  4288. def import_nmap_noko_stream(args, &block)
  4289. if block
  4290. doc = Rex::Parser::NmapDocument.new(args,framework.db) {|type, data| yield type,data }
  4291. else
  4292. doc = Rex::Parser::NmapDocument.new(args,self)
  4293. end
  4294. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  4295. parser.parse(args[:data])
  4296. end
  4297. # If you have Nokogiri installed, you'll be shunted over to
  4298. # that. Otherwise, you'll hit the old NmapXMLStreamParser.
  4299. def import_nmap_xml(args={}, &block)
  4300. return nil if args[:data].nil? or args[:data].empty?
  4301. wspace = args[:wspace] || workspace
  4302. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4303. if Rex::Parser.nokogiri_loaded
  4304. noko_args = args.dup
  4305. noko_args[:blacklist] = bl
  4306. noko_args[:wspace] = wspace
  4307. if block
  4308. yield(:parser, "Nokogiri v#{::Nokogiri::VERSION}")
  4309. import_nmap_noko_stream(noko_args) {|type, data| yield type,data }
  4310. else
  4311. import_nmap_noko_stream(noko_args)
  4312. end
  4313. return true
  4314. end
  4315. # XXX: Legacy nmap xml parser starts here.
  4316. fix_services = args[:fix_services]
  4317. data = args[:data]
  4318. # Use a stream parser instead of a tree parser so we can deal with
  4319. # huge results files without running out of memory.
  4320. parser = Rex::Parser::NmapXMLStreamParser.new
  4321. yield(:parser, parser.class.name) if block
  4322. # Whenever the parser pulls a host out of the nmap results, store
  4323. # it, along with any associated services, in the database.
  4324. parser.on_found_host = Proc.new { |h|
  4325. hobj = nil
  4326. data = {:workspace => wspace}
  4327. if (h["addrs"].has_key?("ipv4"))
  4328. addr = h["addrs"]["ipv4"]
  4329. elsif (h["addrs"].has_key?("ipv6"))
  4330. addr = h["addrs"]["ipv6"]
  4331. else
  4332. # Can't report it if it doesn't have an IP
  4333. raise RuntimeError, "At least one IPv4 or IPv6 address is required"
  4334. end
  4335. next if bl.include? addr
  4336. data[:host] = addr
  4337. if (h["addrs"].has_key?("mac"))
  4338. data[:mac] = h["addrs"]["mac"]
  4339. end
  4340. data[:state] = (h["status"] == "up") ? Msf::HostState::Alive : Msf::HostState::Dead
  4341. if ( h["reverse_dns"] )
  4342. data[:name] = h["reverse_dns"]
  4343. end
  4344. # Only report alive hosts with ports to speak of.
  4345. if(data[:state] != Msf::HostState::Dead)
  4346. if h["ports"].size > 0
  4347. if fix_services
  4348. port_states = h["ports"].map {|p| p["state"]}.reject {|p| p == "filtered"}
  4349. next if port_states.compact.empty?
  4350. end
  4351. yield(:address,data[:host]) if block
  4352. hobj = report_host(data)
  4353. report_import_note(wspace,hobj)
  4354. end
  4355. end
  4356. if( h["os_vendor"] )
  4357. note = {
  4358. :workspace => wspace,
  4359. :host => hobj || addr,
  4360. :type => 'host.os.nmap_fingerprint',
  4361. :data => {
  4362. :os_vendor => h["os_vendor"],
  4363. :os_family => h["os_family"],
  4364. :os_version => h["os_version"],
  4365. :os_accuracy => h["os_accuracy"]
  4366. }
  4367. }
  4368. if(h["os_match"])
  4369. note[:data][:os_match] = h['os_match']
  4370. end
  4371. report_note(note)
  4372. end
  4373. if (h["last_boot"])
  4374. report_note(
  4375. :workspace => wspace,
  4376. :host => hobj || addr,
  4377. :type => 'host.last_boot',
  4378. :data => {
  4379. :time => h["last_boot"]
  4380. }
  4381. )
  4382. end
  4383. if (h["trace"])
  4384. hops = []
  4385. h["trace"]["hops"].each do |hop|
  4386. hops << {
  4387. "ttl" => hop["ttl"].to_i,
  4388. "address" => hop["ipaddr"].to_s,
  4389. "rtt" => hop["rtt"].to_f,
  4390. "name" => hop["host"].to_s
  4391. }
  4392. end
  4393. report_note(
  4394. :workspace => wspace,
  4395. :host => hobj || addr,
  4396. :type => 'host.nmap.traceroute',
  4397. :data => {
  4398. 'port' => h["trace"]["port"].to_i,
  4399. 'proto' => h["trace"]["proto"].to_s,
  4400. 'hops' => hops
  4401. }
  4402. )
  4403. end
  4404. # Put all the ports, regardless of state, into the db.
  4405. h["ports"].each { |p|
  4406. # Localhost port results are pretty unreliable -- if it's
  4407. # unknown, it's no good (possibly Windows-only)
  4408. if (
  4409. p["state"] == "unknown" &&
  4410. h["status_reason"] == "localhost-response"
  4411. )
  4412. next
  4413. end
  4414. extra = ""
  4415. extra << p["product"] + " " if p["product"]
  4416. extra << p["version"] + " " if p["version"]
  4417. extra << p["extrainfo"] + " " if p["extrainfo"]
  4418. data = {}
  4419. data[:workspace] = wspace
  4420. if fix_services
  4421. data[:proto] = nmap_msf_service_map(p["protocol"])
  4422. else
  4423. data[:proto] = p["protocol"].downcase
  4424. end
  4425. data[:port] = p["portid"].to_i
  4426. data[:state] = p["state"]
  4427. data[:host] = hobj || addr
  4428. data[:info] = extra if not extra.empty?
  4429. if p["name"] != "unknown"
  4430. data[:name] = p["name"]
  4431. end
  4432. report_service(data)
  4433. }
  4434. #Parse the scripts output
  4435. if h["scripts"]
  4436. h["scripts"].each do |key,val|
  4437. if key == "smb-check-vulns"
  4438. if val =~ /MS08-067: VULNERABLE/
  4439. vuln_info = {
  4440. :workspace => wspace,
  4441. :host => hobj || addr,
  4442. :port => 445,
  4443. :proto => 'tcp',
  4444. :name => 'MS08-067',
  4445. :info => 'Microsoft Windows Server Service Crafted RPC Request Handling Unspecified Remote Code Execution',
  4446. :refs =>['CVE-2008-4250',
  4447. 'BID-31874',
  4448. 'OSVDB-49243',
  4449. 'CWE-94',
  4450. 'MSFT-MS08-067',
  4451. 'MSF-Microsoft Server Service Relative Path Stack Corruption',
  4452. 'NSS-34476']
  4453. }
  4454. report_vuln(vuln_info)
  4455. end
  4456. if val =~ /MS06-025: VULNERABLE/
  4457. vuln_info = {
  4458. :workspace => wspace,
  4459. :host => hobj || addr,
  4460. :port => 445,
  4461. :proto => 'tcp',
  4462. :name => 'MS06-025',
  4463. :info => 'Vulnerability in Routing and Remote Access Could Allow Remote Code Execution',
  4464. :refs =>['CVE-2006-2370',
  4465. 'CVE-2006-2371',
  4466. 'BID-18325',
  4467. 'BID-18358',
  4468. 'BID-18424',
  4469. 'OSVDB-26436',
  4470. 'OSVDB-26437',
  4471. 'MSFT-MS06-025',
  4472. 'MSF-Microsoft RRAS Service RASMAN Registry Overflow',
  4473. 'NSS-21689']
  4474. }
  4475. report_vuln(vuln_info)
  4476. end
  4477. # This one has NOT been Tested , remove this comment if confirmed working
  4478. if val =~ /MS07-029: VULNERABLE/
  4479. vuln_info = {
  4480. :workspace => wspace,
  4481. :host => hobj || addr,
  4482. :port => 445,
  4483. :proto => 'tcp',
  4484. :name => 'MS07-029',
  4485. :info => 'Vulnerability in Windows DNS RPC Interface Could Allow Remote Code Execution',
  4486. # Add more refs based on nessus/nexpose .. results
  4487. :refs =>['CVE-2007-1748',
  4488. 'OSVDB-34100',
  4489. 'MSF-Microsoft DNS RPC Service extractQuotedChar()',
  4490. 'NSS-25168']
  4491. }
  4492. report_vuln(vuln_info)
  4493. end
  4494. end
  4495. end
  4496. end
  4497. }
  4498. # XXX: Legacy nmap xml parser ends here.
  4499. REXML::Document.parse_stream(data, parser)
  4500. end
  4501. def nmap_msf_service_map(proto)
  4502. service_name_map(proto)
  4503. end
  4504. #
  4505. # This method normalizes an incoming service name to one of the
  4506. # the standard ones recognized by metasploit
  4507. #
  4508. def service_name_map(proto)
  4509. return proto unless proto.kind_of? String
  4510. case proto.downcase
  4511. when "msrpc", "nfs-or-iis", "dce endpoint resolution"
  4512. "dcerpc"
  4513. when "ms-sql-s", "tds"
  4514. "mssql"
  4515. when "ms-sql-m","microsoft sql monitor"
  4516. "mssql-m"
  4517. when "postgresql"; "postgres"
  4518. when "http-proxy"; "http"
  4519. when "iiimsf"; "db2"
  4520. when "oracle-tns"; "oracle"
  4521. when "quickbooksrds"; "metasploit"
  4522. when "microsoft remote display protocol"
  4523. "rdp"
  4524. when "vmware authentication daemon"
  4525. "vmauthd"
  4526. when "netbios-ns", "cifs name service"
  4527. "netbios"
  4528. when "netbios-ssn", "microsoft-ds", "cifs"
  4529. "smb"
  4530. when "remote shell"
  4531. "shell"
  4532. when "remote login"
  4533. "login"
  4534. when "nfs lockd"
  4535. "lockd"
  4536. when "hp jetdirect"
  4537. "jetdirect"
  4538. when "dhcp server"
  4539. "dhcp"
  4540. when /^dns-(udp|tcp)$/; "dns"
  4541. when /^dce[\s+]rpc$/; "dcerpc"
  4542. else
  4543. proto.downcase.gsub(/\s*\(.*/, '') # "service (some service)"
  4544. end
  4545. end
  4546. def report_import_note(wspace,addr)
  4547. if @import_filedata.kind_of?(Hash) && @import_filedata[:filename] && @import_filedata[:filename] !~ /msfe-nmap[0-9]{8}/
  4548. report_note(
  4549. :workspace => wspace,
  4550. :host => addr,
  4551. :type => 'host.imported',
  4552. :data => @import_filedata.merge(:time=> Time.now.utc)
  4553. )
  4554. end
  4555. end
  4556. #
  4557. # Import Nessus NBE files
  4558. #
  4559. def import_nessus_nbe_file(args={})
  4560. filename = args[:filename]
  4561. wspace = args[:wspace] || workspace
  4562. data = ""
  4563. ::File.open(filename, 'rb') do |f|
  4564. data = f.read(f.stat.size)
  4565. end
  4566. import_nessus_nbe(args.merge(:data => data))
  4567. end
  4568. # There is no place the NBE actually stores the plugin name used to
  4569. # scan. You get "Security Note" or "Security Warning," and that's it.
  4570. def import_nessus_nbe(args={}, &block)
  4571. data = args[:data]
  4572. wspace = args[:wspace] || workspace
  4573. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4574. nbe_copy = data.dup
  4575. # First pass, just to build the address map.
  4576. addr_map = {}
  4577. # Cache host objects before passing into handle_nessus()
  4578. hobj_map = {}
  4579. nbe_copy.each_line do |line|
  4580. r = line.split('|')
  4581. next if r[0] != 'results'
  4582. next if r[4] != "12053"
  4583. data = r[6]
  4584. addr,hname = data.match(/([0-9\x2e]+) resolves as (.+)\x2e\\n/)[1,2]
  4585. addr_map[hname] = addr
  4586. end
  4587. data.each_line do |line|
  4588. r = line.split('|')
  4589. next if r[0] != 'results'
  4590. hname = r[2]
  4591. if addr_map[hname]
  4592. addr = addr_map[hname]
  4593. else
  4594. addr = hname # Must be unresolved, probably an IP address.
  4595. end
  4596. port = r[3]
  4597. nasl = r[4]
  4598. type = r[5]
  4599. data = r[6]
  4600. # If there's no resolution, or if it's malformed, skip it.
  4601. next unless ipv46_validator(addr)
  4602. if bl.include? addr
  4603. next
  4604. else
  4605. yield(:address,addr) if block
  4606. end
  4607. hobj_map[ addr ] ||= report_host(:host => addr, :workspace => wspace)
  4608. # Match the NBE types with the XML severity ratings
  4609. case type
  4610. # log messages don't actually have any data, they are just
  4611. # complaints about not being able to perform this or that test
  4612. # because such-and-such was missing
  4613. when "Log Message"; next
  4614. when "Security Hole"; severity = 3
  4615. when "Security Warning"; severity = 2
  4616. when "Security Note"; severity = 1
  4617. # a severity 0 means there's no extra data, it's just an open port
  4618. else; severity = 0
  4619. end
  4620. if nasl == "11936"
  4621. os = data.match(/The remote host is running (.*)\\n/)[1]
  4622. report_note(
  4623. :workspace => wspace,
  4624. :host => hobj_map[ addr ],
  4625. :type => 'host.os.nessus_fingerprint',
  4626. :data => {
  4627. :os => os.to_s.strip
  4628. }
  4629. )
  4630. end
  4631. next if nasl.to_s.strip.empty?
  4632. plugin_name = nil # NBE doesn't ever populate this
  4633. handle_nessus(wspace, hobj_map[ addr ], port, nasl, plugin_name, severity, data)
  4634. end
  4635. end
  4636. #
  4637. # Of course they had to change the nessus format.
  4638. #
  4639. def import_openvas_xml(args={}, &block)
  4640. filename = args[:filename]
  4641. wspace = args[:wspace] || workspace
  4642. raise DBImportError.new("No OpenVAS XML support. Please submit a patch to msfdev[at]metasploit.com")
  4643. end
  4644. #
  4645. # Import IP360 XML v3 output
  4646. #
  4647. def import_ip360_xml_file(args={})
  4648. filename = args[:filename]
  4649. wspace = args[:wspace] || workspace
  4650. data = ""
  4651. ::File.open(filename, 'rb') do |f|
  4652. data = f.read(f.stat.size)
  4653. end
  4654. import_ip360_xml_v3(args.merge(:data => data))
  4655. end
  4656. #
  4657. # Import Nessus XML v1 and v2 output
  4658. #
  4659. # Old versions of openvas exported this as well
  4660. #
  4661. def import_nessus_xml_file(args={})
  4662. filename = args[:filename]
  4663. wspace = args[:wspace] || workspace
  4664. data = ""
  4665. ::File.open(filename, 'rb') do |f|
  4666. data = f.read(f.stat.size)
  4667. end
  4668. if data.index("NessusClientData_v2")
  4669. import_nessus_xml_v2(args.merge(:data => data))
  4670. else
  4671. import_nessus_xml(args.merge(:data => data))
  4672. end
  4673. end
  4674. def import_nessus_xml(args={}, &block)
  4675. data = args[:data]
  4676. wspace = args[:wspace] || workspace
  4677. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4678. doc = rexmlify(data)
  4679. doc.elements.each('/NessusClientData/Report/ReportHost') do |host|
  4680. hobj = nil
  4681. addr = nil
  4682. hname = nil
  4683. os = nil
  4684. # If the name is resolved, the Nessus plugin for DNS
  4685. # resolution should be there. If not, fall back to the
  4686. # HostName
  4687. host.elements.each('ReportItem') do |item|
  4688. next unless item.elements['pluginID'].text == "12053"
  4689. addr = item.elements['data'].text.match(/([0-9\x2e]+) resolves as/)[1]
  4690. hname = host.elements['HostName'].text
  4691. end
  4692. addr ||= host.elements['HostName'].text
  4693. next unless ipv46_validator(addr) # Skip resolved names and SCAN-ERROR.
  4694. if bl.include? addr
  4695. next
  4696. else
  4697. yield(:address,addr) if block
  4698. end
  4699. hinfo = {
  4700. :workspace => wspace,
  4701. :host => addr
  4702. }
  4703. # Record the hostname
  4704. hinfo.merge!(:name => hname.to_s.strip) if hname
  4705. hobj = report_host(hinfo)
  4706. report_import_note(wspace,hobj)
  4707. # Record the OS
  4708. os ||= host.elements["os_name"]
  4709. if os
  4710. report_note(
  4711. :workspace => wspace,
  4712. :host => hobj,
  4713. :type => 'host.os.nessus_fingerprint',
  4714. :data => {
  4715. :os => os.text.to_s.strip
  4716. }
  4717. )
  4718. end
  4719. host.elements.each('ReportItem') do |item|
  4720. nasl = item.elements['pluginID'].text
  4721. plugin_name = item.elements['pluginName'].text
  4722. port = item.elements['port'].text
  4723. data = item.elements['data'].text
  4724. severity = item.elements['severity'].text
  4725. handle_nessus(wspace, hobj, port, nasl, plugin_name, severity, data)
  4726. end
  4727. end
  4728. end
  4729. def import_nessus_xml_v2(args={}, &block)
  4730. data = args[:data]
  4731. wspace = args[:wspace] || workspace
  4732. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4733. #@host = {
  4734. #'hname' => nil,
  4735. #'addr' => nil,
  4736. #'mac' => nil,
  4737. #'os' => nil,
  4738. #'ports' => [ 'port' => { 'port' => nil,
  4739. # 'svc_name' => nil,
  4740. # 'proto' => nil,
  4741. # 'severity' => nil,
  4742. # 'nasl' => nil,
  4743. # 'description' => nil,
  4744. # 'cve' => [],
  4745. # 'bid' => [],
  4746. # 'xref' => []
  4747. # }
  4748. # ]
  4749. #}
  4750. parser = Rex::Parser::NessusXMLStreamParser.new
  4751. parser.on_found_host = Proc.new { |host|
  4752. hobj = nil
  4753. addr = host['addr'] || host['hname']
  4754. next unless ipv46_validator(addr) # Catches SCAN-ERROR, among others.
  4755. if bl.include? addr
  4756. next
  4757. else
  4758. yield(:address,addr) if block
  4759. end
  4760. os = host['os']
  4761. hname = host['hname']
  4762. mac = host['mac']
  4763. host_info = {
  4764. :workspace => wspace,
  4765. :host => addr,
  4766. }
  4767. host_info[:name] = hname.to_s.strip if hname
  4768. # Short mac, protect against Nessus's habit of saving multiple macs
  4769. # We can't use them anyway, so take just the first.
  4770. host_info[:mac] = mac.to_s.strip.upcase.split(/\s+/).first if mac
  4771. hobj = report_host(host_info)
  4772. report_import_note(wspace,hobj)
  4773. os = host['os']
  4774. yield(:os,os) if block
  4775. if os
  4776. report_note(
  4777. :workspace => wspace,
  4778. :host => hobj,
  4779. :type => 'host.os.nessus_fingerprint',
  4780. :data => {
  4781. :os => os.to_s.strip
  4782. }
  4783. )
  4784. end
  4785. host['ports'].each do |item|
  4786. next if item['port'] == 0
  4787. msf = nil
  4788. nasl = item['nasl'].to_s
  4789. nasl_name = item['nasl_name'].to_s
  4790. port = item['port'].to_s
  4791. proto = item['proto'] || "tcp"
  4792. sname = item['svc_name']
  4793. severity = item['severity']
  4794. description = item['description']
  4795. cve = item['cve']
  4796. bid = item['bid']
  4797. xref = item['xref']
  4798. msf = item['msf']
  4799. yield(:port,port) if block
  4800. handle_nessus_v2(wspace, hobj, port, proto, sname, nasl, nasl_name, severity, description, cve, bid, xref, msf)
  4801. end
  4802. yield(:end,hname) if block
  4803. }
  4804. REXML::Document.parse_stream(data, parser)
  4805. end
  4806. def import_mbsa_xml(args={}, &block)
  4807. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4808. wspace = args[:wspace] || workspace
  4809. if Rex::Parser.nokogiri_loaded
  4810. parser = "Nokogiri v#{::Nokogiri::VERSION}"
  4811. noko_args = args.dup
  4812. noko_args[:blacklist] = bl
  4813. noko_args[:wspace] = wspace
  4814. if block
  4815. yield(:parser, parser)
  4816. import_mbsa_noko_stream(noko_args) {|type, data| yield type,data}
  4817. else
  4818. import_mbsa_noko_stream(noko_args)
  4819. end
  4820. return true
  4821. else # Sorry
  4822. raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
  4823. end
  4824. end
  4825. def import_mbsa_noko_stream(args={},&block)
  4826. if block
  4827. doc = Rex::Parser::MbsaDocument.new(args,framework.db) {|type, data| yield type,data }
  4828. else
  4829. doc = Rex::Parser::MbsaDocument.new(args,self)
  4830. end
  4831. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  4832. parser.parse(args[:data])
  4833. end
  4834. def import_foundstone_xml(args={}, &block)
  4835. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4836. wspace = args[:wspace] || workspace
  4837. if Rex::Parser.nokogiri_loaded
  4838. parser = "Nokogiri v#{::Nokogiri::VERSION}"
  4839. noko_args = args.dup
  4840. noko_args[:blacklist] = bl
  4841. noko_args[:wspace] = wspace
  4842. if block
  4843. yield(:parser, parser)
  4844. import_foundstone_noko_stream(noko_args) {|type, data| yield type,data}
  4845. else
  4846. import_foundstone_noko_stream(noko_args)
  4847. end
  4848. return true
  4849. else # Sorry
  4850. raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
  4851. end
  4852. end
  4853. def import_foundstone_noko_stream(args={},&block)
  4854. if block
  4855. doc = Rex::Parser::FoundstoneDocument.new(args,framework.db) {|type, data| yield type,data }
  4856. else
  4857. doc = Rex::Parser::FoundstoneDocument.new(args,self)
  4858. end
  4859. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  4860. parser.parse(args[:data])
  4861. end
  4862. def import_acunetix_xml(args={}, &block)
  4863. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4864. wspace = args[:wspace] || workspace
  4865. if Rex::Parser.nokogiri_loaded
  4866. parser = "Nokogiri v#{::Nokogiri::VERSION}"
  4867. noko_args = args.dup
  4868. noko_args[:blacklist] = bl
  4869. noko_args[:wspace] = wspace
  4870. if block
  4871. yield(:parser, parser)
  4872. import_acunetix_noko_stream(noko_args) {|type, data| yield type,data}
  4873. else
  4874. import_acunetix_noko_stream(noko_args)
  4875. end
  4876. return true
  4877. else # Sorry
  4878. raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
  4879. end
  4880. end
  4881. def import_ci_xml(args={}, &block)
  4882. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4883. wspace = args[:wspace] || workspace
  4884. if Rex::Parser.nokogiri_loaded
  4885. parser = "Nokogiri v#{::Nokogiri::VERSION}"
  4886. noko_args = args.dup
  4887. noko_args[:blacklist] = bl
  4888. noko_args[:wspace] = wspace
  4889. if block
  4890. yield(:parser, parser)
  4891. import_ci_noko_stream(noko_args) {|type, data| yield type,data}
  4892. else
  4893. import_ci_noko_stream(noko_args)
  4894. end
  4895. return true
  4896. else # Sorry
  4897. raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
  4898. end
  4899. end
  4900. def import_acunetix_noko_stream(args={},&block)
  4901. if block
  4902. doc = Rex::Parser::AcunetixDocument.new(args,framework.db) {|type, data| yield type,data }
  4903. else
  4904. doc = Rex::Parser::AcunetixFoundstoneDocument.new(args,self)
  4905. end
  4906. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  4907. parser.parse(args[:data])
  4908. end
  4909. def import_appscan_xml(args={}, &block)
  4910. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4911. wspace = args[:wspace] || workspace
  4912. if Rex::Parser.nokogiri_loaded
  4913. parser = "Nokogiri v#{::Nokogiri::VERSION}"
  4914. noko_args = args.dup
  4915. noko_args[:blacklist] = bl
  4916. noko_args[:wspace] = wspace
  4917. if block
  4918. yield(:parser, parser)
  4919. import_appscan_noko_stream(noko_args) {|type, data| yield type,data}
  4920. else
  4921. import_appscan_noko_stream(noko_args)
  4922. end
  4923. return true
  4924. else # Sorry
  4925. raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
  4926. end
  4927. end
  4928. def import_appscan_noko_stream(args={},&block)
  4929. if block
  4930. doc = Rex::Parser::AppscanDocument.new(args,framework.db) {|type, data| yield type,data }
  4931. else
  4932. doc = Rex::Parser::AppscanDocument.new(args,self)
  4933. end
  4934. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  4935. parser.parse(args[:data])
  4936. end
  4937. def import_burp_session_xml(args={}, &block)
  4938. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4939. wspace = args[:wspace] || workspace
  4940. if Rex::Parser.nokogiri_loaded
  4941. # Rex::Parser.reload("burp_session_nokogiri.rb")
  4942. parser = "Nokogiri v#{::Nokogiri::VERSION}"
  4943. noko_args = args.dup
  4944. noko_args[:blacklist] = bl
  4945. noko_args[:wspace] = wspace
  4946. if block
  4947. yield(:parser, parser)
  4948. import_burp_session_noko_stream(noko_args) {|type, data| yield type,data}
  4949. else
  4950. import_burp_session_noko_stream(noko_args)
  4951. end
  4952. return true
  4953. else # Sorry
  4954. raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
  4955. end
  4956. end
  4957. def import_burp_session_noko_stream(args={},&block)
  4958. if block
  4959. doc = Rex::Parser::BurpSessionDocument.new(args,framework.db) {|type, data| yield type,data }
  4960. else
  4961. doc = Rex::Parser::BurpSessionDocument.new(args,self)
  4962. end
  4963. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  4964. parser.parse(args[:data])
  4965. end
  4966. #
  4967. # Import IP360's ASPL database
  4968. #
  4969. def import_ip360_aspl_xml(args={}, &block)
  4970. data = args[:data]
  4971. wspace = args[:wspace] || workspace
  4972. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4973. if not data.index("<ontology")
  4974. raise DBImportError.new("The ASPL file does not appear to be valid or may still be compressed")
  4975. end
  4976. base = ::File.join(Msf::Config.config_directory, "data", "ncircle")
  4977. ::FileUtils.mkdir_p(base)
  4978. ::File.open(::File.join(base, "ip360.aspl"), "wb") do |fd|
  4979. fd.write(data)
  4980. end
  4981. yield(:notice, "Saved the IP360 ASPL database to #{base}...")
  4982. end
  4983. #
  4984. # Import IP360's xml output
  4985. #
  4986. def import_ip360_xml_v3(args={}, &block)
  4987. data = args[:data]
  4988. wspace = args[:wspace] || workspace
  4989. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  4990. # @aspl = {'vulns' => {'name' => { }, 'cve' => { }, 'bid' => { } }
  4991. # 'oses' => {'name' } }
  4992. aspl_path = nil
  4993. aspl_paths = [
  4994. ::File.join(Msf::Config.config_directory, "data", "ncircle", "ip360.aspl"),
  4995. ::File.join(Msf::Config.data_directory, "ncircle", "ip360.aspl")
  4996. ]
  4997. aspl_paths.each do |tpath|
  4998. next if not (::File.exist?(tpath) and ::File.readable?(tpath))
  4999. aspl_path = tpath
  5000. break
  5001. end
  5002. if not aspl_path
  5003. raise DBImportError.new("The nCircle IP360 ASPL file is not present.\n Download ASPL from nCircle VNE | Administer | Support | Resources, unzip it, and import it first")
  5004. end
  5005. # parse nCircle ASPL file
  5006. aspl = ""
  5007. ::File.open(aspl_path, "rb") do |f|
  5008. aspl = f.read(f.stat.size)
  5009. end
  5010. @asplhash = nil
  5011. parser = Rex::Parser::IP360ASPLXMLStreamParser.new
  5012. parser.on_found_aspl = Proc.new { |asplh|
  5013. @asplhash = asplh
  5014. }
  5015. REXML::Document.parse_stream(aspl, parser)
  5016. # nCircle has some quotes escaped which causes the parser to break
  5017. # we don't need these lines so just replace \" with "
  5018. data.gsub!(/\\"/,'"')
  5019. # parse nCircle Scan Output
  5020. parser = Rex::Parser::IP360XMLStreamParser.new
  5021. parser.on_found_host = Proc.new { |host|
  5022. hobj = nil
  5023. addr = host['addr'] || host['hname']
  5024. next unless ipv46_validator(addr) # Catches SCAN-ERROR, among others.
  5025. if bl.include? addr
  5026. next
  5027. else
  5028. yield(:address,addr) if block
  5029. end
  5030. os = host['os']
  5031. hname = host['hname']
  5032. mac = host['mac']
  5033. host_hash = {
  5034. :workspace => wspace,
  5035. :host => addr,
  5036. }
  5037. host_hash[:name] = hname.to_s.strip if hname
  5038. host_hash[:mac] = mac.to_s.strip.upcase if mac
  5039. hobj = report_host(host_hash)
  5040. yield(:os, os) if block
  5041. if os
  5042. report_note(
  5043. :workspace => wspace,
  5044. :host => hobj,
  5045. :type => 'host.os.ip360_fingerprint',
  5046. :data => {
  5047. :os => @asplhash['oses'][os].to_s.strip
  5048. }
  5049. )
  5050. end
  5051. host['apps'].each do |item|
  5052. port = item['port'].to_s
  5053. proto = item['proto'].to_s
  5054. handle_ip360_v3_svc(wspace, hobj, port, proto, hname)
  5055. end
  5056. host['vulns'].each do |item|
  5057. vulnid = item['vulnid'].to_s
  5058. port = item['port'].to_s
  5059. proto = item['proto'] || "tcp"
  5060. vulnname = @asplhash['vulns']['name'][vulnid]
  5061. cves = @asplhash['vulns']['cve'][vulnid]
  5062. bids = @asplhash['vulns']['bid'][vulnid]
  5063. yield(:port, port) if block
  5064. handle_ip360_v3_vuln(wspace, hobj, port, proto, hname, vulnid, vulnname, cves, bids)
  5065. end
  5066. yield(:end, hname) if block
  5067. }
  5068. REXML::Document.parse_stream(data, parser)
  5069. end
  5070. def find_qualys_asset_vuln_refs(doc)
  5071. vuln_refs = {}
  5072. doc.elements.each("/ASSET_DATA_REPORT/GLOSSARY/VULN_DETAILS_LIST/VULN_DETAILS") do |vuln|
  5073. next unless vuln.elements['QID'] && vuln.elements['QID'].first
  5074. qid = vuln.elements['QID'].first.to_s
  5075. vuln_refs[qid] ||= []
  5076. vuln.elements.each('CVE_ID_LIST/CVE_ID') do |ref|
  5077. vuln_refs[qid].push('CVE-' + /C..-([0-9\-]{9})/.match(ref.elements['ID'].text.to_s)[1])
  5078. end
  5079. vuln.elements.each('BUGTRAQ_ID_LIST/BUGTRAQ_ID') do |ref|
  5080. vuln_refs[qid].push('BID-' + ref.elements['ID'].text.to_s)
  5081. end
  5082. end
  5083. return vuln_refs
  5084. end
  5085. # Pull out vulnerabilities that have at least one matching
  5086. # ref -- many "vulns" are not vulns, just audit information.
  5087. def find_qualys_asset_vulns(host,wspace,hobj,vuln_refs,&block)
  5088. host.elements.each("VULN_INFO_LIST/VULN_INFO") do |vi|
  5089. next unless vi.elements["QID"]
  5090. vi.elements.each("QID") do |qid|
  5091. next if vuln_refs[qid.text].nil? || vuln_refs[qid.text].empty?
  5092. handle_qualys(wspace, hobj, nil, nil, qid.text, nil, vuln_refs[qid.text], nil)
  5093. end
  5094. end
  5095. end
  5096. # Takes QID numbers and finds the discovered services in
  5097. # a qualys_asset_xml.
  5098. def find_qualys_asset_ports(i,host,wspace,hobj)
  5099. return unless (i == 82023 || i == 82004)
  5100. proto = i == 82023 ? 'tcp' : 'udp'
  5101. qid = host.elements["VULN_INFO_LIST/VULN_INFO/QID[@id='qid_#{i}']"]
  5102. qid_result = qid.parent.elements["RESULT[@format='table']"] if qid
  5103. hports = qid_result.first.to_s if qid_result
  5104. if hports
  5105. hports.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match|
  5106. if match[2] == nil or match[2].strip == 'unknown'
  5107. name = match[1].strip
  5108. else
  5109. name = match[2].strip
  5110. end
  5111. handle_qualys(wspace, hobj, match[0].to_s, proto, 0, nil, nil, name)
  5112. end
  5113. end
  5114. end
  5115. #
  5116. # Import Qualys's Asset Data Report format
  5117. #
  5118. def import_qualys_asset_xml(args={}, &block)
  5119. data = args[:data]
  5120. wspace = args[:wspace] || workspace
  5121. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  5122. doc = rexmlify(data)
  5123. vuln_refs = find_qualys_asset_vuln_refs(doc)
  5124. # 2nd pass, actually grab the hosts.
  5125. doc.elements.each("/ASSET_DATA_REPORT/HOST_LIST/HOST") do |host|
  5126. hobj = nil
  5127. addr = host.elements["IP"].text if host.elements["IP"]
  5128. next unless validate_ips(addr)
  5129. if bl.include? addr
  5130. next
  5131. else
  5132. yield(:address,addr) if block
  5133. end
  5134. hname = ( # Prefer NetBIOS over DNS
  5135. (host.elements["NETBIOS"].text if host.elements["NETBIOS"]) ||
  5136. (host.elements["DNS"].text if host.elements["DNS"]) ||
  5137. "" )
  5138. hobj = report_host(:workspace => wspace, :host => addr, :name => hname, :state => Msf::HostState::Alive)
  5139. report_import_note(wspace,hobj)
  5140. if host.elements["OPERATING_SYSTEM"]
  5141. hos = host.elements["OPERATING_SYSTEM"].text
  5142. report_note(
  5143. :workspace => wspace,
  5144. :host => hobj,
  5145. :type => 'host.os.qualys_fingerprint',
  5146. :data => { :os => hos }
  5147. )
  5148. end
  5149. # Report open ports.
  5150. find_qualys_asset_ports(82023,host,wspace,hobj) # TCP
  5151. find_qualys_asset_ports(82004,host,wspace,hobj) # UDP
  5152. # Report vulns
  5153. find_qualys_asset_vulns(host,wspace,hobj,vuln_refs,&block)
  5154. end # host
  5155. end
  5156. #
  5157. # Import Qualys' Scan xml output
  5158. #
  5159. def import_qualys_scan_xml_file(args={})
  5160. filename = args[:filename]
  5161. wspace = args[:wspace] || workspace
  5162. data = ""
  5163. ::File.open(filename, 'rb') do |f|
  5164. data = f.read(f.stat.size)
  5165. end
  5166. import_qualys_scan_xml(args.merge(:data => data))
  5167. end
  5168. def import_qualys_scan_xml(args={}, &block)
  5169. data = args[:data]
  5170. wspace = args[:wspace] || workspace
  5171. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  5172. doc = rexmlify(data)
  5173. doc.elements.each('/SCAN/IP') do |host|
  5174. hobj = nil
  5175. addr = host.attributes['value']
  5176. if bl.include? addr
  5177. next
  5178. else
  5179. yield(:address,addr) if block
  5180. end
  5181. hname = host.attributes['name'] || ''
  5182. hobj = report_host(:workspace => wspace, :host => addr, :name => hname, :state => Msf::HostState::Alive)
  5183. report_import_note(wspace,hobj)
  5184. if host.elements["OS"]
  5185. hos = host.elements["OS"].text
  5186. report_note(
  5187. :workspace => wspace,
  5188. :host => hobj,
  5189. :type => 'host.os.qualys_fingerprint',
  5190. :data => {
  5191. :os => hos
  5192. }
  5193. )
  5194. end
  5195. # Open TCP Services List (Qualys ID 82023)
  5196. services_tcp = host.elements["SERVICES/CAT/SERVICE[@number='82023']/RESULT"]
  5197. if services_tcp
  5198. services_tcp.text.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match|
  5199. if match[2] == nil or match[2].strip == 'unknown'
  5200. name = match[1].strip
  5201. else
  5202. name = match[2].strip
  5203. end
  5204. handle_qualys(wspace, hobj, match[0].to_s, 'tcp', 0, nil, nil, name)
  5205. end
  5206. end
  5207. # Open UDP Services List (Qualys ID 82004)
  5208. services_udp = host.elements["SERVICES/CAT/SERVICE[@number='82004']/RESULT"]
  5209. if services_udp
  5210. services_udp.text.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match|
  5211. if match[2] == nil or match[2].strip == 'unknown'
  5212. name = match[1].strip
  5213. else
  5214. name = match[2].strip
  5215. end
  5216. handle_qualys(wspace, hobj, match[0].to_s, 'udp', 0, nil, nil, name)
  5217. end
  5218. end
  5219. # VULNS are confirmed, PRACTICES are unconfirmed vulnerabilities
  5220. host.elements.each('VULNS/CAT | PRACTICES/CAT') do |cat|
  5221. port = cat.attributes['port']
  5222. protocol = cat.attributes['protocol']
  5223. cat.elements.each('VULN | PRACTICE') do |vuln|
  5224. refs = []
  5225. qid = vuln.attributes['number']
  5226. severity = vuln.attributes['severity']
  5227. title = vuln.elements['TITLE'].text.to_s
  5228. vuln.elements.each('VENDOR_REFERENCE_LIST/VENDOR_REFERENCE') do |ref|
  5229. refs.push(ref.elements['ID'].text.to_s)
  5230. end
  5231. vuln.elements.each('CVE_ID_LIST/CVE_ID') do |ref|
  5232. refs.push('CVE-' + /C..-([0-9\-]{9})/.match(ref.elements['ID'].text.to_s)[1])
  5233. end
  5234. vuln.elements.each('BUGTRAQ_ID_LIST/BUGTRAQ_ID') do |ref|
  5235. refs.push('BID-' + ref.elements['ID'].text.to_s)
  5236. end
  5237. handle_qualys(wspace, hobj, port, protocol, qid, severity, refs, nil,title)
  5238. end
  5239. end
  5240. end
  5241. end
  5242. def import_ip_list_file(args={})
  5243. filename = args[:filename]
  5244. wspace = args[:wspace] || workspace
  5245. data = ""
  5246. ::File.open(filename, 'rb') do |f|
  5247. data = f.read(f.stat.size)
  5248. end
  5249. import_ip_list(args.merge(:data => data))
  5250. end
  5251. def import_ip_list(args={}, &block)
  5252. data = args[:data]
  5253. wspace = args[:wspace] || workspace
  5254. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  5255. data.each_line do |ip|
  5256. ip.strip!
  5257. if bl.include? ip
  5258. next
  5259. else
  5260. yield(:address,ip) if block
  5261. end
  5262. host = find_or_create_host(:workspace => wspace, :host=> ip, :state => Msf::HostState::Alive)
  5263. end
  5264. end
  5265. def import_amap_log_file(args={})
  5266. filename = args[:filename]
  5267. wspace = args[:wspace] || workspace
  5268. data = ""
  5269. ::File.open(filename, 'rb') do |f|
  5270. data = f.read(f.stat.size)
  5271. end
  5272. case import_filetype_detect(data)
  5273. when :amap_log
  5274. import_amap_log(args.merge(:data => data))
  5275. when :amap_mlog
  5276. import_amap_mlog(args.merge(:data => data))
  5277. else
  5278. raise DBImportError.new("Could not determine file type")
  5279. end
  5280. end
  5281. def import_amap_log(args={}, &block)
  5282. data = args[:data]
  5283. wspace = args[:wspace] || workspace
  5284. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  5285. data.each_line do |line|
  5286. next if line =~ /^#/
  5287. next if line !~ /^Protocol on ([^:]+):([^\x5c\x2f]+)[\x5c\x2f](tcp|udp) matches (.*)$/
  5288. addr = $1
  5289. next if bl.include? addr
  5290. port = $2.to_i
  5291. proto = $3.downcase
  5292. name = $4
  5293. host = find_or_create_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
  5294. next if not host
  5295. yield(:address,addr) if block
  5296. info = {
  5297. :workspace => wspace,
  5298. :host => host,
  5299. :proto => proto,
  5300. :port => port
  5301. }
  5302. if name != "unidentified"
  5303. info[:name] = name
  5304. end
  5305. service = find_or_create_service(info)
  5306. end
  5307. end
  5308. def import_amap_mlog(args={}, &block)
  5309. data = args[:data]
  5310. wspace = args[:wspace] || workspace
  5311. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  5312. data.each_line do |line|
  5313. next if line =~ /^#/
  5314. r = line.split(':')
  5315. next if r.length < 6
  5316. addr = r[0]
  5317. next if bl.include? addr
  5318. port = r[1].to_i
  5319. proto = r[2].downcase
  5320. status = r[3]
  5321. name = r[5]
  5322. next if status != "open"
  5323. host = find_or_create_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
  5324. next if not host
  5325. yield(:address,addr) if block
  5326. info = {
  5327. :workspace => wspace,
  5328. :host => host,
  5329. :proto => proto,
  5330. :port => port
  5331. }
  5332. if name != "unidentified"
  5333. info[:name] = name
  5334. end
  5335. service = find_or_create_service(info)
  5336. end
  5337. end
  5338. def import_ci_noko_stream(args, &block)
  5339. if block
  5340. doc = Rex::Parser::CIDocument.new(args,framework.db) {|type, data| yield type,data }
  5341. else
  5342. doc = Rex::Parser::CI.new(args,self)
  5343. end
  5344. parser = ::Nokogiri::XML::SAX::Parser.new(doc)
  5345. parser.parse(args[:data])
  5346. end
  5347. def unserialize_object(xml_elem, allow_yaml = false)
  5348. return nil unless xml_elem
  5349. string = xml_elem.text.to_s.strip
  5350. return string unless string.is_a?(String)
  5351. return nil if (string.empty? || string.nil?)
  5352. begin
  5353. # Validate that it is properly formed base64 first
  5354. if string.gsub(/\s+/, '') =~ /^([a-z0-9A-Z\+\/=]+)$/
  5355. Marshal.load($1.unpack("m")[0])
  5356. else
  5357. if allow_yaml
  5358. begin
  5359. YAML.load(string)
  5360. rescue
  5361. dlog("Badly formatted YAML: '#{string}'")
  5362. string
  5363. end
  5364. else
  5365. string
  5366. end
  5367. end
  5368. rescue ::Exception => e
  5369. if allow_yaml
  5370. YAML.load(string) rescue string
  5371. else
  5372. string
  5373. end
  5374. end
  5375. end
  5376. #
  5377. # Returns something suitable for the +:host+ parameter to the various report_* methods
  5378. #
  5379. # Takes a Host object, a Session object, an Msf::Session object or a String
  5380. # address
  5381. #
  5382. def normalize_host(host)
  5383. return host if host.kind_of? ::Mdm::Host
  5384. norm_host = nil
  5385. if (host.kind_of? String)
  5386. if Rex::Socket.is_ipv4?(host)
  5387. # If it's an IPv4 addr with a port on the end, strip the port
  5388. if host =~ /((\d{1,3}\.){3}\d{1,3}):\d+/
  5389. norm_host = $1
  5390. else
  5391. norm_host = host
  5392. end
  5393. elsif Rex::Socket.is_ipv6?(host)
  5394. # If it's an IPv6 addr, drop the scope
  5395. address, scope = host.split('%', 2)
  5396. norm_host = address
  5397. else
  5398. norm_host = Rex::Socket.getaddress(host, true)
  5399. end
  5400. elsif host.kind_of? ::Mdm::Session
  5401. norm_host = host.host
  5402. elsif host.respond_to?(:session_host)
  5403. # Then it's an Msf::Session object
  5404. thost = host.session_host
  5405. norm_host = thost
  5406. end
  5407. # If we got here and don't have a norm_host yet, it could be a
  5408. # Msf::Session object with an empty or nil tunnel_host and tunnel_peer;
  5409. # see if it has a socket and use its peerhost if so.
  5410. if (
  5411. norm_host.nil? and
  5412. host.respond_to?(:sock) and
  5413. host.sock.respond_to?(:peerhost) and
  5414. host.sock.peerhost.to_s.length > 0
  5415. )
  5416. norm_host = session.sock.peerhost
  5417. end
  5418. # If We got here and still don't have a real host, there's nothing left
  5419. # to try, just log it and return what we were given
  5420. if not norm_host
  5421. dlog("Host could not be normalized: #{host.inspect}")
  5422. norm_host = host
  5423. end
  5424. norm_host
  5425. end
  5426. # A way to sneak the yield back into the db importer.
  5427. # Used by the SAX parsers.
  5428. def emit(sym,data,&block)
  5429. yield(sym,data)
  5430. end
  5431. protected
  5432. #
  5433. # This holds all of the shared parsing/handling used by the
  5434. # Nessus NBE and NESSUS v1 methods
  5435. #
  5436. def handle_nessus(wspace, hobj, port, nasl, plugin_name, severity, data)
  5437. addr = hobj.address
  5438. # The port section looks like:
  5439. # http (80/tcp)
  5440. p = port.match(/^([^\(]+)\((\d+)\/([^\)]+)\)/)
  5441. return if not p
  5442. # Unnecessary as the caller should already have reported this host
  5443. #report_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
  5444. name = p[1].strip
  5445. port = p[2].to_i
  5446. proto = p[3].downcase
  5447. info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto }
  5448. if name != "unknown" and name[-1,1] != "?"
  5449. info[:name] = name
  5450. end
  5451. report_service(info)
  5452. if nasl.nil? || nasl.empty? || nasl == 0 || nasl == "0"
  5453. return
  5454. end
  5455. data.gsub!("\\n", "\n")
  5456. refs = []
  5457. if (data =~ /^CVE : (.*)$/)
  5458. $1.gsub(/C(VE|AN)\-/, '').split(',').map { |r| r.strip }.each do |r|
  5459. refs.push('CVE-' + r)
  5460. end
  5461. end
  5462. if (data =~ /^BID : (.*)$/)
  5463. $1.split(',').map { |r| r.strip }.each do |r|
  5464. refs.push('BID-' + r)
  5465. end
  5466. end
  5467. if (data =~ /^Other references : (.*)$/)
  5468. $1.split(',').map { |r| r.strip }.each do |r|
  5469. ref_id, ref_val = r.split(':')
  5470. ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id)
  5471. end
  5472. end
  5473. nss = 'NSS-' + nasl.to_s.strip
  5474. refs << nss
  5475. unless plugin_name.to_s.strip.empty?
  5476. vuln_name = plugin_name
  5477. else
  5478. vuln_name = nss
  5479. end
  5480. vuln_info = {
  5481. :workspace => wspace,
  5482. :host => hobj,
  5483. :port => port,
  5484. :proto => proto,
  5485. :name => vuln_name,
  5486. :info => data,
  5487. :refs => refs
  5488. }
  5489. report_vuln(vuln_info)
  5490. end
  5491. #
  5492. # NESSUS v2 file format has a dramatically different layout
  5493. # for ReportItem data
  5494. #
  5495. def handle_nessus_v2(wspace,hobj,port,proto,name,nasl,nasl_name,severity,description,cve,bid,xref,msf)
  5496. addr = hobj.address
  5497. info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto }
  5498. unless name =~ /^unknown$|\?$/
  5499. info[:name] = name
  5500. end
  5501. if port.to_i != 0
  5502. report_service(info)
  5503. end
  5504. if nasl.nil? || nasl.empty? || nasl == 0 || nasl == "0"
  5505. return
  5506. end
  5507. refs = []
  5508. cve.each do |r|
  5509. r.to_s.gsub!(/C(VE|AN)\-/, '')
  5510. refs.push('CVE-' + r.to_s)
  5511. end if cve
  5512. bid.each do |r|
  5513. refs.push('BID-' + r.to_s)
  5514. end if bid
  5515. xref.each do |r|
  5516. ref_id, ref_val = r.to_s.split(':')
  5517. ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id)
  5518. end if xref
  5519. msfref = "MSF-" << msf if msf
  5520. refs.push msfref if msfref
  5521. nss = 'NSS-' + nasl
  5522. if nasl_name.nil? || nasl_name.empty?
  5523. vuln_name = nss
  5524. else
  5525. vuln_name = nasl_name
  5526. end
  5527. refs << nss.strip
  5528. vuln = {
  5529. :workspace => wspace,
  5530. :host => hobj,
  5531. :name => vuln_name,
  5532. :info => description ? description : "",
  5533. :refs => refs
  5534. }
  5535. if port.to_i != 0
  5536. vuln[:port] = port
  5537. vuln[:proto] = proto
  5538. end
  5539. report_vuln(vuln)
  5540. end
  5541. #
  5542. # IP360 v3 vuln
  5543. #
  5544. def handle_ip360_v3_svc(wspace,hobj,port,proto,hname)
  5545. addr = hobj.address
  5546. report_host(:workspace => wspace, :host => hobj, :state => Msf::HostState::Alive)
  5547. info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto }
  5548. if hname != "unknown" and hname[-1,1] != "?"
  5549. info[:name] = hname
  5550. end
  5551. if port.to_i != 0
  5552. report_service(info)
  5553. end
  5554. end #handle_ip360_v3_svc
  5555. #
  5556. # IP360 v3 vuln
  5557. #
  5558. def handle_ip360_v3_vuln(wspace,hobj,port,proto,hname,vulnid,vulnname,cves,bids)
  5559. info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto }
  5560. if hname != "unknown" and hname[-1,1] != "?"
  5561. info[:name] = hname
  5562. end
  5563. if port.to_i != 0
  5564. report_service(info)
  5565. end
  5566. refs = []
  5567. cves.split(/,/).each do |cve|
  5568. refs.push(cve.to_s)
  5569. end if cves
  5570. bids.split(/,/).each do |bid|
  5571. refs.push('BID-' + bid.to_s)
  5572. end if bids
  5573. description = nil # not working yet
  5574. vuln = {
  5575. :workspace => wspace,
  5576. :host => hobj,
  5577. :name => vulnname,
  5578. :info => description ? description : "",
  5579. :refs => refs
  5580. }
  5581. if port.to_i != 0
  5582. vuln[:port] = port
  5583. vuln[:proto] = proto
  5584. end
  5585. report_vuln(vuln)
  5586. end #handle_ip360_v3_vuln
  5587. #
  5588. # Qualys report parsing/handling
  5589. #
  5590. def handle_qualys(wspace, hobj, port, protocol, qid, severity, refs, name=nil, title=nil)
  5591. addr = hobj.address
  5592. port = port.to_i if port
  5593. info = { :workspace => wspace, :host => hobj, :port => port, :proto => protocol }
  5594. if name and name != 'unknown' and name != 'No registered hostname'
  5595. info[:name] = name
  5596. end
  5597. if info[:host] && info[:port] && info[:proto]
  5598. report_service(info)
  5599. end
  5600. fixed_refs = []
  5601. if refs
  5602. refs.each do |ref|
  5603. case ref
  5604. when /^MS[0-9]{2}-[0-9]{3}/
  5605. fixed_refs << "MSB-#{ref}"
  5606. else
  5607. fixed_refs << ref
  5608. end
  5609. end
  5610. end
  5611. return if qid == 0
  5612. title = 'QUALYS-' + qid if title.nil? or title.empty?
  5613. if addr
  5614. report_vuln(
  5615. :workspace => wspace,
  5616. :host => hobj,
  5617. :port => port,
  5618. :proto => protocol,
  5619. :name => title,
  5620. :refs => fixed_refs
  5621. )
  5622. end
  5623. end
  5624. def process_nexpose_data_sxml_refs(vuln)
  5625. refs = []
  5626. vid = vuln.attributes['id'].to_s.downcase
  5627. vry = vuln.attributes['resultCode'].to_s.upcase
  5628. # Only process vuln-exploitable and vuln-version statuses
  5629. return if vry !~ /^V[VE]$/
  5630. refs = []
  5631. vuln.elements.each('id') do |ref|
  5632. rtyp = ref.attributes['type'].to_s.upcase
  5633. rval = ref.text.to_s.strip
  5634. case rtyp
  5635. when 'CVE'
  5636. refs << rval.gsub('CAN', 'CVE')
  5637. when 'MS' # obsolete?
  5638. refs << "MSB-MS-#{rval}"
  5639. else
  5640. refs << "#{rtyp}-#{rval}"
  5641. end
  5642. end
  5643. refs << "NEXPOSE-#{vid}"
  5644. refs
  5645. end
  5646. end
  5647. end