PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/msf/core/db.rb

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