PageRenderTime 72ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/msf/core/db.rb

https://bitbucket.org/jrossi/metasploit
Ruby | 4759 lines | 3391 code | 586 blank | 782 comment | 634 complexity | a0f00401a3aa688c88dfb996dbc9b9e1 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause
  1. require 'rex/parser/nmap_xml'
  2. require 'rex/parser/nexpose_xml'
  3. require 'rex/parser/retina_xml'
  4. require 'rex/parser/netsparker_xml'
  5. require 'rex/parser/nessus_xml'
  6. require 'rex/parser/ip360_xml'
  7. require 'rex/parser/ip360_aspl_xml'
  8. require 'rex/socket'
  9. require 'zip'
  10. require 'packetfu'
  11. require 'uri'
  12. require 'tmpdir'
  13. require 'fileutils'
  14. module Msf
  15. ###
  16. #
  17. # The states that a host can be in.
  18. #
  19. ###
  20. module HostState
  21. #
  22. # The host is alive.
  23. #
  24. Alive = "alive"
  25. #
  26. # The host is dead.
  27. #
  28. Dead = "down"
  29. #
  30. # The host state is unknown.
  31. #
  32. Unknown = "unknown"
  33. end
  34. ###
  35. #
  36. # The states that a service can be in.
  37. #
  38. ###
  39. module ServiceState
  40. Open = "open"
  41. Closed = "closed"
  42. Filtered = "filtered"
  43. Unknown = "unknown"
  44. end
  45. ###
  46. #
  47. # Events that can occur in the host/service database.
  48. #
  49. ###
  50. module DatabaseEvent
  51. #
  52. # Called when an existing host's state changes
  53. #
  54. def on_db_host_state(host, ostate)
  55. end
  56. #
  57. # Called when an existing service's state changes
  58. #
  59. def on_db_service_state(host, port, ostate)
  60. end
  61. #
  62. # Called when a new host is added to the database. The host parameter is
  63. # of type Host.
  64. #
  65. def on_db_host(host)
  66. end
  67. #
  68. # Called when a new client is added to the database. The client
  69. # parameter is of type Client.
  70. #
  71. def on_db_client(client)
  72. end
  73. #
  74. # Called when a new service is added to the database. The service
  75. # parameter is of type Service.
  76. #
  77. def on_db_service(service)
  78. end
  79. #
  80. # Called when an applicable vulnerability is found for a service. The vuln
  81. # parameter is of type Vuln.
  82. #
  83. def on_db_vuln(vuln)
  84. end
  85. #
  86. # Called when a new reference is created.
  87. #
  88. def on_db_ref(ref)
  89. end
  90. end
  91. class DBImportError < RuntimeError
  92. end
  93. ###
  94. #
  95. # The DB module ActiveRecord definitions for the DBManager
  96. #
  97. ###
  98. class DBManager
  99. def rfc3330_reserved(ip)
  100. case ip.class.to_s
  101. when "PacketFu::Octets"
  102. ip_x = ip.to_x
  103. ip_i = ip.to_i
  104. when "String"
  105. if ipv4_validator(ip)
  106. ip_x = ip
  107. ip_i = Rex::Socket.addr_atoi(ip)
  108. else
  109. raise ArgumentError, "Invalid IP address: #{ip.inspect}"
  110. end
  111. when "Fixnum"
  112. if (0..2**32-1).include? ip
  113. ip_x = Rex::Socket.addr_itoa(ip)
  114. ip_i = ip
  115. else
  116. raise ArgumentError, "Invalid IP address: #{ip.inspect}"
  117. end
  118. else
  119. raise ArgumentError, "Invalid IP address: #{ip.inspect}"
  120. end
  121. return true if Rex::Socket::RangeWalker.new("0.0.0.0-0.255.255.255").include? ip_x
  122. return true if Rex::Socket::RangeWalker.new("127.0.0.0-127.255.255.255").include? ip_x
  123. return true if Rex::Socket::RangeWalker.new("169.254.0.0-169.254.255.255").include? ip_x
  124. return true if Rex::Socket::RangeWalker.new("224.0.0.0-239.255.255.255").include? ip_x
  125. return true if Rex::Socket::RangeWalker.new("255.255.255.255-255.255.255.255").include? ip_x
  126. return false
  127. end
  128. def ipv4_validator(addr)
  129. return false unless addr.kind_of? String
  130. addr =~ /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
  131. end
  132. # Takes a space-delimited set of ips and ranges, and subjects
  133. # them to RangeWalker for validation. Returns true or false.
  134. def validate_ips(ips)
  135. ret = true
  136. begin
  137. ips.split(' ').each {|ip|
  138. unless Rex::Socket::RangeWalker.new(ip).ranges
  139. ret = false
  140. break
  141. end
  142. }
  143. rescue
  144. ret = false
  145. end
  146. return ret
  147. end
  148. #
  149. # Determines if the database is functional
  150. #
  151. def check
  152. res = Host.find(:first)
  153. end
  154. def default_workspace
  155. Workspace.default
  156. end
  157. def find_workspace(name)
  158. Workspace.find_by_name(name)
  159. end
  160. #
  161. # Creates a new workspace in the database
  162. #
  163. def add_workspace(name)
  164. Workspace.find_or_create_by_name(name)
  165. end
  166. def workspaces
  167. Workspace.find(:all)
  168. end
  169. #
  170. # Wait for all pending write to finish
  171. #
  172. def sync
  173. task = queue( Proc.new { } )
  174. task.wait
  175. end
  176. #
  177. # Find a host. Performs no database writes.
  178. #
  179. def get_host(opts)
  180. if opts.kind_of? Host
  181. return opts
  182. elsif opts.kind_of? String
  183. raise RuntimeError, "This invokation of get_host is no longer supported: #{caller}"
  184. else
  185. address = opts[:addr] || opts[:address] || opts[:host] || return
  186. return address if address.kind_of? Host
  187. end
  188. wspace = opts.delete(:workspace) || workspace
  189. host = wspace.hosts.find_by_address(address)
  190. return host
  191. end
  192. #
  193. # Exactly like report_host but waits for the database to create a host and returns it.
  194. #
  195. def find_or_create_host(opts)
  196. report_host(opts.merge({:wait => true}))
  197. end
  198. #
  199. # Report a host's attributes such as operating system and service pack
  200. #
  201. # The opts parameter MUST contain
  202. # :host -- the host's ip address
  203. #
  204. # The opts parameter can contain:
  205. # :state -- one of the Msf::HostState constants
  206. # :os_name -- one of the Msf::OperatingSystems constants
  207. # :os_flavor -- something like "XP" or "Gentoo"
  208. # :os_sp -- something like "SP2"
  209. # :os_lang -- something like "English", "French", or "en-US"
  210. # :arch -- one of the ARCH_* constants
  211. # :mac -- the host's MAC address
  212. #
  213. def report_host(opts)
  214. return if not active
  215. addr = opts.delete(:host) || return
  216. # Sometimes a host setup through a pivot will see the address as "Remote Pipe"
  217. if addr.eql? "Remote Pipe"
  218. return
  219. end
  220. # Ensure the host field updated_at is changed on each report_host()
  221. if addr.kind_of? Host
  222. queue( Proc.new { addr.updated_at = addr.created_at; addr.save! } )
  223. return addr
  224. end
  225. addr = normalize_host(addr)
  226. wait = opts.delete(:wait)
  227. wspace = opts.delete(:workspace) || workspace
  228. if opts[:host_mac]
  229. opts[:mac] = opts.delete(:host_mac)
  230. end
  231. unless ipv4_validator(addr)
  232. raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
  233. end
  234. ret = {}
  235. task = queue( Proc.new {
  236. if opts[:comm] and opts[:comm].length > 0
  237. host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm])
  238. else
  239. host = wspace.hosts.find_or_initialize_by_address(addr)
  240. end
  241. opts.each { |k,v|
  242. if (host.attribute_names.include?(k.to_s))
  243. host[k] = v unless host.attribute_locked?(k.to_s)
  244. else
  245. dlog("Unknown attribute for Host: #{k}")
  246. end
  247. }
  248. host.info = host.info[0,Host.columns_hash["info"].limit] if host.info
  249. # Set default fields if needed
  250. host.state = HostState::Alive if not host.state
  251. host.comm = '' if not host.comm
  252. host.workspace = wspace if not host.workspace
  253. # Always save the host, helps track updates
  254. msf_import_timestamps(opts,host)
  255. host.save!
  256. ret[:host] = host
  257. } )
  258. if wait
  259. return nil if task.wait != :done
  260. return ret[:host]
  261. end
  262. return task
  263. end
  264. #
  265. # Iterates over the hosts table calling the supplied block with the host
  266. # instance of each entry.
  267. #
  268. def each_host(wspace=workspace, &block)
  269. wspace.hosts.each do |host|
  270. block.call(host)
  271. end
  272. end
  273. #
  274. # Returns a list of all hosts in the database
  275. #
  276. def hosts(wspace = workspace, only_up = false, addresses = nil)
  277. conditions = {}
  278. conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if only_up
  279. conditions[:address] = addresses if addresses
  280. wspace.hosts.all(:conditions => conditions, :order => :address)
  281. end
  282. def find_or_create_service(opts)
  283. report_service(opts.merge({:wait => true}))
  284. end
  285. #
  286. # Record a service in the database.
  287. #
  288. # opts must contain
  289. # :host -- the host where this service is running
  290. # :port -- the port where this service listens
  291. # :proto -- the transport layer protocol (e.g. tcp, udp)
  292. #
  293. # opts may contain
  294. # :name -- the application layer protocol (e.g. ssh, mssql, smb)
  295. #
  296. def report_service(opts)
  297. return if not active
  298. addr = opts.delete(:host) || return
  299. hname = opts.delete(:host_name)
  300. hmac = opts.delete(:host_mac)
  301. wait = opts.delete(:wait)
  302. wspace = opts.delete(:workspace) || workspace
  303. hopts = {:workspace => wspace, :host => addr}
  304. hopts[:name] = hname if hname
  305. hopts[:mac] = hmac if hmac
  306. report_host(hopts)
  307. ret = {}
  308. task = queue(Proc.new {
  309. host = get_host(:workspace => wspace, :address => addr)
  310. if host
  311. host.updated_at = host.created_at
  312. host.state = HostState::Alive
  313. host.save!
  314. end
  315. proto = opts[:proto] || 'tcp'
  316. opts[:name].downcase! if (opts[:name])
  317. service = host.services.find_or_initialize_by_port_and_proto(opts[:port].to_i, proto)
  318. opts.each { |k,v|
  319. if (service.attribute_names.include?(k.to_s))
  320. service[k] = v
  321. else
  322. dlog("Unknown attribute for Service: #{k}")
  323. end
  324. }
  325. if (service.state == nil)
  326. service.state = ServiceState::Open
  327. end
  328. if (service and service.changed?)
  329. msf_import_timestamps(opts,service)
  330. service.save!
  331. end
  332. ret[:service] = service
  333. })
  334. if wait
  335. return nil if task.wait() != :done
  336. return ret[:service]
  337. end
  338. return task
  339. end
  340. def get_service(wspace, host, proto, port)
  341. host = get_host(:workspace => wspace, :address => host)
  342. return if not host
  343. return host.services.find_by_proto_and_port(proto, port)
  344. end
  345. #
  346. # Iterates over the services table calling the supplied block with the
  347. # service instance of each entry.
  348. #
  349. def each_service(wspace=workspace, &block)
  350. services(wspace).each do |service|
  351. block.call(service)
  352. end
  353. end
  354. #
  355. # Returns a list of all services in the database
  356. #
  357. def services(wspace = workspace, only_up = false, proto = nil, addresses = nil, ports = nil, names = nil)
  358. conditions = {}
  359. conditions[:state] = [ServiceState::Open] if only_up
  360. conditions[:proto] = proto if proto
  361. conditions["hosts.address"] = addresses if addresses
  362. conditions[:port] = ports if ports
  363. conditions[:name] = names if names
  364. wspace.services.all(:include => :host, :conditions => conditions, :order => "hosts.address, port")
  365. end
  366. def get_client(opts)
  367. wspace = opts.delete(:workspace) || workspace
  368. host = get_host(:workspace => wspace, :host => opts[:host]) || return
  369. client = host.clients.find(:first, :conditions => {:ua_string => opts[:ua_string]})
  370. return client
  371. end
  372. def find_or_create_client(opts)
  373. report_client(opts.merge({:wait => true}))
  374. end
  375. #
  376. # Report a client running on a host.
  377. #
  378. # opts must contain
  379. # :ua_string -- the value of the User-Agent header
  380. # :host -- the host where this client connected from, can be an ip address or a Host object
  381. #
  382. # opts can contain
  383. # :ua_name -- one of the Msf::HttpClients constants
  384. # :ua_ver -- detected version of the given client
  385. # :campaign -- an id or Campaign object
  386. #
  387. # Returns a Client.
  388. #
  389. def report_client(opts)
  390. return if not active
  391. addr = opts.delete(:host) || return
  392. wspace = opts.delete(:workspace) || workspace
  393. report_host(:workspace => wspace, :host => addr)
  394. wait = opts.delete(:wait)
  395. ret = {}
  396. task = queue(Proc.new {
  397. host = get_host(:workspace => wspace, :host => addr)
  398. client = host.clients.find_or_initialize_by_ua_string(opts[:ua_string])
  399. campaign = opts.delete(:campaign)
  400. if campaign
  401. case campaign
  402. when Campaign
  403. opts[:campaign_id] = campaign.id
  404. else
  405. opts[:campaign_id] = campaign
  406. end
  407. end
  408. opts.each { |k,v|
  409. if (client.attribute_names.include?(k.to_s))
  410. client[k] = v
  411. else
  412. dlog("Unknown attribute for Client: #{k}")
  413. end
  414. }
  415. if (client and client.changed?)
  416. client.save!
  417. end
  418. ret[:client] = client
  419. })
  420. if wait
  421. return nil if task.wait() != :done
  422. return ret[:client]
  423. end
  424. return task
  425. end
  426. #
  427. # This method iterates the vulns table calling the supplied block with the
  428. # vuln instance of each entry.
  429. #
  430. def each_vuln(wspace=workspace,&block)
  431. wspace.vulns.each do |vulns|
  432. block.call(vulns)
  433. end
  434. end
  435. #
  436. # This methods returns a list of all vulnerabilities in the database
  437. #
  438. def vulns(wspace=workspace)
  439. wspace.vulns
  440. end
  441. #
  442. # This methods returns a list of all credentials in the database
  443. #
  444. def creds(wspace=workspace)
  445. Cred.find(
  446. :all,
  447. :include => {:service => :host}, # That's some magic right there.
  448. :conditions => ["hosts.workspace_id = ?", wspace.id]
  449. )
  450. end
  451. #
  452. # This method returns a list of all exploited hosts in the database.
  453. #
  454. def exploited_hosts(wspace=workspace)
  455. wspace.exploited_hosts
  456. end
  457. #
  458. # This method iterates the notes table calling the supplied block with the
  459. # note instance of each entry.
  460. #
  461. def each_note(wspace=workspace, &block)
  462. wspace.notes.each do |note|
  463. block.call(note)
  464. end
  465. end
  466. #
  467. # Find or create a note matching this type/data
  468. #
  469. def find_or_create_note(opts)
  470. report_note(opts.merge({:wait => true}))
  471. end
  472. #
  473. # Report a Note to the database. Notes can be tied to a Workspace, Host, or Service.
  474. #
  475. # opts MUST contain
  476. # :data -- whatever it is you're making a note of
  477. # :type -- The type of note, e.g. smb_peer_os
  478. #
  479. # opts can contain
  480. # :workspace -- the workspace to associate with this Note
  481. # :host -- an IP address or a Host object to associate with this Note
  482. # :service -- a Service object to associate with this Note
  483. # :port -- along with :host and proto, a service to associate with this Note
  484. # :proto -- along with :host and port, a service to associate with this Note
  485. # :update -- what to do in case a similar Note exists, see below
  486. #
  487. # The :update option can have the following values:
  488. # :unique -- allow only a single Note per +host+/+type+ pair
  489. # :unique_data -- like :uniqe, but also compare +data+
  490. # :insert -- always insert a new Note even if one with identical values exists
  491. #
  492. # If the provided :host is an IP address and does not exist in the
  493. # database, it will be created. If :workspace, :host and :service are all
  494. # omitted, the new Note will be associated with the current workspace.
  495. #
  496. def report_note(opts)
  497. return if not active
  498. wait = opts.delete(:wait)
  499. wspace = opts.delete(:workspace) || workspace
  500. seen = opts.delete(:seen) || false
  501. crit = opts.delete(:critical) || false
  502. host = nil
  503. addr = nil
  504. # Report the host so it's there for the Proc to use below
  505. if opts[:host]
  506. if opts[:host].kind_of? Host
  507. host = opts[:host]
  508. else
  509. report_host({:workspace => wspace, :host => opts[:host]})
  510. addr = normalize_host(opts[:host])
  511. end
  512. # Do the same for a service if that's also included.
  513. if (opts[:port])
  514. proto = nil
  515. sname = nil
  516. case opts[:proto].to_s.downcase # Catch incorrect usages
  517. when 'tcp','udp'
  518. proto = opts[:proto]
  519. sname = opts[:sname] if opts[:sname]
  520. when 'dns','snmp','dhcp'
  521. proto = 'udp'
  522. sname = opts[:proto]
  523. else
  524. proto = 'tcp'
  525. sname = opts[:proto]
  526. end
  527. sopts = {
  528. :workspace => wspace,
  529. :host => opts[:host],
  530. :port => opts[:port],
  531. :proto => proto
  532. }
  533. sopts[:name] = sname if sname
  534. report_service(sopts)
  535. end
  536. end
  537. # Update Modes can be :unique, :unique_data, :insert
  538. mode = opts[:update] || :unique
  539. ret = {}
  540. task = queue(Proc.new {
  541. if addr and not host
  542. host = get_host(:workspace => wspace, :host => addr)
  543. end
  544. if host and (opts[:port] and opts[:proto])
  545. service = get_service(wspace, host, opts[:proto], opts[:port])
  546. elsif opts[:service] and opts[:service].kind_of? Service
  547. service = opts[:service]
  548. end
  549. if host
  550. host.updated_at = host.created_at
  551. host.state = HostState::Alive
  552. host.save!
  553. end
  554. ntype = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required")
  555. data = opts[:data] || (raise RuntimeError, "Note :data is required")
  556. method = nil
  557. args = []
  558. note = nil
  559. conditions = { :ntype => ntype }
  560. conditions[:host_id] = host[:id] if host
  561. conditions[:service_id] = service[:id] if service
  562. notes = wspace.notes.find(:all, :conditions => conditions)
  563. case mode
  564. when :unique
  565. # Only one note of this type should exist, make a new one if it
  566. # isn't there. If it is, grab it and overwrite its data.
  567. if notes.empty?
  568. note = wspace.notes.new(conditions)
  569. else
  570. note = notes[0]
  571. end
  572. note.data = data
  573. when :unique_data
  574. # Don't make a new Note with the same data as one that already
  575. # exists for the given: type and (host or service)
  576. notes.each do |n|
  577. # Compare the deserialized data from the table to the raw
  578. # data we're looking for. Because of the serialization we
  579. # can't do this easily or reliably in SQL.
  580. if n.data == data
  581. note = n
  582. break
  583. end
  584. end
  585. if not note
  586. # We didn't find one with the data we're looking for, make
  587. # a new one.
  588. note = wspace.notes.new(conditions.merge(:data => data))
  589. end
  590. else
  591. # Otherwise, assume :insert, which means always make a new one
  592. note = wspace.notes.new
  593. if host
  594. note.host_id = host[:id]
  595. end
  596. if opts[:service] and opts[:service].kind_of? Service
  597. note.service_id = opts[:service][:id]
  598. end
  599. note.seen = seen
  600. note.critical = crit
  601. note.ntype = ntype
  602. note.data = data
  603. end
  604. msf_import_timestamps(opts,note)
  605. note.save!
  606. ret[:note] = note
  607. })
  608. if wait
  609. return nil if task.wait() != :done
  610. return ret[:note]
  611. end
  612. return task
  613. end
  614. #
  615. # This methods returns a list of all notes in the database
  616. #
  617. def notes(wspace=workspace)
  618. wspace.notes
  619. end
  620. # This is only exercised by MSF3 XML importing for now. Needs the wait
  621. # conditions and return hash as well.
  622. def report_host_tag(opts)
  623. name = opts.delete(:name)
  624. raise DBImportError.new("Missing required option :name") unless name
  625. addr = opts.delete(:addr)
  626. raise DBImportError.new("Missing required option :addr") unless addr
  627. wspace = opts.delete(:wspace)
  628. raise DBImportError.new("Missing required option :wspace") unless wspace
  629. host = nil
  630. report_host(:workspace => wspace, :address => addr)
  631. task = queue( Proc.new {
  632. host = get_host(:workspace => wspace, :address => addr)
  633. desc = opts.delete(:desc)
  634. summary = opts.delete(:summary)
  635. detail = opts.delete(:detail)
  636. crit = opts.delete(:crit)
  637. possible_tag = Tag.find(:all,
  638. :include => :hosts,
  639. :conditions => ["hosts.workspace_id = ? and tags.name = ?",
  640. wspace.id,
  641. name
  642. ]
  643. ).first
  644. tag = possible_tag || Tag.new
  645. tag.name = name
  646. tag.desc = desc
  647. tag.report_summary = !!summary
  648. tag.report_detail = !!detail
  649. tag.critical = !!crit
  650. tag.hosts = tag.hosts | [host]
  651. tag.save! if tag.changed?
  652. })
  653. return task
  654. end
  655. # report_auth_info used to create a note, now it creates
  656. # an entry in the creds table. It's much more akin to
  657. # report_vuln() now.
  658. #
  659. # opts must contain
  660. # :host -- an IP address
  661. # :port -- a port number
  662. #
  663. # opts can contain
  664. # :user -- the username
  665. # :pass -- the password, or path to ssh_key
  666. # :ptype -- the type of password (password, hash, or ssh_key)
  667. # :proto -- a transport name for the port
  668. # :sname -- service name
  669. # :active -- by default, a cred is active, unless explicitly false
  670. # :proof -- data used to prove the account is actually active.
  671. #
  672. # Sources: Credentials can be sourced from another credential, or from
  673. # a vulnerability. For example, if an exploit was used to dump the
  674. # smb_hashes, and this credential comes from there, the source_id would
  675. # be the Vuln id (as reported by report_vuln) and the type would be "Vuln".
  676. #
  677. # :source_id -- The Vuln or Cred id of the source of this cred.
  678. # :source_type -- Either Vuln or Cred
  679. #
  680. # TODO: This is written somewhat host-centric, when really the
  681. # Service is the thing. Need to revisit someday.
  682. def report_auth_info(opts={})
  683. return if not active
  684. raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
  685. raise ArgumentError.new("Invalid address for :host") unless validate_ips(opts[:host])
  686. raise ArgumentError.new("Missing required option :port") if opts[:port].nil?
  687. host = opts.delete(:host)
  688. ptype = opts.delete(:type) || "password"
  689. token = [opts.delete(:user), opts.delete(:pass)]
  690. sname = opts.delete(:sname)
  691. port = opts.delete(:port)
  692. proto = opts.delete(:proto) || "tcp"
  693. proof = opts.delete(:proof)
  694. source_id = opts.delete(:source_id)
  695. source_type = opts.delete(:source_type)
  696. duplicate_ok = opts.delete(:duplicate_ok)
  697. # Nil is true for active.
  698. active = (opts[:active] || opts[:active].nil?) ? true : false
  699. wait = opts.delete(:wait)
  700. wspace = opts.delete(:workspace) || workspace
  701. # Service management; assume the user knows what
  702. # he's talking about.
  703. unless service = get_service(wspace, host, proto, port)
  704. report_service(:host => host, :port => port, :proto => proto, :name => sname, :workspace => wspace)
  705. end
  706. ret = {}
  707. task = queue( Proc.new {
  708. # Get the service
  709. service ||= get_service(wspace, host, proto, port)
  710. # If duplicate usernames are okay, find by both user and password (allows
  711. # for actual duplicates to get modified updated_at, sources, etc)
  712. if duplicate_ok
  713. cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
  714. else
  715. # Create the cred by username only (so we can change passwords)
  716. cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype)
  717. end
  718. # Update with the password
  719. cred.pass = (token[1] || "")
  720. # Annotate the credential
  721. cred.ptype = ptype
  722. cred.active = active
  723. # Update the source ID only if there wasn't already one.
  724. if source_id and !cred.source_id
  725. cred.source_id = source_id
  726. cred.source_type = source_type if source_type
  727. end
  728. # Safe proof (lazy way) -- doesn't chop expanded
  729. # characters correctly, but shouldn't ever be a problem.
  730. unless proof.nil?
  731. proof = Rex::Text.to_hex_ascii(proof)
  732. proof = proof[0,4096]
  733. end
  734. cred.proof = proof
  735. # Update the timestamp
  736. if cred.changed?
  737. msf_import_timestamps(opts,cred)
  738. cred.save!
  739. end
  740. # Ensure the updated_at is touched any time report_auth_info is called
  741. # except when it's set explicitly (as it is for imports)
  742. unless opts[:updated_at] || opts["updated_at"]
  743. cred.updated_at = Time.now.utc
  744. cred.save!
  745. end
  746. ret[:cred] = cred
  747. })
  748. if wait
  749. return nil if task.wait() != :done
  750. return ret[:cred]
  751. end
  752. return task
  753. end
  754. alias :report_cred :report_auth_info
  755. alias :report_auth :report_auth_info
  756. #
  757. # Find or create a credential matching this type/data
  758. #
  759. def find_or_create_cred(opts)
  760. report_auth_info(opts.merge({:wait => true}))
  761. end
  762. #
  763. # This method iterates the creds table calling the supplied block with the
  764. # cred instance of each entry.
  765. #
  766. def each_cred(wspace=workspace,&block)
  767. wspace.creds.each do |cred|
  768. block.call(cred)
  769. end
  770. end
  771. def each_exploited_host(wspace=workspace,&block)
  772. wspace.exploited_hosts.each do |eh|
  773. block.call(eh)
  774. end
  775. end
  776. #
  777. # Find or create a vuln matching this service/name
  778. #
  779. def find_or_create_vuln(opts)
  780. report_vuln(opts.merge({:wait => true}))
  781. end
  782. #
  783. # opts must contain
  784. # :host -- the host where this vulnerability resides
  785. # :name -- the scanner-specific id of the vuln (e.g. NEXPOSE-cifs-acct-password-never-expires)
  786. #
  787. # opts can contain
  788. # :info -- a human readable description of the vuln, free-form text
  789. # :refs -- an array of Ref objects or string names of references
  790. #
  791. def report_vuln(opts)
  792. return if not active
  793. raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
  794. raise ArgumentError.new("Deprecated data column for vuln, use .info instead") if opts[:data]
  795. name = opts[:name] || return
  796. info = opts[:info]
  797. wait = opts.delete(:wait)
  798. wspace = opts.delete(:workspace) || workspace
  799. rids = nil
  800. if opts[:refs]
  801. rids = []
  802. opts[:refs].each do |r|
  803. if r.respond_to? :ctx_id
  804. r = r.ctx_id + '-' + r.ctx_val
  805. end
  806. rids << find_or_create_ref(:name => r)
  807. end
  808. end
  809. host = nil
  810. addr = nil
  811. if opts[:host].kind_of? Host
  812. host = opts[:host]
  813. else
  814. report_host({:workspace => wspace, :host => opts[:host]})
  815. addr = normalize_host(opts[:host])
  816. end
  817. ret = {}
  818. task = queue( Proc.new {
  819. if host
  820. host.updated_at = host.created_at
  821. host.state = HostState::Alive
  822. host.save!
  823. else
  824. host = get_host(:workspace => wspace, :address => addr)
  825. end
  826. if info
  827. vuln = host.vulns.find_or_initialize_by_name_and_info(name, info, :include => :refs)
  828. else
  829. vuln = host.vulns.find_or_initialize_by_name(name, :include => :refs)
  830. end
  831. if opts[:port]
  832. proto = nil
  833. case opts[:proto].to_s.downcase # Catch incorrect usages, as in report_note
  834. when 'tcp','udp'
  835. proto = opts[:proto]
  836. when 'dns','snmp','dhcp'
  837. proto = 'udp'
  838. sname = opts[:proto]
  839. else
  840. proto = 'tcp'
  841. sname = opts[:proto]
  842. end
  843. vuln.service = host.services.find_or_create_by_port_and_proto(opts[:port], proto)
  844. end
  845. if rids
  846. vuln.refs << (rids - vuln.refs)
  847. end
  848. if vuln.changed?
  849. msf_import_timestamps(opts,vuln)
  850. vuln.save!
  851. end
  852. ret[:vuln] = vuln
  853. })
  854. if wait
  855. return nil if task.wait() != :done
  856. return ret[:vuln]
  857. end
  858. return task
  859. end
  860. def get_vuln(wspace, host, service, name, data='')
  861. raise RuntimeError, "Not workspace safe: #{caller.inspect}"
  862. vuln = nil
  863. if (service)
  864. vuln = Vuln.find(:first, :conditions => [ "name = ? and service_id = ? and host_id = ?", name, service.id, host.id])
  865. else
  866. vuln = Vuln.find(:first, :conditions => [ "name = ? and host_id = ?", name, host.id])
  867. end
  868. return vuln
  869. end
  870. #
  871. # Find or create a reference matching this name
  872. #
  873. def find_or_create_ref(opts)
  874. ret = {}
  875. ret[:ref] = get_ref(opts[:name])
  876. return ret[:ref] if ret[:ref]
  877. task = queue(Proc.new {
  878. ref = Ref.find_or_initialize_by_name(opts[:name])
  879. if ref and ref.changed?
  880. ref.save!
  881. end
  882. ret[:ref] = ref
  883. })
  884. return nil if task.wait() != :done
  885. return ret[:ref]
  886. end
  887. def get_ref(name)
  888. Ref.find_by_name(name)
  889. end
  890. def report_exploit(opts={})
  891. return if not active
  892. raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
  893. wait = opts[:wait]
  894. wspace = opts.delete(:workspace) || workspace
  895. host = nil
  896. addr = nil
  897. sname = opts.delete(:sname)
  898. port = opts.delete(:port)
  899. proto = opts.delete(:proto) || "tcp"
  900. name = opts.delete(:name)
  901. payload = opts.delete(:payload)
  902. session_uuid = opts.delete(:session_uuid)
  903. if opts[:host].kind_of? Host
  904. host = opts[:host]
  905. else
  906. report_host({:workspace => wspace, :host => opts[:host]})
  907. addr = normalize_host(opts[:host])
  908. end
  909. if opts[:service].kind_of? Service
  910. service = opts[:service]
  911. elsif port
  912. report_service(:host => host, :port => port, :proto => proto, :name => sname)
  913. service = get_service(wspace, host, proto, port)
  914. else
  915. service = nil
  916. end
  917. ret = {}
  918. task = queue(
  919. Proc.new {
  920. if host
  921. host.updated_at = host.created_at
  922. host.state = HostState::Alive
  923. host.save!
  924. else
  925. host = get_host(:workspace => wspace, :address => addr)
  926. end
  927. exploit_info = {
  928. :workspace => wspace,
  929. :host_id => host.id,
  930. :name => name,
  931. :payload => payload,
  932. }
  933. exploit_info[:service_id] = service.id if service
  934. exploit_info[:session_uuid] = session_uuid if session_uuid
  935. exploit_record = ExploitedHost.create(exploit_info)
  936. exploit_record.save!
  937. ret[:exploit] = exploit_record
  938. }
  939. )
  940. if wait
  941. return nil if task.wait() != :done
  942. return ret[:exploit]
  943. end
  944. return task
  945. end
  946. #
  947. # Deletes a host and associated data matching this address/comm
  948. #
  949. def del_host(wspace, address, comm='')
  950. host = wspace.hosts.find_by_address_and_comm(address, comm)
  951. host.destroy if host
  952. end
  953. #
  954. # Deletes a port and associated vulns matching this port
  955. #
  956. def del_service(wspace, address, proto, port, comm='')
  957. host = get_host(:workspace => wspace, :address => address)
  958. return unless host
  959. host.services.all(:conditions => {:proto => proto, :port => port}).each { |s| s.destroy }
  960. end
  961. #
  962. # Find a reference matching this name
  963. #
  964. def has_ref?(name)
  965. Ref.find_by_name(name)
  966. end
  967. #
  968. # Find a vulnerability matching this name
  969. #
  970. def has_vuln?(name)
  971. Vuln.find_by_name(name)
  972. end
  973. #
  974. # Look for an address across all comms
  975. #
  976. def has_host?(wspace,addr)
  977. wspace.hosts.find_by_address(addr)
  978. end
  979. def events(wspace=workspace)
  980. wspace.events.find :all, :order => 'created_at ASC'
  981. end
  982. def report_event(opts = {})
  983. return if not active
  984. wspace = opts.delete(:workspace) || workspace
  985. uname = opts.delete(:username)
  986. if opts[:host]
  987. report_host(:workspace => wspace, :host => opts[:host])
  988. end
  989. framework.db.queue(Proc.new {
  990. opts[:host] = get_host(:workspace => wspace, :host => opts[:host]) if opts[:host]
  991. Event.create(opts.merge(:workspace_id => wspace[:id], :username => uname))
  992. })
  993. end
  994. #
  995. # Loot collection
  996. #
  997. #
  998. # This method iterates the loot table calling the supplied block with the
  999. # instance of each entry.
  1000. #
  1001. def each_loot(wspace=workspace, &block)
  1002. wspace.loots.each do |note|
  1003. block.call(note)
  1004. end
  1005. end
  1006. #
  1007. # Find or create a loot matching this type/data
  1008. #
  1009. def find_or_create_loot(opts)
  1010. report_loot(opts.merge({:wait => true}))
  1011. end
  1012. def report_loot(opts)
  1013. return if not active
  1014. wait = opts.delete(:wait)
  1015. wspace = opts.delete(:workspace) || workspace
  1016. path = opts.delete(:path) || (raise RuntimeError, "A loot :path is required")
  1017. host = nil
  1018. addr = nil
  1019. # Report the host so it's there for the Proc to use below
  1020. if opts[:host]
  1021. if opts[:host].kind_of? Host
  1022. host = opts[:host]
  1023. else
  1024. report_host({:workspace => wspace, :host => opts[:host]})
  1025. addr = normalize_host(opts[:host])
  1026. end
  1027. end
  1028. ret = {}
  1029. task = queue(Proc.new {
  1030. if addr and not host
  1031. host = get_host(:workspace => wspace, :host => addr)
  1032. end
  1033. ltype = opts.delete(:type) || opts.delete(:ltype) || (raise RuntimeError, "A loot :type or :ltype is required")
  1034. ctype = opts.delete(:ctype) || opts.delete(:content_type) || 'text/plain'
  1035. name = opts.delete(:name)
  1036. info = opts.delete(:info)
  1037. data = opts[:data]
  1038. loot = wspace.loots.new
  1039. if host
  1040. loot.host_id = host[:id]
  1041. end
  1042. if opts[:service] and opts[:service].kind_of? Service
  1043. loot.service_id = opts[:service][:id]
  1044. end
  1045. loot.path = path
  1046. loot.ltype = ltype
  1047. loot.content_type = ctype
  1048. loot.data = data
  1049. loot.name = name if name
  1050. loot.info = info if info
  1051. msf_import_timestamps(opts,loot)
  1052. loot.save!
  1053. if !opts[:created_at]
  1054. if host
  1055. host.updated_at = host.created_at
  1056. host.state = HostState::Alive
  1057. host.save!
  1058. end
  1059. end
  1060. ret[:loot] = loot
  1061. })
  1062. if wait
  1063. return nil if task.wait() != :done
  1064. return ret[:loot]
  1065. end
  1066. return task
  1067. end
  1068. #
  1069. # This methods returns a list of all loot in the database
  1070. #
  1071. def loots(wspace=workspace)
  1072. wspace.loots
  1073. end
  1074. #
  1075. # Find or create a task matching this type/data
  1076. #
  1077. def find_or_create_task(opts)
  1078. report_task(opts.merge({:wait => true}))
  1079. end
  1080. def report_task(opts)
  1081. return if not active
  1082. wait = opts.delete(:wait)
  1083. wspace = opts.delete(:workspace) || workspace
  1084. path = opts.delete(:path) || (raise RuntimeError, "A task :path is required")
  1085. ret = {}
  1086. this_task = queue(Proc.new {
  1087. user = opts.delete(:user)
  1088. desc = opts.delete(:desc)
  1089. error = opts.delete(:error)
  1090. info = opts.delete(:info)
  1091. mod = opts.delete(:mod)
  1092. options = opts.delete(:options)
  1093. prog = opts.delete(:prog)
  1094. result = opts.delete(:result)
  1095. completed_at = opts.delete(:completed_at)
  1096. task = wspace.tasks.new
  1097. task.created_by = user
  1098. task.description = desc
  1099. task.error = error if error
  1100. task.info = info
  1101. task.module = mod
  1102. task.options = options
  1103. task.path = path
  1104. task.progress = prog
  1105. task.result = result if result
  1106. msf_import_timestamps(opts,task)
  1107. # Having blank completed_ats, while accurate, will cause unstoppable tasks.
  1108. if completed_at.nil? || completed_at.empty?
  1109. task.completed_at = opts[:updated_at]
  1110. else
  1111. task.completed_at = completed_at
  1112. end
  1113. task.save!
  1114. ret[:task] = task
  1115. })
  1116. if wait
  1117. return nil if this_task.wait() != :done
  1118. return ret[:task]
  1119. end
  1120. return this_task
  1121. end
  1122. #
  1123. # This methods returns a list of all tasks in the database
  1124. #
  1125. def tasks(wspace=workspace)
  1126. wspace.tasks
  1127. end
  1128. #
  1129. # Find or create a task matching this type/data
  1130. #
  1131. def find_or_create_report(opts)
  1132. report_report(opts.merge({:wait => true}))
  1133. end
  1134. def report_report(opts)
  1135. return if not active
  1136. wait = opts.delete(:wait)
  1137. wspace = opts.delete(:workspace) || workspace
  1138. path = opts.delete(:path) || (raise RuntimeError, "A report :path is required")
  1139. ret = {}
  1140. this_task = queue(Proc.new {
  1141. user = opts.delete(:user)
  1142. options = opts.delete(:options)
  1143. rtype = opts.delete(:rtype)
  1144. report = wspace.reports.new
  1145. report.created_by = user
  1146. report.options = options
  1147. report.rtype = rtype
  1148. report.path = path
  1149. msf_import_timestamps(opts,report)
  1150. report.save!
  1151. ret[:task] = report
  1152. })
  1153. if wait
  1154. return nil if this_task.wait() != :done
  1155. return ret[:task]
  1156. end
  1157. return this_task
  1158. end
  1159. #
  1160. # This methods returns a list of all reports in the database
  1161. #
  1162. def reports(wspace=workspace)
  1163. wspace.reports
  1164. end
  1165. #
  1166. # WMAP
  1167. # Support methods
  1168. #
  1169. #
  1170. # Report a Web Site to the database. WebSites must be tied to an existing Service
  1171. #
  1172. # opts MUST contain
  1173. # :service* -- the service object this site should be associated with
  1174. # :vhost -- the virtual host name for this particular web site`
  1175. # If service is NOT specified, the following values are mandatory
  1176. # :host -- the ip address of the server hosting the web site
  1177. # :port -- the port number of the associated web site
  1178. # :ssl -- whether or not SSL is in use on this port
  1179. #
  1180. # These values will be used to create new host and service records
  1181. #
  1182. # opts can contain
  1183. # :options -- a hash of options for accessing this particular web site
  1184. #
  1185. # Duplicate records for a given host, port, vhost combination will be overwritten
  1186. #
  1187. def report_web_site(opts)
  1188. return if not active
  1189. wait = opts.delete(:wait)
  1190. wspace = opts.delete(:workspace) || workspace
  1191. vhost = opts.delete(:vhost)
  1192. addr = nil
  1193. port = nil
  1194. name = nil
  1195. serv = nil
  1196. if opts[:service] and opts[:service].kind_of?(Service)
  1197. serv = opts[:service]
  1198. else
  1199. addr = opts[:host]
  1200. port = opts[:port]
  1201. name = opts[:ssl] ? 'https' : 'http'
  1202. if not (addr and port)
  1203. raise ArgumentError, "report_web_site requires service OR host/port/ssl"
  1204. end
  1205. # Force addr to be the address and not hostname
  1206. addr = Rex::Socket.getaddress(addr)
  1207. end
  1208. ret = {}
  1209. task = queue(Proc.new {
  1210. host = serv ? serv.host : find_or_create_host(
  1211. :workspace => wspace,
  1212. :host => addr,
  1213. :state => Msf::HostState::Alive
  1214. )
  1215. if host.name.to_s.empty?
  1216. host.name = vhost
  1217. host.save!
  1218. end
  1219. serv = serv ? serv : find_or_create_service(
  1220. :workspace => wspace,
  1221. :host => host,
  1222. :port => port,
  1223. :proto => 'tcp',
  1224. :state => 'open'
  1225. )
  1226. # Change the service name if it is blank or it has
  1227. # been explicitly specified.
  1228. if opts.keys.include?(:ssl) or serv.name.to_s.empty?
  1229. name = opts[:ssl] ? 'https' : 'http'
  1230. serv.name = name
  1231. serv.save!
  1232. end
  1233. host.updated_at = host.created_at
  1234. host.state = HostState::Alive
  1235. host.save!
  1236. vhost ||= host.address
  1237. site = WebSite.find_or_initialize_by_vhost_and_service_id(vhost, serv[:id])
  1238. site.options = opts[:options] if opts[:options]
  1239. # XXX:
  1240. msf_import_timestamps(opts, site)
  1241. site.save!
  1242. ret[:web_site] = site
  1243. })
  1244. if wait
  1245. return nil if task.wait() != :done
  1246. return ret[:web_site]
  1247. end
  1248. return task
  1249. end
  1250. #
  1251. # Report a Web Page to the database. WebPage must be tied to an existing Web Site
  1252. #
  1253. # opts MUST contain
  1254. # :web_site* -- the web site object that this page should be associated with
  1255. # :path -- the virtual host name for this particular web site
  1256. # :code -- the http status code from requesting this page
  1257. # :headers -- this is a HASH of headers (lowercase name as key) of ARRAYs of values
  1258. # :body -- the document body of the server response
  1259. # :query -- the query string after the path
  1260. # If web_site is NOT specified, the following values are mandatory
  1261. # :host -- the ip address of the server hosting the web site
  1262. # :port -- the port number of the associated web site
  1263. # :vhost -- the virtual host for this particular web site
  1264. # :ssl -- whether or not SSL is in use on this port
  1265. #
  1266. # These values will be used to create new host, service, and web_site records
  1267. #
  1268. # opts can contain
  1269. # :cookie -- the Set-Cookie headers, merged into a string
  1270. # :auth -- the Authorization headers, merged into a string
  1271. # :ctype -- the Content-Type headers, merged into a string
  1272. # :mtime -- the timestamp returned from the server of the last modification time
  1273. # :location -- the URL that a redirect points to
  1274. #
  1275. # Duplicate records for a given web_site, path, and query combination will be overwritten
  1276. #
  1277. def report_web_page(opts)
  1278. return if not active
  1279. wait = opts.delete(:wait)
  1280. wspace = opts.delete(:workspace) || workspace
  1281. path = opts[:path]
  1282. code = opts[:code].to_i
  1283. body = opts[:body].to_s
  1284. query = opts[:query].to_s
  1285. headers = opts[:headers]
  1286. site = nil
  1287. if not (path and code and body and headers)
  1288. raise ArgumentError, "report_web_page requires the path, query, code, body, and headers parameters"
  1289. end
  1290. if opts[:web_site] and opts[:web_site].kind_of?(WebSite)
  1291. site = opts.delete(:web_site)
  1292. else
  1293. site = report_web_site(
  1294. :workspace => wspace,
  1295. :host => opts[:host], :port => opts[:port],
  1296. :vhost => opts[:host], :ssl => opts[:ssl],
  1297. :wait => true
  1298. )
  1299. if not site
  1300. raise ArgumentError, "report_web_page was unable to create the associated web site"
  1301. end
  1302. end
  1303. ret = {}
  1304. task = queue(Proc.new {
  1305. page = WebPage.find_or_initialize_by_web_site_id_and_path_and_query(site[:id], path, query)
  1306. page.code = code
  1307. page.body = body
  1308. page.headers = headers
  1309. page.cookie = opts[:cookie] if opts[:cookie]
  1310. page.auth = opts[:auth] if opts[:auth]
  1311. page.mtime = opts[:mtime] if opts[:mtime]
  1312. page.ctype = opts[:ctype] if opts[:ctype]
  1313. page.location = opts[:location] if opts[:location]
  1314. msf_import_timestamps(opts, page)
  1315. page.save!
  1316. ret[:web_page] = page
  1317. })
  1318. if wait
  1319. return nil if task.wait() != :done
  1320. return ret[:web_page]
  1321. end
  1322. return task
  1323. end
  1324. #
  1325. # Report a Web Form to the database. WebForm must be tied to an existing Web Site
  1326. #
  1327. # opts MUST contain
  1328. # :web_site* -- the web site object that this page should be associated with
  1329. # :path -- the virtual host name for this particular web site
  1330. # :query -- the query string that is appended to the path (not valid for GET)
  1331. # :method -- the form method, one of GET, POST, or PATH
  1332. # :params -- an ARRAY of all parameters and values specified in the form
  1333. #
  1334. # If web_site is NOT specified, the following values are mandatory
  1335. # :host -- the ip address of the server hosting the web site
  1336. # :port -- the port number of the associated web site
  1337. # :vhost -- the virtual host for this particular web site
  1338. # :ssl -- whether or not SSL is in use on this port
  1339. #
  1340. #
  1341. # Duplicate records for a given web_site, path, method, and params combination will be overwritten
  1342. #
  1343. def report_web_form(opts)
  1344. return if not active
  1345. wait = opts.delete(:wait)
  1346. wspace = opts.delete(:workspace) || workspace
  1347. path = opts[:path]
  1348. meth = opts[:method].to_s.upcase
  1349. para = opts[:params]
  1350. quer = opts[:query].to_s
  1351. site = nil
  1352. if not (path and meth)
  1353. raise ArgumentError, "report_web_form requires the path and method parameters"
  1354. end
  1355. if not %W{GET POST PATH}.include?(meth)
  1356. raise ArgumentError, "report_web_form requires the method to be one of GET, POST, PATH"
  1357. end
  1358. if opts[:web_site] and opts[:web_site].kind_of?(WebSite)
  1359. site = opts.delete(:web_site)
  1360. else
  1361. site = report_web_site(
  1362. :workspace => wspace,
  1363. :host => opts[:host], :port => opts[:port],
  1364. :vhost => opts[:host], :ssl => opts[:ssl],
  1365. :wait => true
  1366. )
  1367. if not site
  1368. raise ArgumentError, "report_web_form was unable to create the associated web site"
  1369. end
  1370. end
  1371. ret = {}
  1372. task = queue(Proc.new {
  1373. # Since one of our serialized fields is used as a unique parameter, we must do the final
  1374. # comparisons through ruby and not SQL.
  1375. form = nil
  1376. WebForm.find_all_by_web_site_id_and_path_and_method_and_query(site[:id], path, meth, quer).each do |xform|
  1377. if xform.params == para
  1378. form = xform
  1379. break
  1380. end
  1381. end
  1382. if not form
  1383. form = WebForm.new
  1384. form.web_site_id = site[:id]
  1385. form.path = path
  1386. form.method = meth
  1387. form.params = para
  1388. form.query = quer
  1389. end
  1390. msf_import_timestamps(opts, form)
  1391. form.save!
  1392. ret[:web_form] = form
  1393. })
  1394. if wait
  1395. return nil if task.wait() != :done
  1396. return ret[:web_form]
  1397. end
  1398. return task
  1399. end
  1400. #
  1401. # Report a Web Vuln to the database. WebVuln must be tied to an existing Web Site
  1402. #
  1403. # opts MUST contain
  1404. # :web_site* -- the web site object that this page should be associated with
  1405. # :path -- the virtual host name for this particular web site
  1406. # :query -- the query string appended to the path (not valid for GET method flaws)
  1407. # :method -- the form method, one of GET, POST, or PATH
  1408. # :params -- an ARRAY of all parameters and values specified in the form
  1409. # :pname -- the specific field where the vulnerability occurs
  1410. # :proof -- the string showing proof of the vulnerability
  1411. # :risk -- an INTEGER value from 0 to 5 indicating the risk (5 is highest)
  1412. # :name -- the string indicating the type of vulnerability
  1413. #
  1414. # If web_site is NOT specified, the following values are mandatory
  1415. # :host -- the ip address of the server hosting the web site
  1416. # :port -- the port number of the associated web site
  1417. # :vhost -- the virtual host for this particular web site
  1418. # :ssl -- whether or not SSL is in use on this port
  1419. #
  1420. #
  1421. # Duplicate records for a given web_site, path, method, pname, and name combination will be overwritten
  1422. #
  1423. def report_web_vuln(opts)
  1424. return if not active
  1425. wait = opts.delete(:wait)
  1426. wspace = opts.delete(:workspace) || workspace
  1427. path = opts[:path]
  1428. meth = opts[:method]
  1429. para = opts[:params] || []
  1430. quer = opts[:query].to_s
  1431. pname = opts[:pname]
  1432. proof = opts[:proof]
  1433. risk = opts[:risk].to_i
  1434. name = opts[:name].to_s.strip
  1435. blame = opts[:blame].to_s.strip
  1436. desc = opts[:description].to_s.strip
  1437. conf = opts[:confidence].to_i
  1438. cat = opts[:category].to_s.strip
  1439. site = nil
  1440. if not (path and meth and proof and pname)
  1441. raise ArgumentError, "report_web_vuln requires the path, method, proof, risk, name, params, and pname parameters. Received #{opts.inspect}"
  1442. end
  1443. if not %W{GET POST PATH}.include?(meth)
  1444. raise ArgumentError, "report_web_vuln requires the method to be one of GET, POST, PATH. Received '#{meth}'"
  1445. end
  1446. if risk < 0 or risk > 5
  1447. raise ArgumentError, "report_web_vuln requires the risk to be between 0 and 5 (inclusive). Received '#{risk}'"
  1448. end
  1449. if conf < 0 or conf > 100
  1450. raise ArgumentError, "report_web_vuln requires the confidence to be between 1 and 100 (inclusive). Received '#{conf}'"
  1451. end
  1452. if cat.empty?
  1453. raise ArgumentError, "report_web_vuln requires the category to be a valid string"
  1454. end
  1455. if name.empty?
  1456. raise ArgumentError, "report_web_vuln requires the name to be a valid string"
  1457. end
  1458. if opts[:web_site] and opts[:web_site].kind_of?(WebSite)
  1459. site = opts.delete(:web_site)
  1460. else
  1461. site = report_web_site(
  1462. :workspace => wspace,
  1463. :host => opts[:host], :port => opts[:port],
  1464. :vhost => opts[:host], :ssl => opts[:ssl],
  1465. :wait => true
  1466. )
  1467. if not site
  1468. raise ArgumentError, "report_web_form was unable to create the associated web site"
  1469. end
  1470. end
  1471. ret = {}
  1472. task = queue(Proc.new {
  1473. meth = meth.to_s.upcase
  1474. vuln = WebVuln.find_or_initialize_by_web_site_id_and_path_and_method_and_pname_and_category_and_query(site[:id], path, meth, pname, cat, quer)
  1475. vuln.name = name
  1476. vuln.risk = risk
  1477. vuln.params = para
  1478. vuln.proof = proof.to_s
  1479. vuln.category = cat
  1480. vuln.blame = blame
  1481. vuln.description = desc
  1482. vuln.confidence = conf
  1483. msf_import_timestamps(opts, vuln)
  1484. vuln.save!
  1485. ret[:web_vuln] = vuln
  1486. })
  1487. if wait
  1488. return nil if task.wait() != :done
  1489. return ret[:web_vuln]
  1490. end
  1491. return task
  1492. end
  1493. #
  1494. # WMAP
  1495. # Selected host
  1496. #
  1497. def selected_host
  1498. selhost = WmapTarget.find(:first, :conditions => ["selected != 0"] )
  1499. if selhost
  1500. return selhost.host
  1501. else
  1502. return
  1503. end
  1504. end
  1505. #
  1506. # WMAP
  1507. # Selected port
  1508. #
  1509. def selected_port
  1510. WmapTarget.find(:first, :conditions => ["selected != 0"] ).port
  1511. end
  1512. #
  1513. # WMAP
  1514. # Selected ssl
  1515. #
  1516. def selected_ssl
  1517. WmapTarget.find(:first, :conditions => ["selected != 0"] ).ssl
  1518. end
  1519. #
  1520. # WMAP
  1521. # Selected id
  1522. #
  1523. def selected_id
  1524. WmapTarget.find(:first, :conditions => ["selected != 0"] ).object_id
  1525. end
  1526. #
  1527. # WMAP
  1528. # This method iterates the requests table identifiying possible targets
  1529. # This method wiil be remove on second phase of db merging.
  1530. #
  1531. def each_distinct_target(&block)
  1532. request_distinct_targets.each do |target|
  1533. block.call(target)
  1534. end
  1535. end
  1536. #
  1537. # WMAP
  1538. # This method returns a list of all possible targets available in requests
  1539. # This method wiil be remove on second phase of db merging.
  1540. #
  1541. def request_distinct_targets
  1542. WmapRequest.find(:all, :select => 'DISTINCT host,address,port,ssl')
  1543. end
  1544. #
  1545. # WMAP
  1546. # This method iterates the requests table returning a list of all requests of a specific target
  1547. #
  1548. def each_request_target_with_path(&block)
  1549. target_requests('AND wmap_requests.path IS NOT NULL').each do |req|
  1550. block.call(req)
  1551. end
  1552. end
  1553. #
  1554. # WMAP
  1555. # This method iterates the requests table returning a list of all requests of a specific target
  1556. #
  1557. def each_request_target_with_query(&block)
  1558. target_requests('AND wmap_requests.query IS NOT NULL').each do |req|
  1559. block.call(req)
  1560. end
  1561. end
  1562. #
  1563. # WMAP
  1564. # This method iterates the requests table returning a list of all requests of a specific target
  1565. #
  1566. def each_request_target_with_body(&block)
  1567. target_requests('AND wmap_requests.body IS NOT NULL').each do |req|
  1568. block.call(req)
  1569. end
  1570. end
  1571. #
  1572. # WMAP
  1573. # This method iterates the requests table returning a list of all requests of a specific target
  1574. #
  1575. def each_request_target_with_headers(&block)
  1576. target_requests('AND wmap_requests.headers IS NOT NULL').each do |req|
  1577. block.call(req)
  1578. end
  1579. end
  1580. #
  1581. # WMAP
  1582. # This method iterates the requests table returning a list of all requests of a specific target
  1583. #
  1584. def each_request_target(&block)
  1585. target_requests('').each do |req|
  1586. block.call(req)
  1587. end
  1588. end
  1589. #
  1590. # WMAP
  1591. # This method returns a list of all requests from target
  1592. #
  1593. def target_requests(extra_condition)
  1594. WmapRequest.find(:all, :conditions => ["wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}",selected_host,selected_port])
  1595. end
  1596. #
  1597. # WMAP
  1598. # This method iterates the requests table calling the supplied block with the
  1599. # request instance of each entry.
  1600. #
  1601. def each_request(&block)
  1602. requests.each do |request|
  1603. block.call(request)
  1604. end
  1605. end
  1606. #
  1607. # WMAP
  1608. # This method allows to query directly the requests table. To be used mainly by modules
  1609. #
  1610. def request_sql(host,port,extra_condition)
  1611. WmapRequest.find(:all, :conditions => ["wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}",host,port])
  1612. end
  1613. #
  1614. # WMAP
  1615. # This methods returns a list of all targets in the database
  1616. #
  1617. def requests
  1618. WmapRequest.find(:all)
  1619. end
  1620. #
  1621. # WMAP
  1622. # This method iterates the targets table calling the supplied block with the
  1623. # target instance of each entry.
  1624. #
  1625. def each_target(&block)
  1626. targets.each do |target|
  1627. block.call(target)
  1628. end
  1629. end
  1630. #
  1631. # WMAP
  1632. # This methods returns a list of all targets in the database
  1633. #
  1634. def targets
  1635. WmapTarget.find(:all)
  1636. end
  1637. #
  1638. # WMAP
  1639. # This methods deletes all targets from targets table in the database
  1640. #
  1641. def delete_all_targets
  1642. WmapTarget.delete_all
  1643. end
  1644. #
  1645. # WMAP
  1646. # Find a target matching this id
  1647. #
  1648. def get_target(id)
  1649. target = WmapTarget.find(:first, :conditions => [ "id = ?", id])
  1650. return target
  1651. end
  1652. #
  1653. # WMAP
  1654. # Create a target
  1655. #
  1656. def create_target(host,port,ssl,sel)
  1657. tar = WmapTarget.create(
  1658. :host => host,
  1659. :address => host,
  1660. :port => port,
  1661. :ssl => ssl,
  1662. :selected => sel
  1663. )
  1664. #framework.events.on_db_target(rec)
  1665. end
  1666. #
  1667. # WMAP
  1668. # Create a request (by hand)
  1669. #
  1670. def create_request(host,port,ssl,meth,path,headers,query,body,respcode,resphead,response)
  1671. req = WmapRequest.create(
  1672. :host => host,
  1673. :address => host,
  1674. :port => port,
  1675. :ssl => ssl,
  1676. :meth => meth,
  1677. :path => path,
  1678. :headers => headers,
  1679. :query => query,
  1680. :body => body,
  1681. :respcode => respcode,
  1682. :resphead => resphead,
  1683. :response => response
  1684. )
  1685. #framework.events.on_db_request(rec)
  1686. end
  1687. #
  1688. # WMAP
  1689. # Quick way to query the database (used by wmap_sql)
  1690. #
  1691. def sql_query(sqlquery)
  1692. ActiveRecord::Base.connection.select_all(sqlquery)
  1693. end
  1694. # Returns a REXML::Document from the given data.
  1695. def rexmlify(data)
  1696. if data.kind_of?(REXML::Document)
  1697. return data
  1698. else
  1699. # Make an attempt to recover from a REXML import fail, since
  1700. # it's better than dying outright.
  1701. begin
  1702. return REXML::Document.new(data)
  1703. rescue REXML::ParseException => e
  1704. dlog("REXML error: Badly formatted XML, attempting to recover. Error was: #{e.inspect}")
  1705. return REXML::Document.new(data.gsub(/([\x00-\x08\x0b\x0c\x0e-\x19\x80-\xff])/){ |x| "\\x%.2x" % x.unpack("C*")[0] })
  1706. end
  1707. end
  1708. end
  1709. # Handles timestamps from Metasploit Express imports.
  1710. def msf_import_timestamps(opts,obj)
  1711. obj.created_at = opts["created_at"] if opts["created_at"]
  1712. obj.created_at = opts[:created_at] if opts[:created_at]
  1713. obj.updated_at = opts["updated_at"] ? opts["updated_at"] : obj.created_at
  1714. obj.updated_at = opts[:updated_at] ? opts[:updated_at] : obj.created_at
  1715. return obj
  1716. end
  1717. ##
  1718. #
  1719. # Import methods
  1720. #
  1721. ##
  1722. #
  1723. # Generic importer that automatically determines the file type being
  1724. # imported. Since this looks for vendor-specific strings in the given
  1725. # file, there shouldn't be any false detections, but no guarantees.
  1726. #
  1727. def import_file(args={}, &block)
  1728. filename = args[:filename] || args['filename']
  1729. wspace = args[:wspace] || args['wspace'] || workspace
  1730. @import_filedata = {}
  1731. @import_filedata[:filename] = filename
  1732. data = ""
  1733. ::File.open(filename, 'rb') do |f|
  1734. data = f.read(f.stat.size)
  1735. end
  1736. case data[0,4]
  1737. when "PK\x03\x04"
  1738. data = Zip::ZipFile.open(filename)
  1739. when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4"
  1740. data = PacketFu::PcapFile.new.readfile(filename)
  1741. end
  1742. if block
  1743. import(args.merge(:data => data)) { |type,data| yield type,data }
  1744. else
  1745. import(args.merge(:data => data))
  1746. end
  1747. end
  1748. # A dispatcher method that figures out the data's file type,
  1749. # and sends it off to the appropriate importer. Note that
  1750. # import_file_detect will raise an error if the filetype
  1751. # is unknown.
  1752. def import(args={}, &block)
  1753. data = args[:data] || args['data']
  1754. wspace = args[:wspace] || args['wspace'] || workspace
  1755. ftype = import_filetype_detect(data)
  1756. yield(:filetype, @import_filedata[:type]) if block
  1757. self.send "import_#{ftype}".to_sym, args, &block
  1758. end
  1759. # Returns one of: :nexpose_simplexml :nexpose_rawxml :nmap_xml :openvas_xml
  1760. # :nessus_xml :nessus_xml_v2 :qualys_xml :msf_xml :nessus_nbe :amap_mlog
  1761. # :amap_log :ip_list, :msf_zip, :libpcap
  1762. # If there is no match, an error is raised instead.
  1763. def import_filetype_detect(data)
  1764. if data and data.kind_of? Zip::ZipFile
  1765. raise DBImportError.new("The zip file provided is empty.") if data.entries.empty?
  1766. @import_filedata ||= {}
  1767. @import_filedata[:zip_filename] = File.split(data.to_s).last
  1768. @import_filedata[:zip_basename] = @import_filedata[:zip_filename].gsub(/\.zip$/,"")
  1769. @import_filedata[:zip_entry_names] = data.entries.map {|x| x.name}
  1770. @import_filedata[:zip_xml] = @import_filedata[:zip_entry_names].grep(/^(.*)_[0-9]+\.xml$/).first
  1771. @import_filedata[:zip_wspace] = @import_filedata[:zip_xml].to_s.match(/^(.*)_[0-9]+\.xml$/)[1]
  1772. @import_filedata[:type] = "Metasploit ZIP Report"
  1773. if @import_filedata[:zip_xml]
  1774. return :msf_zip
  1775. else
  1776. raise DBImportError.new("The zip file provided is not a Metasploit ZIP report")
  1777. end
  1778. end
  1779. if data and data.kind_of? PacketFu::PcapFile
  1780. raise DBImportError.new("The pcap file provided is empty.") if data.body.empty?
  1781. @import_filedata ||= {}
  1782. @import_filedata[:type] = "Libpcap Packet Capture"
  1783. return :libpcap
  1784. end
  1785. # Text string kinds of data.
  1786. if data and data.to_s.strip.size.zero?
  1787. raise DBImportError.new("The data provided to the import function was empty")
  1788. end
  1789. di = data.index("\n")
  1790. firstline = data[0, di]
  1791. @import_filedata ||= {}
  1792. if (firstline.index("<NeXposeSimpleXML"))
  1793. @import_filedata[:type] = "NeXpose Simple XML"
  1794. return :nexpose_simplexml
  1795. elsif (firstline.index("<NexposeReport"))
  1796. @import_filedata[:type] = "NeXpose XML Report"
  1797. return :nexpose_rawxml
  1798. elsif (firstline.index("<scanJob>"))
  1799. @import_filedata[:type] = "Retina XML"
  1800. return :retina_xml
  1801. elsif (firstline.index("<NessusClientData>"))
  1802. @import_filedata[:type] = "Nessus XML (v1)"
  1803. return :nessus_xml
  1804. elsif (firstline.index("<?xml"))
  1805. # it's xml, check for root tags we can handle
  1806. line_count = 0
  1807. data.each_line { |line|
  1808. line =~ /<([a-zA-Z0-9\-\_]+)[ >]/
  1809. case $1
  1810. when "nmaprun"
  1811. @import_filedata[:type] = "Nmap XML"
  1812. return :nmap_xml
  1813. when "openvas-report"
  1814. @import_filedata[:type] = "OpenVAS Report"
  1815. return :openvas_xml
  1816. when "NessusClientData"
  1817. @import_filedata[:type] = "Nessus XML (v1)"
  1818. return :nessus_xml
  1819. when "NessusClientData_v2"
  1820. @import_filedata[:type] = "Nessus XML (v2)"
  1821. return :nessus_xml_v2
  1822. when "SCAN"
  1823. @import_filedata[:type] = "Qualys XML"
  1824. return :qualys_xml
  1825. when /MetasploitExpressV[1234]/
  1826. @import_filedata[:type] = "Metasploit XML"
  1827. return :msf_xml
  1828. when /MetasploitV4/
  1829. @import_filedata[:type] = "Metasploit XML"
  1830. return :msf_xml
  1831. when /netsparker/
  1832. @import_filedata[:type] = "NetSparker XML"
  1833. return :netsparker_xml
  1834. when /audits/
  1835. @import_filedata[:type] = "IP360 XML v3"
  1836. return :ip360_xml_v3
  1837. else
  1838. # Give up if we haven't hit the root tag in the first few lines
  1839. break if line_count > 10
  1840. end
  1841. line_count += 1
  1842. }
  1843. elsif (firstline.index("timestamps|||scan_start"))
  1844. @import_filedata[:type] = "Nessus NBE Report"
  1845. # then it's a nessus nbe
  1846. return :nessus_nbe
  1847. elsif (firstline.index("# amap v"))
  1848. # then it's an amap mlog
  1849. @import_filedata[:type] = "Amap Log -m"
  1850. return :amap_mlog
  1851. elsif (firstline.index("amap v"))
  1852. # then it's an amap log
  1853. @import_filedata[:type] = "Amap Log"
  1854. return :amap_log
  1855. elsif (firstline =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)
  1856. # then its an IP list
  1857. @import_filedata[:type] = "IP Address List"
  1858. return :ip_list
  1859. elsif (data[0,1024].index("<netsparker"))
  1860. @import_filedata[:type] = "NetSparker XML"
  1861. return :netsparker_xml
  1862. elsif (firstline.index("# Metasploit PWDump Export"))
  1863. # then it's a Metasploit PWDump export
  1864. @import_filedata[:type] = "msf_pwdump"
  1865. return :msf_pwdump
  1866. end
  1867. raise DBImportError.new("Could not automatically determine file type")
  1868. end
  1869. # Boils down the validate_import_file to a boolean
  1870. def validate_import_file(data)
  1871. begin
  1872. import_filetype_detect(data)
  1873. rescue DBImportError
  1874. return false
  1875. end
  1876. return true
  1877. end
  1878. def import_libpcap_file(args={})
  1879. filename = args[:filename]
  1880. wspace = args[:wspace] || workspace
  1881. data = PacketFu::PcapFile.new.readfile(filename)
  1882. import_libpcap(args.merge(:data => data))
  1883. end
  1884. # The libpcap file format is handled by PacketFu for data
  1885. # extraction. TODO: Make this its own mixin, and possibly
  1886. # extend PacketFu to do better stream analysis on the fly.
  1887. def import_libpcap(args={}, &block)
  1888. data = args[:data]
  1889. wspace = args[:wspace] || workspace
  1890. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  1891. # seen_hosts is only used for determining when to yield an address. Once we get
  1892. # some packet analysis going, the values will have all sorts of info. The plan
  1893. # is to ru through all the packets as a first pass and report host and service,
  1894. # then, once we have everything parsed, we can reconstruct sessions and ngrep
  1895. # out things like authentication sequences, examine ttl's and window sizes, all
  1896. # kinds of crazy awesome stuff like that.
  1897. seen_hosts = {}
  1898. decoded_packets = 0
  1899. last_count = 0
  1900. data.body.map {|p| p.data}.each do |p|
  1901. if (decoded_packets >= last_count + 1000) and block
  1902. yield(:pcap_count, decoded_packets)
  1903. last_count = decoded_packets
  1904. end
  1905. decoded_packets += 1
  1906. pkt = PacketFu::Packet.parse(p) rescue next # Just silently skip bad packets
  1907. next unless pkt.is_ip? # Skip anything that's not IP. Technically, not Ethernet::Ip
  1908. saddr = pkt.ip_saddr
  1909. daddr = pkt.ip_daddr
  1910. # Handle blacklists and obviously useless IP addresses, and report the host.
  1911. next if (bl | [saddr,daddr]).size == bl.size # Both hosts are blacklisted, skip everything.
  1912. unless( bl.include?(saddr) || rfc3330_reserved(saddr))
  1913. yield(:address,saddr) if block and !seen_hosts.keys.include?(saddr)
  1914. report_host(:workspace => wspace, :host => saddr, :state => Msf::HostState::Alive) unless seen_hosts[saddr]
  1915. seen_hosts[saddr] ||= []
  1916. end
  1917. unless( bl.include?(daddr) || rfc3330_reserved(daddr))
  1918. yield(:address,daddr) if block and !seen_hosts.keys.include?(daddr)
  1919. report_host(:workspace => wspace, :host => daddr, :state => Msf::HostState::Alive) unless seen_hosts[daddr]
  1920. seen_hosts[daddr] ||= []
  1921. end
  1922. if pkt.is_tcp? # First pass on TCP packets
  1923. if (pkt.tcp_flags.syn == 1 and pkt.tcp_flags.ack == 1) or # Oh, this kills me
  1924. pkt.tcp_src < 1024 # If it's a low port, assume it's a proper service.
  1925. if seen_hosts[saddr]
  1926. unless seen_hosts[saddr].include? [pkt.tcp_src,"tcp"]
  1927. report_service(
  1928. :workspace => wspace, :host => saddr,
  1929. :proto => "tcp", :port => pkt.tcp_src,
  1930. :state => Msf::ServiceState::Open
  1931. )
  1932. seen_hosts[saddr] << [pkt.tcp_src,"tcp"]
  1933. yield(:service,"%s:%d/%s" % [saddr,pkt.tcp_src,"tcp"])
  1934. end
  1935. end
  1936. end
  1937. elsif pkt.is_udp? # First pass on UDP packets
  1938. if pkt.udp_src == pkt.udp_dst # Very basic p2p detection.
  1939. [saddr,daddr].each do |xaddr|
  1940. if seen_hosts[xaddr]
  1941. unless seen_hosts[xaddr].include? [pkt.udp_src,"udp"]
  1942. report_service(
  1943. :workspace => wspace, :host => xaddr,
  1944. :proto => "udp", :port => pkt.udp_src,
  1945. :state => Msf::ServiceState::Open
  1946. )
  1947. seen_hosts[xaddr] << [pkt.udp_src,"udp"]
  1948. yield(:service,"%s:%d/%s" % [xaddr,pkt.udp_src,"udp"])
  1949. end
  1950. end
  1951. end
  1952. elsif pkt.udp_src < 1024 # Probably a service
  1953. if seen_hosts[saddr]
  1954. unless seen_hosts[saddr].include? [pkt.udp_src,"udp"]
  1955. report_service(
  1956. :workspace => wspace, :host => saddr,
  1957. :proto => "udp", :port => pkt.udp_src,
  1958. :state => Msf::ServiceState::Open
  1959. )
  1960. seen_hosts[saddr] << [pkt.udp_src,"udp"]
  1961. yield(:service,"%s:%d/%s" % [saddr,pkt.udp_src,"udp"])
  1962. end
  1963. end
  1964. end
  1965. end # tcp or udp
  1966. inspect_single_packet(pkt,wspace)
  1967. end # data.body.map
  1968. # Right about here, we should have built up some streams for some stream analysis.
  1969. # Not sure what form that will take, but people like shoving many hundreds of
  1970. # thousands of packets through this thing, so it'll need to be memory efficient.
  1971. end
  1972. # Do all the single packet analysis we can while churning through the pcap
  1973. # the first time. Multiple packet inspection will come later, where we can
  1974. # do stream analysis, compare requests and responses, etc.
  1975. def inspect_single_packet(pkt,wspace)
  1976. if pkt.is_tcp? or pkt.is_udp?
  1977. inspect_single_packet_http(pkt,wspace)
  1978. end
  1979. end
  1980. # Checks for packets that are headed towards port 80, are tcp, contain an HTTP/1.0
  1981. # line, contains an Authorization line, contains a b64-encoded credential, and
  1982. # extracts it. Reports this credential and solidifies the service as HTTP.
  1983. def inspect_single_packet_http(pkt,wspace)
  1984. # First, check the server side (data from port 80).
  1985. if pkt.is_tcp? and pkt.tcp_src == 80 and !pkt.payload.nil? and !pkt.payload.empty?
  1986. if pkt.payload =~ /^HTTP\x2f1\x2e[01]/
  1987. http_server_match = pkt.payload.match(/\nServer:\s+([^\r\n]+)[\r\n]/)
  1988. if http_server_match.kind_of?(MatchData) and http_server_match[1]
  1989. report_service(
  1990. :workspace => wspace,
  1991. :host => pkt.ip_saddr,
  1992. :port => pkt.tcp_src,
  1993. :proto => "tcp",
  1994. :name => "http",
  1995. :info => http_server_match[1],
  1996. :state => Msf::ServiceState::Open
  1997. )
  1998. # That's all we want to know from this service.
  1999. return :something_significant
  2000. end
  2001. end
  2002. end
  2003. # Next, check the client side (data to port 80)
  2004. if pkt.is_tcp? and pkt.tcp_dst == 80 and !pkt.payload.nil? and !pkt.payload.empty?
  2005. if pkt.payload.match(/[\x00-\x20]HTTP\x2f1\x2e[10]/)
  2006. auth_match = pkt.payload.match(/\nAuthorization:\s+Basic\s+([A-Za-z0-9=\x2b]+)/)
  2007. if auth_match.kind_of?(MatchData) and auth_match[1]
  2008. b64_cred = auth_match[1]
  2009. else
  2010. return false
  2011. end
  2012. # If we're this far, we can surmise that at least the client is a web browser,
  2013. # he thinks the server is HTTP and he just made an authentication attempt. At
  2014. # this point, we'll just believe everything the packet says -- validation ought
  2015. # to come later.
  2016. user,pass = b64_cred.unpack("m*").first.split(/:/,2)
  2017. report_service(
  2018. :workspace => wspace,
  2019. :host => pkt.ip_daddr,
  2020. :port => pkt.tcp_dst,
  2021. :proto => "tcp",
  2022. :name => "http"
  2023. )
  2024. report_auth_info(
  2025. :workspace => wspace,
  2026. :host => pkt.ip_daddr,
  2027. :port => pkt.tcp_dst,
  2028. :proto => "tcp",
  2029. :type => "password",
  2030. :active => true, # Once we can build a stream, determine if the auth was successful. For now, assume it is.
  2031. :user => user,
  2032. :pass => pass
  2033. )
  2034. # That's all we want to know from this service.
  2035. return :something_significant
  2036. end
  2037. end
  2038. end
  2039. #
  2040. # Metasploit PWDump Export
  2041. #
  2042. # This file format is generated by the db_export -f pwdump and
  2043. # the Metasploit Express and Pro report types of "PWDump."
  2044. #
  2045. # This particular block scheme is temporary, since someone is
  2046. # bound to want to import gigantic lists, so we'll want a
  2047. # stream parser eventually (just like the other non-nmap formats).
  2048. #
  2049. # The file format is:
  2050. # # 1.2.3.4:23/tcp (telnet)
  2051. # username password
  2052. # user2 p\x01a\x02ss2
  2053. # <BLANK> pass3
  2054. # user3 <BLANK>
  2055. # smbuser:sid:lmhash:nthash:::
  2056. #
  2057. # Note the leading hash for the host:port line. Note also all usernames
  2058. # and passwords must be in 7-bit ASCII (character sequences of "\x01"
  2059. # will be interpolated -- this includes spaces, which must be notated
  2060. # as "\x20". Blank usernames or passwords should be <BLANK>.
  2061. #
  2062. def import_msf_pwdump(args={}, &block)
  2063. data = args[:data]
  2064. wspace = args[:wspace] || workspace
  2065. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2066. last_host = nil
  2067. addr = nil
  2068. port = nil
  2069. proto = nil
  2070. sname = nil
  2071. ptype = nil
  2072. active = false # Are there cases where imported creds are good? I just hate trusting the import right away.
  2073. data.each_line do |line|
  2074. case line
  2075. when /^[\s]*#/ # Comment lines
  2076. if line[/^#[\s]*([0-9.]+):([0-9]+)(\x2f(tcp|udp))?[\s]*(\x28([^\x29]*)\x29)?/]
  2077. addr = $1
  2078. port = $2
  2079. proto = $4
  2080. sname = $6
  2081. end
  2082. when /^[\s]*Warning:/
  2083. next # Discard warning messages.
  2084. when /^[\s]*([^\s:]+):[0-9]+:([A-Fa-f0-9]+:[A-Fa-f0-9]+):[^\s]*$/ # SMB Hash
  2085. user = ([nil, "<BLANK>"].include?($1)) ? "" : $1
  2086. pass = ([nil, "<BLANK>"].include?($2)) ? "" : $2
  2087. ptype = "smb_hash"
  2088. when /^[\s]*([^\s:]+):([0-9]+):NO PASSWORD\*+:NO PASSWORD\*+[^\s]*$/ # SMB Hash
  2089. user = ([nil, "<BLANK>"].include?($1)) ? "" : $1
  2090. pass = ""
  2091. ptype = "smb_hash"
  2092. when /^[\s]*([\x21-\x7f]+)[\s]+([\x21-\x7f]+)?/ # Must be a user pass
  2093. user = ([nil, "<BLANK>"].include?($1)) ? "" : dehex($1)
  2094. pass = ([nil, "<BLANK>"].include?($2)) ? "" : dehex($2)
  2095. ptype = "password"
  2096. else # Some unknown line not broken by a space.
  2097. next
  2098. end
  2099. next unless [addr,port,user,pass].compact.size == 4
  2100. next unless ipv4_validator(addr) # Skip Malformed addrs
  2101. next unless port[/^[0-9]+$/] # Skip malformed ports
  2102. if bl.include? addr
  2103. next
  2104. else
  2105. yield(:address,addr) if block and addr != last_host
  2106. last_host = addr
  2107. end
  2108. cred_info = {
  2109. :host => addr,
  2110. :port => port,
  2111. :user => user,
  2112. :pass => pass,
  2113. :type => ptype,
  2114. :workspace => wspace
  2115. }
  2116. cred_info[:proto] = proto if proto
  2117. cred_info[:sname] = sname if sname
  2118. cred_info[:active] = active
  2119. report_auth_info(cred_info)
  2120. user = pass = ptype = nil
  2121. end
  2122. end
  2123. # If hex notation is present, turn them into a character.
  2124. def dehex(str)
  2125. hexen = str.scan(/\x5cx[0-9a-fA-F]{2}/)
  2126. hexen.each { |h|
  2127. str.gsub!(h,h[2,2].to_i(16).chr)
  2128. }
  2129. return str
  2130. end
  2131. #
  2132. # Nexpose Simple XML
  2133. #
  2134. # XXX At some point we'll want to make this a stream parser for dealing
  2135. # with large results files
  2136. #
  2137. def import_nexpose_simplexml_file(args={})
  2138. filename = args[:filename]
  2139. wspace = args[:wspace] || workspace
  2140. data = ""
  2141. ::File.open(filename, 'rb') do |f|
  2142. data = f.read(f.stat.size)
  2143. end
  2144. import_nexpose_simplexml(args.merge(:data => data))
  2145. end
  2146. # Import a Metasploit XML file.
  2147. def import_msf_file(args={})
  2148. filename = args[:filename]
  2149. wspace = args[:wspace] || workspace
  2150. data = ""
  2151. ::File.open(filename, 'rb') do |f|
  2152. data = f.read(f.stat.size)
  2153. end
  2154. import_msf_xml(args.merge(:data => data))
  2155. end
  2156. # Import a Metasploit Express ZIP file. Note that this requires
  2157. # a fair bit of filesystem manipulation, and is very much tied
  2158. # up with the Metasploit Express ZIP file format export (for
  2159. # obvious reasons). In the event directories exist, they will
  2160. # be reused. If target files exist, they will be overwritten.
  2161. #
  2162. # XXX: Refactor so it's not quite as sanity-blasting.
  2163. def import_msf_zip(args={}, &block)
  2164. data = args[:data]
  2165. wpsace = args[:wspace] || workspace
  2166. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2167. new_tmp = ::File.join(Dir::tmpdir,"msf","imp_#{Rex::Text::rand_text_alphanumeric(4)}",@import_filedata[:zip_basename])
  2168. if ::File.exists? new_tmp
  2169. unless (::File.directory?(new_tmp) && ::File.writable?(new_tmp))
  2170. raise DBImportError.new("Could not extract zip file to #{new_tmp}")
  2171. end
  2172. else
  2173. FileUtils.mkdir_p(new_tmp)
  2174. end
  2175. @import_filedata[:zip_tmp] = new_tmp
  2176. @import_filedata[:zip_tmp_subdirs] = @import_filedata[:zip_entry_names].map {|x| ::File.split(x)}.map {|x| x[0]}.uniq.reject {|x| x == "."}
  2177. @import_filedata[:zip_tmp_subdirs].each {|sub|
  2178. tmp_subdirs = ::File.join(@import_filedata[:zip_tmp],sub)
  2179. if File.exists? tmp_subdirs
  2180. unless (::File.directory?(tmp_subdirs) && File.writable?(tmp_subdirs))
  2181. raise DBImportError.new("Could not extract zip file to #{tmp_subdirs}")
  2182. end
  2183. else
  2184. ::FileUtils.mkdir(tmp_subdirs)
  2185. end
  2186. }
  2187. data.entries.each do |e|
  2188. target = ::File.join(@import_filedata[:zip_tmp],e.name)
  2189. ::File.unlink target if ::File.exists?(target) # Yep. Deleted.
  2190. data.extract(e,target)
  2191. if target =~ /^.*.xml$/
  2192. target_data = ::File.open(target) {|f| f.read 1024}
  2193. if import_filetype_detect(target_data) == :msf_xml
  2194. @import_filedata[:zip_extracted_xml] = target
  2195. break
  2196. end
  2197. end
  2198. end
  2199. # This will kick the newly-extracted XML file through
  2200. # the import_file process all over again.
  2201. if @import_filedata[:zip_extracted_xml]
  2202. new_args = args.dup
  2203. new_args[:filename] = @import_filedata[:zip_extracted_xml]
  2204. new_args[:data] = nil
  2205. new_args[:ifd] = @import_filedata.dup
  2206. if block
  2207. import_file(new_args, &block)
  2208. else
  2209. import_file(new_args)
  2210. end
  2211. end
  2212. # Kick down to all the MSFX ZIP specific items
  2213. if block
  2214. import_msf_collateral(new_args, &block)
  2215. else
  2216. import_msf_collateral(new_args)
  2217. end
  2218. end
  2219. # Imports loot, tasks, and reports from an MSF ZIP report.
  2220. # XXX: This function is stupidly long. It needs to be refactored.
  2221. def import_msf_collateral(args={}, &block)
  2222. data = ::File.open(args[:filename], "rb") {|f| f.read(f.stat.size)}
  2223. wspace = args[:wspace] || args['wspace'] || workspace
  2224. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2225. basedir = args[:basedir] || args['basedir'] || ::File.join(Msf::Config.install_root, "data", "msf")
  2226. allow_yaml = false
  2227. btag = nil
  2228. doc = rexmlify(data)
  2229. if doc.elements["MetasploitExpressV1"]
  2230. m_ver = 1
  2231. allow_yaml = true
  2232. btag = "MetasploitExpressV1"
  2233. elsif doc.elements["MetasploitExpressV2"]
  2234. m_ver = 2
  2235. allow_yaml = true
  2236. btag = "MetasploitExpressV2"
  2237. elsif doc.elements["MetasploitExpressV3"]
  2238. m_ver = 3
  2239. btag = "MetasploitExpressV3"
  2240. elsif doc.elements["MetasploitExpressV4"]
  2241. m_ver = 4
  2242. btag = "MetasploitExpressV4"
  2243. elsif doc.elements["MetasploitV4"]
  2244. m_ver = 4
  2245. btag = "MetasploitV4"
  2246. else
  2247. m_ver = nil
  2248. end
  2249. unless m_ver and btag
  2250. raise DBImportError.new("Unsupported Metasploit XML document format")
  2251. end
  2252. host_info = {}
  2253. doc.elements.each("/#{btag}/hosts/host") do |host|
  2254. host_info[host.elements["id"].text.to_s.strip] = nils_for_nulls(host.elements["address"].text.to_s.strip)
  2255. end
  2256. # Import Loot
  2257. doc.elements.each("/#{btag}/loots/loot") do |loot|
  2258. next if bl.include? host_info[loot.elements["host-id"].text.to_s.strip]
  2259. loot_info = {}
  2260. loot_info[:host] = host_info[loot.elements["host-id"].text.to_s.strip]
  2261. loot_info[:workspace] = args[:wspace]
  2262. loot_info[:ctype] = nils_for_nulls(loot.elements["content-type"].text.to_s.strip)
  2263. loot_info[:info] = nils_for_nulls(unserialize_object(loot.elements["info"], allow_yaml))
  2264. loot_info[:ltype] = nils_for_nulls(loot.elements["ltype"].text.to_s.strip)
  2265. loot_info[:name] = nils_for_nulls(loot.elements["name"].text.to_s.strip)
  2266. loot_info[:created_at] = nils_for_nulls(loot.elements["created-at"].text.to_s.strip)
  2267. loot_info[:updated_at] = nils_for_nulls(loot.elements["updated-at"].text.to_s.strip)
  2268. loot_info[:name] = nils_for_nulls(loot.elements["name"].text.to_s.strip)
  2269. loot_info[:orig_path] = nils_for_nulls(loot.elements["path"].text.to_s.strip)
  2270. tmp = args[:ifd][:zip_tmp]
  2271. loot_info[:orig_path].gsub!(/^\./,tmp) if loot_info[:orig_path]
  2272. if !loot.elements["service-id"].text.to_s.strip.empty?
  2273. unless loot.elements["service-id"].text.to_s.strip == "NULL"
  2274. loot_info[:service] = loot.elements["service-id"].text.to_s.strip
  2275. end
  2276. end
  2277. # Only report loot if we actually have it.
  2278. # TODO: Copypasta. Seperate this out.
  2279. if ::File.exists? loot_info[:orig_path]
  2280. loot_dir = ::File.join(basedir,"loot")
  2281. loot_file = ::File.split(loot_info[:orig_path]).last
  2282. if ::File.exists? loot_dir
  2283. unless (::File.directory?(loot_dir) && ::File.writable?(loot_dir))
  2284. raise DBImportError.new("Could not move files to #{loot_dir}")
  2285. end
  2286. else
  2287. ::FileUtils.mkdir_p(loot_dir)
  2288. end
  2289. new_loot = ::File.join(loot_dir,loot_file)
  2290. loot_info[:path] = new_loot
  2291. if ::File.exists?(new_loot)
  2292. ::File.unlink new_loot # Delete it, and don't report it.
  2293. else
  2294. report_loot(loot_info) # It's new, so report it.
  2295. end
  2296. ::FileUtils.copy(loot_info[:orig_path], new_loot)
  2297. yield(:msf_loot, new_loot) if block
  2298. end
  2299. end
  2300. # Import Tasks
  2301. doc.elements.each("/#{btag}/tasks/task") do |task|
  2302. task_info = {}
  2303. task_info[:workspace] = args[:wspace]
  2304. # Should user be imported (original) or declared (the importing user)?
  2305. task_info[:user] = nils_for_nulls(task.elements["created-by"].text.to_s.strip)
  2306. task_info[:desc] = nils_for_nulls(task.elements["description"].text.to_s.strip)
  2307. task_info[:info] = nils_for_nulls(unserialize_object(task.elements["info"], allow_yaml))
  2308. task_info[:mod] = nils_for_nulls(task.elements["module"].text.to_s.strip)
  2309. task_info[:options] = nils_for_nulls(task.elements["options"].text.to_s.strip)
  2310. task_info[:prog] = nils_for_nulls(task.elements["progress"].text.to_s.strip).to_i
  2311. task_info[:created_at] = nils_for_nulls(task.elements["created-at"].text.to_s.strip)
  2312. task_info[:updated_at] = nils_for_nulls(task.elements["updated-at"].text.to_s.strip)
  2313. if !task.elements["completed-at"].text.to_s.empty?
  2314. task_info[:completed_at] = nils_for_nulls(task.elements["completed-at"].text.to_s.strip)
  2315. end
  2316. if !task.elements["error"].text.to_s.empty?
  2317. task_info[:error] = nils_for_nulls(task.elements["error"].text.to_s.strip)
  2318. end
  2319. if !task.elements["result"].text.to_s.empty?
  2320. task_info[:result] = nils_for_nulls(task.elements["result"].text.to_s.strip)
  2321. end
  2322. task_info[:orig_path] = nils_for_nulls(task.elements["path"].text.to_s.strip)
  2323. tmp = args[:ifd][:zip_tmp]
  2324. task_info[:orig_path].gsub!(/^\./,tmp) if task_info[:orig_path]
  2325. # Only report a task if we actually have it.
  2326. # TODO: Copypasta. Seperate this out.
  2327. if ::File.exists? task_info[:orig_path]
  2328. tasks_dir = ::File.join(basedir,"tasks")
  2329. task_file = ::File.split(task_info[:orig_path]).last
  2330. if ::File.exists? tasks_dir
  2331. unless (::File.directory?(tasks_dir) && ::File.writable?(tasks_dir))
  2332. raise DBImportError.new("Could not move files to #{tasks_dir}")
  2333. end
  2334. else
  2335. ::FileUtils.mkdir_p(tasks_dir)
  2336. end
  2337. new_task = ::File.join(tasks_dir,task_file)
  2338. task_info[:path] = new_task
  2339. if ::File.exists?(new_task)
  2340. ::File.unlink new_task # Delete it, and don't report it.
  2341. else
  2342. report_task(task_info) # It's new, so report it.
  2343. end
  2344. ::FileUtils.copy(task_info[:orig_path], new_task)
  2345. yield(:msf_task, new_task) if block
  2346. end
  2347. end
  2348. # Import Reports
  2349. doc.elements.each("/#{btag}/reports/report") do |report|
  2350. report_info = {}
  2351. report_info[:workspace] = args[:wspace]
  2352. # Should user be imported (original) or declared (the importing user)?
  2353. report_info[:user] = nils_for_nulls(report.elements["created-by"].text.to_s.strip)
  2354. report_info[:options] = nils_for_nulls(report.elements["options"].text.to_s.strip)
  2355. report_info[:rtype] = nils_for_nulls(report.elements["rtype"].text.to_s.strip)
  2356. report_info[:created_at] = nils_for_nulls(report.elements["created-at"].text.to_s.strip)
  2357. report_info[:updated_at] = nils_for_nulls(report.elements["updated-at"].text.to_s.strip)
  2358. report_info[:orig_path] = nils_for_nulls(report.elements["path"].text.to_s.strip)
  2359. tmp = args[:ifd][:zip_tmp]
  2360. report_info[:orig_path].gsub!(/^\./,tmp) if report_info[:orig_path]
  2361. # Only report a report if we actually have it.
  2362. # TODO: Copypasta. Seperate this out.
  2363. if ::File.exists? report_info[:orig_path]
  2364. reports_dir = ::File.join(basedir,"reports")
  2365. report_file = ::File.split(report_info[:orig_path]).last
  2366. if ::File.exists? reports_dir
  2367. unless (::File.directory?(reports_dir) && ::File.writable?(reports_dir))
  2368. raise DBImportError.new("Could not move files to #{reports_dir}")
  2369. end
  2370. else
  2371. ::FileUtils.mkdir_p(reports_dir)
  2372. end
  2373. new_report = ::File.join(reports_dir,report_file)
  2374. report_info[:path] = new_report
  2375. if ::File.exists?(new_report)
  2376. ::File.unlink new_report
  2377. else
  2378. report_report(report_info)
  2379. end
  2380. ::FileUtils.copy(report_info[:orig_path], new_report)
  2381. yield(:msf_report, new_report) if block
  2382. end
  2383. end
  2384. end
  2385. # For each host, step through services, notes, and vulns, and import
  2386. # them.
  2387. # TODO: loot, tasks, and reports
  2388. def import_msf_xml(args={}, &block)
  2389. data = args[:data]
  2390. wspace = args[:wspace] || workspace
  2391. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2392. allow_yaml = false
  2393. btag = nil
  2394. doc = rexmlify(data)
  2395. if doc.elements["MetasploitExpressV1"]
  2396. m_ver = 1
  2397. allow_yaml = true
  2398. btag = "MetasploitExpressV1"
  2399. elsif doc.elements["MetasploitExpressV2"]
  2400. m_ver = 2
  2401. allow_yaml = true
  2402. btag = "MetasploitExpressV2"
  2403. elsif doc.elements["MetasploitExpressV3"]
  2404. m_ver = 3
  2405. btag = "MetasploitExpressV3"
  2406. elsif doc.elements["MetasploitExpressV4"]
  2407. m_ver = 4
  2408. btag = "MetasploitExpressV4"
  2409. elsif doc.elements["MetasploitV4"]
  2410. m_ver = 4
  2411. btag = "MetasploitV4"
  2412. else
  2413. m_ver = nil
  2414. end
  2415. unless m_ver and btag
  2416. raise DBImportError.new("Unsupported Metasploit XML document format")
  2417. end
  2418. doc.elements.each("/#{btag}/hosts/host") do |host|
  2419. host_data = {}
  2420. host_data[:workspace] = wspace
  2421. host_data[:host] = nils_for_nulls(host.elements["address"].text.to_s.strip)
  2422. if bl.include? host_data[:host]
  2423. next
  2424. else
  2425. yield(:address,host_data[:host]) if block
  2426. end
  2427. host_data[:host_mac] = nils_for_nulls(host.elements["mac"].text.to_s.strip)
  2428. if host.elements["comm"].text
  2429. host_data[:comm] = nils_for_nulls(host.elements["comm"].text.to_s.strip)
  2430. end
  2431. %W{created-at updated-at name state os-flavor os-lang os-name os-sp purpose}.each { |datum|
  2432. if host.elements[datum].text
  2433. host_data[datum.gsub('-','_')] = nils_for_nulls(host.elements[datum].text.to_s.strip)
  2434. end
  2435. }
  2436. host_address = host_data[:host].dup # Preserve after report_host() deletes
  2437. report_host(host_data)
  2438. host.elements.each('services/service') do |service|
  2439. service_data = {}
  2440. service_data[:workspace] = wspace
  2441. service_data[:host] = host_address
  2442. service_data[:port] = nils_for_nulls(service.elements["port"].text.to_s.strip).to_i
  2443. service_data[:proto] = nils_for_nulls(service.elements["proto"].text.to_s.strip)
  2444. %W{created-at updated-at name state info}.each { |datum|
  2445. if service.elements[datum].text
  2446. if datum == "info"
  2447. service_data["info"] = nils_for_nulls(unserialize_object(service.elements[datum], false))
  2448. else
  2449. service_data[datum.gsub("-","_")] = nils_for_nulls(service.elements[datum].text.to_s.strip)
  2450. end
  2451. end
  2452. }
  2453. report_service(service_data)
  2454. end
  2455. host.elements.each('notes/note') do |note|
  2456. note_data = {}
  2457. note_data[:workspace] = wspace
  2458. note_data[:host] = host_address
  2459. note_data[:type] = nils_for_nulls(note.elements["ntype"].text.to_s.strip)
  2460. note_data[:data] = nils_for_nulls(unserialize_object(note.elements["data"], allow_yaml))
  2461. if note.elements["critical"].text
  2462. note_data[:critical] = true unless note.elements["critical"].text.to_s.strip == "NULL"
  2463. end
  2464. if note.elements["seen"].text
  2465. note_data[:seen] = true unless note.elements["critical"].text.to_s.strip == "NULL"
  2466. end
  2467. %W{created-at updated-at}.each { |datum|
  2468. if note.elements[datum].text
  2469. note_data[datum.gsub("-","_")] = nils_for_nulls(note.elements[datum].text.to_s.strip)
  2470. end
  2471. }
  2472. report_note(note_data)
  2473. end
  2474. host.elements.each('tags/tag') do |tag|
  2475. tag_data = {}
  2476. tag_data[:addr] = host_address
  2477. tag_data[:wspace] = wspace
  2478. tag_data[:name] = tag.elements["name"].text.to_s.strip
  2479. tag_data[:desc] = tag.elements["desc"].text.to_s.strip
  2480. if tag.elements["report-summary"].text
  2481. tag_data[:summary] = tag.elements["report-summary"].text.to_s.strip
  2482. end
  2483. if tag.elements["report-detail"].text
  2484. tag_data[:detail] = tag.elements["report-detail"].text.to_s.strip
  2485. end
  2486. if tag.elements["critical"].text
  2487. tag_data[:crit] = true unless tag.elements["critical"].text.to_s.strip == "NULL"
  2488. end
  2489. report_host_tag(tag_data)
  2490. end
  2491. host.elements.each('vulns/vuln') do |vuln|
  2492. vuln_data = {}
  2493. vuln_data[:workspace] = wspace
  2494. vuln_data[:host] = host_address
  2495. vuln_data[:info] = nils_for_nulls(unserialize_object(vuln.elements["info"], allow_yaml))
  2496. vuln_data[:name] = nils_for_nulls(vuln.elements["name"].text.to_s.strip)
  2497. %W{created-at updated-at}.each { |datum|
  2498. if vuln.elements[datum].text
  2499. vuln_data[datum.gsub("-","_")] = nils_for_nulls(vuln.elements[datum].text.to_s.strip)
  2500. end
  2501. }
  2502. report_vuln(vuln_data)
  2503. end
  2504. host.elements.each('creds/cred') do |cred|
  2505. cred_data = {}
  2506. cred_data[:workspace] = wspace
  2507. cred_data[:host] = host_address
  2508. %W{port ptype sname proto proof active user pass}.each {|datum|
  2509. if cred.elements[datum].respond_to? :text
  2510. cred_data[datum.intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
  2511. end
  2512. }
  2513. %W{created-at updated-at}.each { |datum|
  2514. if cred.elements[datum].respond_to? :text
  2515. cred_data[datum.gsub("-","_")] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
  2516. end
  2517. }
  2518. if cred_data[:pass] == "<masked>"
  2519. cred_data[:pass] = ""
  2520. cred_data[:active] = false
  2521. elsif cred_data[:pass] == "*BLANK PASSWORD*"
  2522. cred_data[:pass] = ""
  2523. end
  2524. report_cred(cred_data.merge(:wait => true))
  2525. end
  2526. end
  2527. # Import web sites
  2528. doc.elements.each("/#{btag}/web_sites/web_site") do |web|
  2529. info = {}
  2530. info[:workspace] = wspace
  2531. %W{host port vhost ssl comments}.each do |datum|
  2532. if web.elements[datum].respond_to? :text
  2533. info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  2534. end
  2535. end
  2536. info[:options] = nils_for_nulls(unserialize_object(web.elements["options"], allow_yaml)) if web.elements["options"].respond_to?(:text)
  2537. info[:ssl] = (info[:ssl] and info[:ssl].to_s.strip.downcase == "true") ? true : false
  2538. %W{created-at updated-at}.each { |datum|
  2539. if web.elements[datum].text
  2540. info[datum.gsub("-","_")] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  2541. end
  2542. }
  2543. report_web_site(info)
  2544. yield(:web_site, "#{info[:host]}:#{info[:port]} (#{info[:vhost]})") if block
  2545. end
  2546. %W{page form vuln}.each do |wtype|
  2547. doc.elements.each("/#{btag}/web_#{wtype}s/web_#{wtype}") do |web|
  2548. info = {}
  2549. info[:workspace] = wspace
  2550. info[:host] = nils_for_nulls(web.elements["host"].text.to_s.strip) if web.elements["host"].respond_to?(:text)
  2551. info[:port] = nils_for_nulls(web.elements["port"].text.to_s.strip) if web.elements["port"].respond_to?(:text)
  2552. info[:ssl] = nils_for_nulls(web.elements["ssl"].text.to_s.strip) if web.elements["ssl"].respond_to?(:text)
  2553. info[:vhost] = nils_for_nulls(web.elements["vhost"].text.to_s.strip) if web.elements["vhost"].respond_to?(:text)
  2554. info[:ssl] = (info[:ssl] and info[:ssl].to_s.strip.downcase == "true") ? true : false
  2555. case wtype
  2556. when "page"
  2557. %W{path code body query cookie auth ctype mtime location}.each do |datum|
  2558. if web.elements[datum].respond_to? :text
  2559. info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  2560. end
  2561. end
  2562. info[:headers] = nils_for_nulls(unserialize_object(web.elements["headers"], allow_yaml))
  2563. when "form"
  2564. %W{path query method}.each do |datum|
  2565. if web.elements[datum].respond_to? :text
  2566. info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  2567. end
  2568. end
  2569. info[:params] = nils_for_nulls(unserialize_object(web.elements["params"], allow_yaml))
  2570. when "vuln"
  2571. %W{path query method pname proof risk name blame description category confidence}.each do |datum|
  2572. if web.elements[datum].respond_to? :text
  2573. info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  2574. end
  2575. end
  2576. info[:params] = nils_for_nulls(unserialize_object(web.elements["params"], allow_yaml))
  2577. info[:risk] = info[:risk].to_i
  2578. info[:confidence] = info[:confidence].to_i
  2579. end
  2580. %W{created-at updated-at}.each { |datum|
  2581. if web.elements[datum].text
  2582. info[datum.gsub("-","_")] = nils_for_nulls(web.elements[datum].text.to_s.strip)
  2583. end
  2584. }
  2585. self.send("report_web_#{wtype}", info)
  2586. yield("web_#{wtype}".intern, info[:path]) if block
  2587. end
  2588. end
  2589. end
  2590. # Convert the string "NULL" to actual nil
  2591. def nils_for_nulls(str)
  2592. str == "NULL" ? nil : str
  2593. end
  2594. def import_nexpose_simplexml(args={}, &block)
  2595. data = args[:data]
  2596. wspace = args[:wspace] || workspace
  2597. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2598. doc = rexmlify(data)
  2599. doc.elements.each('/NeXposeSimpleXML/devices/device') do |dev|
  2600. addr = dev.attributes['address'].to_s
  2601. if bl.include? addr
  2602. next
  2603. else
  2604. yield(:address,addr) if block
  2605. end
  2606. fprint = {}
  2607. dev.elements.each('fingerprint/description') do |str|
  2608. fprint[:desc] = str.text.to_s.strip
  2609. end
  2610. dev.elements.each('fingerprint/vendor') do |str|
  2611. fprint[:vendor] = str.text.to_s.strip
  2612. end
  2613. dev.elements.each('fingerprint/family') do |str|
  2614. fprint[:family] = str.text.to_s.strip
  2615. end
  2616. dev.elements.each('fingerprint/product') do |str|
  2617. fprint[:product] = str.text.to_s.strip
  2618. end
  2619. dev.elements.each('fingerprint/version') do |str|
  2620. fprint[:version] = str.text.to_s.strip
  2621. end
  2622. dev.elements.each('fingerprint/architecture') do |str|
  2623. fprint[:arch] = str.text.to_s.upcase.strip
  2624. end
  2625. conf = {
  2626. :workspace => wspace,
  2627. :host => addr,
  2628. :state => Msf::HostState::Alive
  2629. }
  2630. report_host(conf)
  2631. report_note(
  2632. :workspace => wspace,
  2633. :host => addr,
  2634. :type => 'host.os.nexpose_fingerprint',
  2635. :data => fprint
  2636. )
  2637. # Load vulnerabilities not associated with a service
  2638. dev.elements.each('vulnerabilities/vulnerability') do |vuln|
  2639. vid = vuln.attributes['id'].to_s.downcase
  2640. refs = process_nexpose_data_sxml_refs(vuln)
  2641. next if not refs
  2642. report_vuln(
  2643. :workspace => wspace,
  2644. :host => addr,
  2645. :name => 'NEXPOSE-' + vid,
  2646. :info => vid,
  2647. :refs => refs)
  2648. end
  2649. # Load the services
  2650. dev.elements.each('services/service') do |svc|
  2651. sname = svc.attributes['name'].to_s
  2652. sprot = svc.attributes['protocol'].to_s.downcase
  2653. sport = svc.attributes['port'].to_s.to_i
  2654. next if sport == 0
  2655. name = sname.split('(')[0].strip
  2656. info = ''
  2657. svc.elements.each('fingerprint/description') do |str|
  2658. info = str.text.to_s.strip
  2659. end
  2660. if(sname.downcase != '<unknown>')
  2661. report_service(:workspace => wspace, :host => addr, :proto => sprot, :port => sport, :name => name, :info => info)
  2662. else
  2663. report_service(:workspace => wspace, :host => addr, :proto => sprot, :port => sport, :info => info)
  2664. end
  2665. # Load vulnerabilities associated with this service
  2666. svc.elements.each('vulnerabilities/vulnerability') do |vuln|
  2667. vid = vuln.attributes['id'].to_s.downcase
  2668. refs = process_nexpose_data_sxml_refs(vuln)
  2669. next if not refs
  2670. report_vuln(
  2671. :workspace => wspace,
  2672. :host => addr,
  2673. :port => sport,
  2674. :proto => sprot,
  2675. :name => 'NEXPOSE-' + vid,
  2676. :info => vid,
  2677. :refs => refs)
  2678. end
  2679. end
  2680. end
  2681. end
  2682. #
  2683. # Nexpose Raw XML
  2684. #
  2685. def import_nexpose_rawxml_file(args={})
  2686. filename = args[:filename]
  2687. wspace = args[:wspace] || workspace
  2688. data = ""
  2689. ::File.open(filename, 'rb') do |f|
  2690. data = f.read(f.stat.size)
  2691. end
  2692. import_nexpose_rawxml(args.merge(:data => data))
  2693. end
  2694. def import_nexpose_rawxml(args={}, &block)
  2695. data = args[:data]
  2696. wspace = args[:wspace] || workspace
  2697. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2698. # Use a stream parser instead of a tree parser so we can deal with
  2699. # huge results files without running out of memory.
  2700. parser = Rex::Parser::NexposeXMLStreamParser.new
  2701. # Since all the Refs have to be in the database before we can use them
  2702. # in a Vuln, we store all the hosts until we finish parsing and only
  2703. # then put everything in the database. This is memory-intensive for
  2704. # large files, but should be much less so than a tree parser.
  2705. #
  2706. # This method is also considerably faster than parsing through the tree
  2707. # looking for references every time we hit a vuln.
  2708. hosts = []
  2709. vulns = []
  2710. # The callback merely populates our in-memory table of hosts and vulns
  2711. parser.callback = Proc.new { |type, value|
  2712. case type
  2713. when :host
  2714. hosts.push(value)
  2715. when :vuln
  2716. value["id"] = value["id"].downcase if value["id"]
  2717. vulns.push(value)
  2718. end
  2719. }
  2720. REXML::Document.parse_stream(data, parser)
  2721. vuln_refs = nexpose_refs_to_hash(vulns)
  2722. hosts.each do |host|
  2723. if bl.include? host["addr"]
  2724. next
  2725. else
  2726. yield(:address,host["addr"]) if block
  2727. end
  2728. nexpose_host(host, vuln_refs, wspace)
  2729. end
  2730. end
  2731. #
  2732. # Takes an array of vuln hashes, as returned by the NeXpose rawxml stream
  2733. # parser, like:
  2734. # [
  2735. # {"id"=>"winreg-notes-protocol-handler", severity="8", "refs"=>[{"source"=>"BID", "value"=>"10600"}, ...]}
  2736. # {"id"=>"windows-zotob-c", severity="8", "refs"=>[{"source"=>"BID", "value"=>"14513"}, ...]}
  2737. # ]
  2738. # and transforms it into a hash of vuln references keyed on vuln id, like:
  2739. # { "windows-zotob-c" => [{"source"=>"BID", "value"=>"14513"}, ...] }
  2740. #
  2741. # This method ignores all attributes other than the vuln's NeXpose ID and
  2742. # references (including title, severity, et cetera).
  2743. #
  2744. def nexpose_refs_to_hash(vulns)
  2745. refs = {}
  2746. vulns.each do |vuln|
  2747. vuln["refs"].each do |ref|
  2748. refs[vuln['id']] ||= []
  2749. if ref['source'] == 'BID'
  2750. refs[vuln['id']].push('BID-' + ref["value"])
  2751. elsif ref['source'] == 'CVE'
  2752. # value is CVE-$ID
  2753. refs[vuln['id']].push(ref["value"])
  2754. elsif ref['source'] == 'MS'
  2755. refs[vuln['id']].push('MSB-' + ref["value"])
  2756. elsif ref['source'] == 'URL'
  2757. refs[vuln['id']].push('URL-' + ref["value"])
  2758. #else
  2759. # $stdout.puts("Unknown source: #{ref["source"]}")
  2760. end
  2761. end
  2762. end
  2763. refs
  2764. end
  2765. def nexpose_host(h, vuln_refs, wspace)
  2766. data = {:workspace => wspace}
  2767. if h["addr"]
  2768. addr = h["addr"]
  2769. else
  2770. # Can't report it if it doesn't have an IP
  2771. return
  2772. end
  2773. data[:host] = addr
  2774. if (h["hardware-address"])
  2775. # Put colons between each octet of the MAC address
  2776. data[:mac] = h["hardware-address"].gsub(':', '').scan(/../).join(':')
  2777. end
  2778. data[:state] = (h["status"] == "alive") ? Msf::HostState::Alive : Msf::HostState::Dead
  2779. # Since we only have one name field per host in the database, just
  2780. # take the first one.
  2781. if (h["names"] and h["names"].first)
  2782. data[:name] = h["names"].first
  2783. end
  2784. if (data[:state] != Msf::HostState::Dead)
  2785. report_host(data)
  2786. end
  2787. if h["os_family"]
  2788. note = {
  2789. :workspace => wspace,
  2790. :host => addr,
  2791. :type => 'host.os.nexpose_fingerprint',
  2792. :data => {
  2793. :family => h["os_family"],
  2794. :certainty => h["os_certainty"]
  2795. }
  2796. }
  2797. note[:data][:vendor] = h["os_vendor"] if h["os_vendor"]
  2798. note[:data][:product] = h["os_product"] if h["os_product"]
  2799. note[:data][:arch] = h["arch"] if h["arch"]
  2800. report_note(note)
  2801. end
  2802. h["endpoints"].each { |p|
  2803. extra = ""
  2804. extra << p["product"] + " " if p["product"]
  2805. extra << p["version"] + " " if p["version"]
  2806. # Skip port-0 endpoints
  2807. next if p["port"].to_i == 0
  2808. # XXX This should probably be handled in a more standard way
  2809. # extra << "(" + p["certainty"] + " certainty) " if p["certainty"]
  2810. data = {}
  2811. data[:workspace] = wspace
  2812. data[:proto] = p["protocol"].downcase
  2813. data[:port] = p["port"].to_i
  2814. data[:state] = p["status"]
  2815. data[:host] = addr
  2816. data[:info] = extra if not extra.empty?
  2817. if p["name"] != "<unknown>"
  2818. data[:name] = p["name"]
  2819. end
  2820. report_service(data)
  2821. }
  2822. h["vulns"].each_pair { |k,v|
  2823. next if v["status"] !~ /^vulnerable/
  2824. data = {}
  2825. data[:workspace] = wspace
  2826. data[:host] = addr
  2827. data[:proto] = v["protocol"].downcase if v["protocol"]
  2828. data[:port] = v["port"].to_i if v["port"]
  2829. data[:name] = "NEXPOSE-" + v["id"]
  2830. data[:refs] = vuln_refs[v["id"].to_s.downcase]
  2831. report_vuln(data)
  2832. }
  2833. end
  2834. #
  2835. # Retina XML
  2836. #
  2837. # Process a Retina XML file
  2838. def import_retina_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_retina_xml(args.merge(:data => data))
  2846. end
  2847. # Process Retina XML
  2848. def import_retina_xml(args={}, &block)
  2849. data = args[:data]
  2850. wspace = args[:wspace] || workspace
  2851. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2852. parser = Rex::Parser::RetinaXMLStreamParser.new
  2853. parser.on_found_host = Proc.new do |host|
  2854. data = {:workspace => wspace}
  2855. addr = host['address']
  2856. next if not addr
  2857. next if bl.include? addr
  2858. data[:host] = addr
  2859. if host['mac']
  2860. data[:mac] = host['mac']
  2861. end
  2862. data[:state] = Msf::HostState::Alive
  2863. if host['hostname']
  2864. data[:name] = host['hostname']
  2865. end
  2866. if host['netbios']
  2867. data[:name] = host['netbios']
  2868. end
  2869. yield(:address, data[:host]) if block
  2870. # Import Host
  2871. report_host(data)
  2872. report_import_note(wspace, addr)
  2873. # Import OS fingerprint
  2874. if host["os"]
  2875. note = {
  2876. :workspace => wspace,
  2877. :host => addr,
  2878. :type => 'host.os.retina_fingerprint',
  2879. :data => {
  2880. :os => host["os"]
  2881. }
  2882. }
  2883. report_note(note)
  2884. end
  2885. # Import vulnerabilities
  2886. host['vulns'].each do |vuln|
  2887. refs = vuln['refs'].map{|v| v.join("-")}
  2888. refs << "RETINA-#{vuln['rthid']}" if vuln['rthid']
  2889. vuln_info = {
  2890. :workspace => wspace,
  2891. :host => addr,
  2892. :name => vuln['name'],
  2893. :info => vuln['description'],
  2894. :refs => refs
  2895. }
  2896. report_vuln(vuln_info)
  2897. end
  2898. end
  2899. REXML::Document.parse_stream(data, parser)
  2900. end
  2901. #
  2902. # NetSparker XML
  2903. #
  2904. # Process a NetSparker XML file
  2905. def import_netsparker_xml_file(args={})
  2906. filename = args[:filename]
  2907. wspace = args[:wspace] || workspace
  2908. data = ""
  2909. ::File.open(filename, 'rb') do |f|
  2910. data = f.read(f.stat.size)
  2911. end
  2912. import_netsparker_xml(args.merge(:data => data))
  2913. end
  2914. # Process NetSparker XML
  2915. def import_netsparker_xml(args={}, &block)
  2916. data = args[:data]
  2917. wspace = args[:wspace] || workspace
  2918. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  2919. addr = nil
  2920. parser = Rex::Parser::NetSparkerXMLStreamParser.new
  2921. parser.on_found_vuln = Proc.new do |vuln|
  2922. data = {:workspace => wspace}
  2923. # Parse the URL
  2924. url = vuln['url']
  2925. return if not url
  2926. # Crack the URL into a URI
  2927. uri = URI(url) rescue nil
  2928. return if not uri
  2929. # Resolve the host and cache the IP
  2930. if not addr
  2931. baddr = Rex::Socket.addr_aton(uri.host) rescue nil
  2932. if baddr
  2933. addr = Rex::Socket.addr_ntoa(baddr)
  2934. yield(:address, data[:host]) if block
  2935. end
  2936. end
  2937. # Bail early if we have no IP address
  2938. if not addr
  2939. raise Interrupt, "Not a valid IP address"
  2940. end
  2941. if bl.include?(addr)
  2942. raise Interrupt, "IP address is on the blacklist"
  2943. end
  2944. data[:host] = addr
  2945. data[:vhost] = uri.host
  2946. data[:port] = uri.port
  2947. data[:ssl] = (uri.scheme == "ssl")
  2948. body = nil
  2949. # First report a web page
  2950. if vuln['response']
  2951. headers = {}
  2952. code = 200
  2953. head,body = vuln['response'].to_s.split(/\r?\n\r?\n/, 2)
  2954. if body
  2955. if head =~ /^HTTP\d+\.\d+\s+(\d+)\s*/
  2956. code = $1.to_i
  2957. end
  2958. headers = {}
  2959. head.split(/\r?\n/).each do |line|
  2960. hname,hval = line.strip.split(/\s*:\s*/, 2)
  2961. next if hval.to_s.strip.empty?
  2962. headers[hname.downcase] ||= []
  2963. headers[hname.downcase] << hval
  2964. end
  2965. info = {
  2966. :path => uri.path,
  2967. :query => uri.query,
  2968. :code => code,
  2969. :body => body,
  2970. :headers => headers
  2971. }
  2972. info.merge!(data)
  2973. if headers['content-type']
  2974. info[:ctype] = headers['content-type'][0]
  2975. end
  2976. if headers['set-cookie']
  2977. info[:cookie] = headers['set-cookie'].join("\n")
  2978. end
  2979. if headers['authorization']
  2980. info[:auth] = headers['authorization'].join("\n")
  2981. end
  2982. if headers['location']
  2983. info[:location] = headers['location'][0]
  2984. end
  2985. if headers['last-modified']
  2986. info[:mtime] = headers['last-modified'][0]
  2987. end
  2988. # Report the web page to the database
  2989. report_web_page(info)
  2990. yield(:web_page, url) if block
  2991. end
  2992. end # End web_page reporting
  2993. details = netsparker_vulnerability_map(vuln)
  2994. method = netsparker_method_map(vuln)
  2995. pname = netsparker_pname_map(vuln)
  2996. params = netsparker_params_map(vuln)
  2997. proof = ''
  2998. if vuln['info'] and vuln['info'].length > 0
  2999. proof << vuln['info'].map{|x| "#{x[0]}: #{x[1]}\n" }.join + "\n"
  3000. end
  3001. if proof.empty?
  3002. if body
  3003. proof << body + "\n"
  3004. else
  3005. proof << vuln['response'].to_s + "\n"
  3006. end
  3007. end
  3008. if params.empty? and pname
  3009. params = [[pname, vuln['vparam_name'].to_s]]
  3010. end
  3011. info = {
  3012. :path => uri.path,
  3013. :query => uri.query,
  3014. :method => method,
  3015. :params => params,
  3016. :pname => pname.to_s,
  3017. :proof => proof,
  3018. :risk => details[:risk],
  3019. :name => details[:name],
  3020. :blame => details[:blame],
  3021. :category => details[:category],
  3022. :description => details[:description],
  3023. :confidence => details[:confidence],
  3024. }
  3025. info.merge!(data)
  3026. next if vuln['type'].to_s.empty?
  3027. report_web_vuln(info)
  3028. yield(:web_vuln, url) if block
  3029. end
  3030. # We throw interrupts in our parser when the job is hopeless
  3031. begin
  3032. REXML::Document.parse_stream(data, parser)
  3033. rescue ::Interrupt => e
  3034. wlog("The netsparker_xml_import() job was interrupted: #{e}")
  3035. end
  3036. end
  3037. def netsparker_method_map(vuln)
  3038. case vuln['vparam_type']
  3039. when "FullQueryString"
  3040. "GET"
  3041. when "Querystring"
  3042. "GET"
  3043. when "Post"
  3044. "POST"
  3045. when "RawUrlInjection"
  3046. "GET"
  3047. else
  3048. "GET"
  3049. end
  3050. end
  3051. def netsparker_pname_map(vuln)
  3052. case vuln['vparam_name']
  3053. when "URI-BASED", "Query Based"
  3054. "PATH"
  3055. else
  3056. vuln['vparam_name']
  3057. end
  3058. end
  3059. def netsparker_params_map(vuln)
  3060. []
  3061. end
  3062. def netsparker_vulnerability_map(vuln)
  3063. res = {
  3064. :risk => 1,
  3065. :name => 'Information Disclosure',
  3066. :blame => 'System Administrator',
  3067. :category => 'info',
  3068. :description => "This is an information leak",
  3069. :confidence => 100
  3070. }
  3071. # Risk is a value from 1-5 indicating the severity of the issue
  3072. # Examples: 1, 4, 5
  3073. # Name is a descriptive name for this vulnerability.
  3074. # Examples: XSS, ReflectiveXSS, PersistentXSS
  3075. # Blame indicates who is at fault for the vulnerability
  3076. # Examples: App Developer, Server Developer, System Administrator
  3077. # Category indicates the general class of vulnerability
  3078. # Examples: info, xss, sql, rfi, lfi, cmd
  3079. # Description is a textual summary of the vulnerability
  3080. # Examples: "A reflective cross-site scripting attack"
  3081. # "The web server leaks the internal IP address"
  3082. # "The cookie is not set to HTTP-only"
  3083. #
  3084. # Confidence is a value from 1 to 100 indicating how confident the
  3085. # software is that the results are valid.
  3086. # Examples: 100, 90, 75, 15, 10, 0
  3087. case vuln['type'].to_s
  3088. when "ApacheDirectoryListing"
  3089. res = {
  3090. :risk => 1,
  3091. :name => 'Directory Listing',
  3092. :blame => 'System Administrator',
  3093. :category => 'info',
  3094. :description => "",
  3095. :confidence => 100
  3096. }
  3097. when "ApacheMultiViewsEnabled"
  3098. res = {
  3099. :risk => 1,
  3100. :name => 'Apache MultiViews Enabled',
  3101. :blame => 'System Administrator',
  3102. :category => 'info',
  3103. :description => "",
  3104. :confidence => 100
  3105. }
  3106. when "ApacheVersion"
  3107. res = {
  3108. :risk => 1,
  3109. :name => 'Web Server Version',
  3110. :blame => 'System Administrator',
  3111. :category => 'info',
  3112. :description => "",
  3113. :confidence => 100
  3114. }
  3115. when "PHPVersion"
  3116. res = {
  3117. :risk => 1,
  3118. :name => 'PHP Module Version',
  3119. :blame => 'System Administrator',
  3120. :category => 'info',
  3121. :description => "",
  3122. :confidence => 100
  3123. }
  3124. when "AutoCompleteEnabled"
  3125. res = {
  3126. :risk => 1,
  3127. :name => 'Form AutoComplete Enabled',
  3128. :blame => 'App Developer',
  3129. :category => 'info',
  3130. :description => "",
  3131. :confidence => 100
  3132. }
  3133. when "CookieNotMarkedAsHttpOnly"
  3134. res = {
  3135. :risk => 1,
  3136. :name => 'Cookie Not HttpOnly',
  3137. :blame => 'App Developer',
  3138. :category => 'info',
  3139. :description => "",
  3140. :confidence => 100
  3141. }
  3142. when "EmailDisclosure"
  3143. res = {
  3144. :risk => 1,
  3145. :name => 'Email Address Disclosure',
  3146. :blame => 'App Developer',
  3147. :category => 'info',
  3148. :description => "",
  3149. :confidence => 100
  3150. }
  3151. when "ForbiddenResource"
  3152. res = {
  3153. :risk => 1,
  3154. :name => 'Forbidden Resource',
  3155. :blame => 'App Developer',
  3156. :category => 'info',
  3157. :description => "",
  3158. :confidence => 100
  3159. }
  3160. when "FileUploadFound"
  3161. res = {
  3162. :risk => 1,
  3163. :name => 'File Upload Form',
  3164. :blame => 'App Developer',
  3165. :category => 'info',
  3166. :description => "",
  3167. :confidence => 100
  3168. }
  3169. when "PasswordOverHTTP"
  3170. res = {
  3171. :risk => 2,
  3172. :name => 'Password Over HTTP',
  3173. :blame => 'App Developer',
  3174. :category => 'info',
  3175. :description => "",
  3176. :confidence => 100
  3177. }
  3178. when "MySQL5Identified"
  3179. res = {
  3180. :risk => 1,
  3181. :name => 'MySQL 5 Identified',
  3182. :blame => 'App Developer',
  3183. :category => 'info',
  3184. :description => "",
  3185. :confidence => 100
  3186. }
  3187. when "PossibleInternalWindowsPathLeakage"
  3188. res = {
  3189. :risk => 1,
  3190. :name => 'Path Leakage - Windows',
  3191. :blame => 'App Developer',
  3192. :category => 'info',
  3193. :description => "",
  3194. :confidence => 100
  3195. }
  3196. when "PossibleInternalUnixPathLeakage"
  3197. res = {
  3198. :risk => 1,
  3199. :name => 'Path Leakage - Unix',
  3200. :blame => 'App Developer',
  3201. :category => 'info',
  3202. :description => "",
  3203. :confidence => 100
  3204. }
  3205. when "PossibleXSS", "LowPossibilityPermanentXSS", "XSS", "PermanentXSS"
  3206. conf = 100
  3207. conf = 25 if vuln['type'].to_s == "LowPossibilityPermanentXSS"
  3208. conf = 50 if vuln['type'].to_s == "PossibleXSS"
  3209. res = {
  3210. :risk => 3,
  3211. :name => 'Cross-Site Scripting',
  3212. :blame => 'App Developer',
  3213. :category => 'xss',
  3214. :description => "",
  3215. :confidence => conf
  3216. }
  3217. when "ConfirmedBlindSQLInjection", "ConfirmedSQLInjection", "HighlyPossibleSqlInjection", "DatabaseErrorMessages"
  3218. conf = 100
  3219. conf = 90 if vuln['type'].to_s == "HighlyPossibleSqlInjection"
  3220. conf = 25 if vuln['type'].to_s == "DatabaseErrorMessages"
  3221. res = {
  3222. :risk => 5,
  3223. :name => 'SQL Injection',
  3224. :blame => 'App Developer',
  3225. :category => 'sql',
  3226. :description => "",
  3227. :confidence => conf
  3228. }
  3229. else
  3230. conf = 100
  3231. res = {
  3232. :risk => 1,
  3233. :name => vuln['type'].to_s,
  3234. :blame => 'App Developer',
  3235. :category => 'info',
  3236. :description => "",
  3237. :confidence => conf
  3238. }
  3239. end
  3240. res
  3241. end
  3242. #
  3243. # Import Nmap's -oX xml output
  3244. #
  3245. def import_nmap_xml_file(args={})
  3246. filename = args[:filename]
  3247. wspace = args[:wspace] || workspace
  3248. data = ""
  3249. ::File.open(filename, 'rb') do |f|
  3250. data = f.read(f.stat.size)
  3251. end
  3252. import_nmap_xml(args.merge(:data => data))
  3253. end
  3254. # Too many functions in one def! Refactor this.
  3255. def import_nmap_xml(args={}, &block)
  3256. data = args[:data]
  3257. wspace = args[:wspace] || workspace
  3258. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3259. fix_services = args[:fix_services]
  3260. # Use a stream parser instead of a tree parser so we can deal with
  3261. # huge results files without running out of memory.
  3262. parser = Rex::Parser::NmapXMLStreamParser.new
  3263. # Whenever the parser pulls a host out of the nmap results, store
  3264. # it, along with any associated services, in the database.
  3265. parser.on_found_host = Proc.new { |h|
  3266. data = {:workspace => wspace}
  3267. if (h["addrs"].has_key?("ipv4"))
  3268. addr = h["addrs"]["ipv4"]
  3269. elsif (h["addrs"].has_key?("ipv6"))
  3270. addr = h["addrs"]["ipv6"]
  3271. else
  3272. # Can't report it if it doesn't have an IP
  3273. raise RuntimeError, "At least one IPv4 or IPv6 address is required"
  3274. end
  3275. next if bl.include? addr
  3276. data[:host] = addr
  3277. if (h["addrs"].has_key?("mac"))
  3278. data[:mac] = h["addrs"]["mac"]
  3279. end
  3280. data[:state] = (h["status"] == "up") ? Msf::HostState::Alive : Msf::HostState::Dead
  3281. if ( h["reverse_dns"] )
  3282. data[:name] = h["reverse_dns"]
  3283. end
  3284. # Only report alive hosts with ports to speak of.
  3285. if(data[:state] != Msf::HostState::Dead)
  3286. if h["ports"].size > 0
  3287. if fix_services
  3288. port_states = h["ports"].map {|p| p["state"]}.reject {|p| p == "filtered"}
  3289. next if port_states.compact.empty?
  3290. end
  3291. yield(:address,data[:host]) if block
  3292. report_host(data)
  3293. report_import_note(wspace,addr)
  3294. end
  3295. end
  3296. if( h["os_vendor"] )
  3297. note = {
  3298. :workspace => wspace,
  3299. :host => addr,
  3300. :type => 'host.os.nmap_fingerprint',
  3301. :data => {
  3302. :os_vendor => h["os_vendor"],
  3303. :os_family => h["os_family"],
  3304. :os_version => h["os_version"],
  3305. :os_accuracy => h["os_accuracy"]
  3306. }
  3307. }
  3308. if(h["os_match"])
  3309. note[:data][:os_match] = h['os_match']
  3310. end
  3311. report_note(note)
  3312. end
  3313. if (h["last_boot"])
  3314. report_note(
  3315. :workspace => wspace,
  3316. :host => addr,
  3317. :type => 'host.last_boot',
  3318. :data => {
  3319. :time => h["last_boot"]
  3320. }
  3321. )
  3322. end
  3323. if (h["trace"])
  3324. hops = []
  3325. h["trace"]["hops"].each do |hop|
  3326. hops << {
  3327. "ttl" => hop["ttl"].to_i,
  3328. "address" => hop["ipaddr"].to_s,
  3329. "rtt" => hop["rtt"].to_f,
  3330. "name" => hop["host"].to_s
  3331. }
  3332. end
  3333. report_note(
  3334. :workspace => wspace,
  3335. :host => addr,
  3336. :type => 'host.nmap.traceroute',
  3337. :data => {
  3338. 'port' => h["trace"]["port"].to_i,
  3339. 'proto' => h["trace"]["proto"].to_s,
  3340. 'hops' => hops
  3341. }
  3342. )
  3343. end
  3344. # Put all the ports, regardless of state, into the db.
  3345. h["ports"].each { |p|
  3346. # Localhost port results are pretty unreliable -- if it's
  3347. # unknown, it's no good (possibly Windows-only)
  3348. if (
  3349. p["state"] == "unknown" &&
  3350. h["status_reason"] == "localhost-response"
  3351. )
  3352. next
  3353. end
  3354. extra = ""
  3355. extra << p["product"] + " " if p["product"]
  3356. extra << p["version"] + " " if p["version"]
  3357. extra << p["extrainfo"] + " " if p["extrainfo"]
  3358. data = {}
  3359. data[:workspace] = wspace
  3360. if fix_services
  3361. data[:proto] = nmap_msf_service_map(p["protocol"])
  3362. else
  3363. data[:proto] = p["protocol"].downcase
  3364. end
  3365. data[:port] = p["portid"].to_i
  3366. data[:state] = p["state"]
  3367. data[:host] = addr
  3368. data[:info] = extra if not extra.empty?
  3369. if p["name"] != "unknown"
  3370. data[:name] = p["name"]
  3371. end
  3372. report_service(data)
  3373. }
  3374. }
  3375. REXML::Document.parse_stream(data, parser)
  3376. end
  3377. def nmap_msf_service_map(proto)
  3378. return proto unless proto.kind_of? String
  3379. case proto.downcase
  3380. when "msrpc", "nfs-or-iis"; "dcerpc"
  3381. when "netbios-ns"; "netbios"
  3382. when "netbios-ssn", "microsoft-ds"; "smb"
  3383. when "ms-sql-s"; "mssql"
  3384. when "ms-sql-m"; "mssql-m"
  3385. when "postgresql"; "postgres"
  3386. when "http-proxy"; "http"
  3387. when "iiimsf"; "db2"
  3388. else
  3389. proto.downcase
  3390. end
  3391. end
  3392. def report_import_note(wspace,addr)
  3393. if @import_filedata.kind_of?(Hash) && @import_filedata[:filename] && @import_filedata[:filename] !~ /msfe-nmap[0-9]{8}/
  3394. report_note(
  3395. :workspace => wspace,
  3396. :host => addr,
  3397. :type => 'host.imported',
  3398. :data => @import_filedata.merge(:time=> Time.now.utc)
  3399. )
  3400. end
  3401. end
  3402. #
  3403. # Import Nessus NBE files
  3404. #
  3405. def import_nessus_nbe_file(args={})
  3406. filename = args[:filename]
  3407. wspace = args[:wspace] || workspace
  3408. data = ""
  3409. ::File.open(filename, 'rb') do |f|
  3410. data = f.read(f.stat.size)
  3411. end
  3412. import_nessus_nbe(args.merge(:data => data))
  3413. end
  3414. def import_nessus_nbe(args={}, &block)
  3415. data = args[:data]
  3416. wspace = args[:wspace] || workspace
  3417. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3418. nbe_copy = data.dup
  3419. # First pass, just to build the address map.
  3420. addr_map = {}
  3421. nbe_copy.each_line do |line|
  3422. r = line.split('|')
  3423. next if r[0] != 'results'
  3424. next if r[4] != "12053"
  3425. data = r[6]
  3426. addr,hname = data.match(/([0-9\x2e]+) resolves as (.+)\x2e\\n/)[1,2]
  3427. addr_map[hname] = addr
  3428. end
  3429. data.each_line do |line|
  3430. r = line.split('|')
  3431. next if r[0] != 'results'
  3432. hname = r[2]
  3433. if addr_map[hname]
  3434. addr = addr_map[hname]
  3435. else
  3436. addr = hname # Must be unresolved, probably an IP address.
  3437. end
  3438. port = r[3]
  3439. nasl = r[4]
  3440. type = r[5]
  3441. data = r[6]
  3442. # If there's no resolution, or if it's malformed, skip it.
  3443. next unless ipv4_validator(addr)
  3444. if bl.include? addr
  3445. next
  3446. else
  3447. yield(:address,addr) if block
  3448. end
  3449. # Match the NBE types with the XML severity ratings
  3450. case type
  3451. # log messages don't actually have any data, they are just
  3452. # complaints about not being able to perform this or that test
  3453. # because such-and-such was missing
  3454. when "Log Message"; next
  3455. when "Security Hole"; severity = 3
  3456. when "Security Warning"; severity = 2
  3457. when "Security Note"; severity = 1
  3458. # a severity 0 means there's no extra data, it's just an open port
  3459. else; severity = 0
  3460. end
  3461. if nasl == "11936"
  3462. os = data.match(/The remote host is running (.*)\\n/)[1]
  3463. report_note(
  3464. :workspace => wspace,
  3465. :host => addr,
  3466. :type => 'host.os.nessus_fingerprint',
  3467. :data => {
  3468. :os => os.to_s.strip
  3469. }
  3470. )
  3471. end
  3472. handle_nessus(wspace, addr, port, nasl, severity, data)
  3473. end
  3474. end
  3475. #
  3476. # Of course they had to change the nessus format.
  3477. #
  3478. def import_openvas_xml(args={}, &block)
  3479. filename = args[:filename]
  3480. wspace = args[:wspace] || workspace
  3481. raise DBImportError.new("No OpenVAS XML support. Please submit a patch to msfdev[at]metasploit.com")
  3482. end
  3483. #
  3484. # Import IP360 XML v3 output
  3485. #
  3486. def import_ip360_xml_file(args={})
  3487. filename = args[:filename]
  3488. wspace = args[:wspace] || workspace
  3489. data = ""
  3490. ::File.open(filename, 'rb') do |f|
  3491. data = f.read(f.stat.size)
  3492. end
  3493. import_ip360_xml_v3(args.merge(:data => data))
  3494. end
  3495. #
  3496. # Import Nessus XML v1 and v2 output
  3497. #
  3498. # Old versions of openvas exported this as well
  3499. #
  3500. def import_nessus_xml_file(args={})
  3501. filename = args[:filename]
  3502. wspace = args[:wspace] || workspace
  3503. data = ""
  3504. ::File.open(filename, 'rb') do |f|
  3505. data = f.read(f.stat.size)
  3506. end
  3507. if data.index("NessusClientData_v2")
  3508. import_nessus_xml_v2(args.merge(:data => data))
  3509. else
  3510. import_nessus_xml(args.merge(:data => data))
  3511. end
  3512. end
  3513. def import_nessus_xml(args={}, &block)
  3514. data = args[:data]
  3515. wspace = args[:wspace] || workspace
  3516. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3517. doc = rexmlify(data)
  3518. doc.elements.each('/NessusClientData/Report/ReportHost') do |host|
  3519. addr = nil
  3520. hname = nil
  3521. os = nil
  3522. # If the name is resolved, the Nessus plugin for DNS
  3523. # resolution should be there. If not, fall back to the
  3524. # HostName
  3525. host.elements.each('ReportItem') do |item|
  3526. next unless item.elements['pluginID'].text == "12053"
  3527. addr = item.elements['data'].text.match(/([0-9\x2e]+) resolves as/)[1]
  3528. hname = host.elements['HostName'].text
  3529. end
  3530. addr ||= host.elements['HostName'].text
  3531. next unless ipv4_validator(addr) # Skip resolved names and SCAN-ERROR.
  3532. if bl.include? addr
  3533. next
  3534. else
  3535. yield(:address,addr) if block
  3536. end
  3537. hinfo = {
  3538. :workspace => wspace,
  3539. :host => addr
  3540. }
  3541. # Record the hostname
  3542. hinfo.merge!(:name => hname.to_s.strip) if hname
  3543. report_host(hinfo)
  3544. # Record the OS
  3545. os ||= host.elements["os_name"]
  3546. if os
  3547. report_note(
  3548. :workspace => wspace,
  3549. :host => addr,
  3550. :type => 'host.os.nessus_fingerprint',
  3551. :data => {
  3552. :os => os.text.to_s.strip
  3553. }
  3554. )
  3555. end
  3556. host.elements.each('ReportItem') do |item|
  3557. nasl = item.elements['pluginID'].text
  3558. port = item.elements['port'].text
  3559. data = item.elements['data'].text
  3560. severity = item.elements['severity'].text
  3561. handle_nessus(wspace, addr, port, nasl, severity, data)
  3562. end
  3563. end
  3564. end
  3565. def import_nessus_xml_v2(args={}, &block)
  3566. data = args[:data]
  3567. wspace = args[:wspace] || workspace
  3568. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3569. #@host = {
  3570. #'hname' => nil,
  3571. #'addr' => nil,
  3572. #'mac' => nil,
  3573. #'os' => nil,
  3574. #'ports' => [ 'port' => { 'port' => nil,
  3575. # 'svc_name' => nil,
  3576. # 'proto' => nil,
  3577. # 'severity' => nil,
  3578. # 'nasl' => nil,
  3579. # 'description' => nil,
  3580. # 'cve' => [],
  3581. # 'bid' => [],
  3582. # 'xref' => []
  3583. # }
  3584. # ]
  3585. #}
  3586. parser = Rex::Parser::NessusXMLStreamParser.new
  3587. parser.on_found_host = Proc.new { |host|
  3588. addr = host['addr'] || host['hname']
  3589. next unless ipv4_validator(addr) # Catches SCAN-ERROR, among others.
  3590. if bl.include? addr
  3591. next
  3592. else
  3593. yield(:address,addr) if block
  3594. end
  3595. os = host['os']
  3596. yield(:os,os) if block
  3597. if os
  3598. report_note(
  3599. :workspace => wspace,
  3600. :host => addr,
  3601. :type => 'host.os.nessus_fingerprint',
  3602. :data => {
  3603. :os => os.to_s.strip
  3604. }
  3605. )
  3606. end
  3607. hname = host['hname']
  3608. if hname
  3609. report_host(
  3610. :workspace => wspace,
  3611. :host => addr,
  3612. :name => hname.to_s.strip
  3613. )
  3614. end
  3615. mac = host['mac']
  3616. if mac
  3617. report_host(
  3618. :workspace => wspace,
  3619. :host => addr,
  3620. :mac => mac.to_s.strip.upcase
  3621. )
  3622. end
  3623. host['ports'].each do |item|
  3624. next if item['port'] == 0
  3625. msf = nil
  3626. nasl = item['nasl'].to_s
  3627. port = item['port'].to_s
  3628. proto = item['proto'] || "tcp"
  3629. name = item['svc_name']
  3630. severity = item['severity']
  3631. description = item['description']
  3632. cve = item['cve']
  3633. bid = item['bid']
  3634. xref = item['xref']
  3635. msf = item['msf']
  3636. yield(:port,port) if block
  3637. handle_nessus_v2(wspace, addr, port, proto, hname, nasl, severity, description, cve, bid, xref, msf)
  3638. end
  3639. yield(:end,hname) if block
  3640. }
  3641. REXML::Document.parse_stream(data, parser)
  3642. end
  3643. #
  3644. # Import IP360's xml output
  3645. #
  3646. def import_ip360_xml_v3(args={}, &block)
  3647. data = args[:data]
  3648. wspace = args[:wspace] || workspace
  3649. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3650. # @aspl = {'vulns' => {'name' => { }, 'cve' => { }, 'bid' => { } }
  3651. # 'oses' => {'name' } }
  3652. aspl_path = File.join(Msf::Config.data_directory, "ncircle", "ip360.aspl")
  3653. if not ::File.exist?(aspl_path)
  3654. raise DBImportError.new("The nCircle IP360 ASPL file is not present.\n Download ASPL from nCircle VNE | Administer | Support | Resources, unzip it, and save it as " + aspl_path)
  3655. end
  3656. if not ::File.readable?(aspl_path)
  3657. raise DBImportError.new("Could not read the IP360 ASPL XML file provided at " + aspl_path)
  3658. end
  3659. # parse nCircle ASPL file
  3660. aspl = ""
  3661. ::File.open(aspl_path, "rb") do |f|
  3662. aspl = f.read(f.stat.size)
  3663. end
  3664. @asplhash = nil
  3665. parser = Rex::Parser::IP360ASPLXMLStreamParser.new
  3666. parser.on_found_aspl = Proc.new { |asplh|
  3667. @asplhash = asplh
  3668. }
  3669. REXML::Document.parse_stream(aspl, parser)
  3670. #@host = {'hname' => nil, 'addr' => nil, 'mac' => nil, 'os' => nil, 'hid' => nil,
  3671. # 'vulns' => ['vuln' => {'vulnid' => nil, 'port' => nil, 'proto' => nil } ],
  3672. # 'apps' => ['app' => {'appid' => nil, 'svcid' => nil, 'port' => nil, 'proto' => nil } ],
  3673. # 'shares' => []
  3674. # }
  3675. # nCircle has some quotes escaped which causes the parser to break
  3676. # we don't need these lines so just replace \" with "
  3677. data.gsub!(/\\"/,'"')
  3678. # parse nCircle Scan Output
  3679. parser = Rex::Parser::IP360XMLStreamParser.new
  3680. parser.on_found_host = Proc.new { |host|
  3681. addr = host['addr'] || host['hname']
  3682. next unless ipv4_validator(addr) # Catches SCAN-ERROR, among others.
  3683. if bl.include? addr
  3684. next
  3685. else
  3686. yield(:address,addr) if block
  3687. end
  3688. os = host['os']
  3689. yield(:os, os) if block
  3690. if os
  3691. report_note(
  3692. :workspace => wspace,
  3693. :host => addr,
  3694. :type => 'host.os.ip360_fingerprint',
  3695. :data => {
  3696. :os => @asplhash['oses'][os].to_s.strip
  3697. }
  3698. )
  3699. end
  3700. hname = host['hname']
  3701. if hname
  3702. report_host(
  3703. :workspace => wspace,
  3704. :host => addr,
  3705. :name => hname.to_s.strip
  3706. )
  3707. end
  3708. mac = host['mac']
  3709. if mac
  3710. report_host(
  3711. :workspace => wspace,
  3712. :host => addr,
  3713. :mac => mac.to_s.strip.upcase
  3714. )
  3715. end
  3716. host['apps'].each do |item|
  3717. port = item['port'].to_s
  3718. proto = item['proto'].to_s
  3719. handle_ip360_v3_svc(wspace, addr, port, proto, hname)
  3720. end
  3721. host['vulns'].each do |item|
  3722. vulnid = item['vulnid'].to_s
  3723. port = item['port'].to_s
  3724. proto = item['proto'] || "tcp"
  3725. vulnname = @asplhash['vulns']['name'][vulnid]
  3726. cves = @asplhash['vulns']['cve'][vulnid]
  3727. bids = @asplhash['vulns']['bid'][vulnid]
  3728. yield(:port, port) if block
  3729. handle_ip360_v3_vuln(wspace, addr, port, proto, hname, vulnid, vulnname, cves, bids)
  3730. end
  3731. yield(:end, hname) if block
  3732. }
  3733. REXML::Document.parse_stream(data, parser)
  3734. end
  3735. #
  3736. # Import Qualys' xml output
  3737. #
  3738. def import_qualys_xml_file(args={})
  3739. filename = args[:filename]
  3740. wspace = args[:wspace] || workspace
  3741. data = ""
  3742. ::File.open(filename, 'rb') do |f|
  3743. data = f.read(f.stat.size)
  3744. end
  3745. import_qualys_xml(args.merge(:data => data))
  3746. end
  3747. def import_qualys_xml(args={}, &block)
  3748. data = args[:data]
  3749. wspace = args[:wspace] || workspace
  3750. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3751. doc = rexmlify(data)
  3752. doc.elements.each('/SCAN/IP') do |host|
  3753. addr = host.attributes['value']
  3754. if bl.include? addr
  3755. next
  3756. else
  3757. yield(:address,addr) if block
  3758. end
  3759. hname = host.attributes['name'] || ''
  3760. report_host(:workspace => wspace, :host => addr, :name => hname, :state => Msf::HostState::Alive)
  3761. if host.elements["OS"]
  3762. hos = host.elements["OS"].text
  3763. report_note(
  3764. :workspace => wspace,
  3765. :host => addr,
  3766. :type => 'host.os.qualys_fingerprint',
  3767. :data => {
  3768. :os => hos
  3769. }
  3770. )
  3771. end
  3772. # Open TCP Services List (Qualys ID 82023)
  3773. services_tcp = host.elements["SERVICES/CAT/SERVICE[@number='82023']/RESULT"]
  3774. if services_tcp
  3775. services_tcp.text.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match|
  3776. if match[2] == nil or match[2].strip == 'unknown'
  3777. name = match[1].strip
  3778. else
  3779. name = match[2].strip
  3780. end
  3781. handle_qualys(wspace, addr, match[0].to_s, 'tcp', 0, nil, nil, name)
  3782. end
  3783. end
  3784. # Open UDP Services List (Qualys ID 82004)
  3785. services_udp = host.elements["SERVICES/CAT/SERVICE[@number='82004']/RESULT"]
  3786. if services_udp
  3787. services_udp.text.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match|
  3788. if match[2] == nil or match[2].strip == 'unknown'
  3789. name = match[1].strip
  3790. else
  3791. name = match[2].strip
  3792. end
  3793. handle_qualys(wspace, addr, match[0].to_s, 'udp', 0, nil, nil, name)
  3794. end
  3795. end
  3796. # VULNS are confirmed, PRACTICES are unconfirmed vulnerabilities
  3797. host.elements.each('VULNS/CAT | PRACTICES/CAT') do |cat|
  3798. port = cat.attributes['port']
  3799. protocol = cat.attributes['protocol']
  3800. cat.elements.each('VULN | PRACTICE') do |vuln|
  3801. refs = []
  3802. qid = vuln.attributes['number']
  3803. severity = vuln.attributes['severity']
  3804. vuln.elements.each('VENDOR_REFERENCE_LIST/VENDOR_REFERENCE') do |ref|
  3805. refs.push(ref.elements['ID'].text.to_s)
  3806. end
  3807. vuln.elements.each('CVE_ID_LIST/CVE_ID') do |ref|
  3808. refs.push('CVE-' + /C..-([0-9\-]{9})/.match(ref.elements['ID'].text.to_s)[1])
  3809. end
  3810. vuln.elements.each('BUGTRAQ_ID_LIST/BUGTRAQ_ID') do |ref|
  3811. refs.push('BID-' + ref.elements['ID'].text.to_s)
  3812. end
  3813. handle_qualys(wspace, addr, port, protocol, qid, severity, refs)
  3814. end
  3815. end
  3816. end
  3817. end
  3818. def import_ip_list_file(args={})
  3819. filename = args[:filename]
  3820. wspace = args[:wspace] || workspace
  3821. data = ""
  3822. ::File.open(filename, 'rb') do |f|
  3823. data = f.read(f.stat.size)
  3824. end
  3825. import_ip_list(args.merge(:data => data))
  3826. end
  3827. def import_ip_list(args={}, &block)
  3828. data = args[:data]
  3829. wspace = args[:wspace] || workspace
  3830. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3831. data.each_line do |ip|
  3832. ip.strip!
  3833. if bl.include? ip
  3834. next
  3835. else
  3836. yield(:address,ip) if block
  3837. end
  3838. host = find_or_create_host(:workspace => wspace, :host=> ip, :state => Msf::HostState::Alive)
  3839. end
  3840. end
  3841. def import_amap_log_file(args={})
  3842. filename = args[:filename]
  3843. wspace = args[:wspace] || workspace
  3844. data = ""
  3845. ::File.open(filename, 'rb') do |f|
  3846. data = f.read(f.stat.size)
  3847. end
  3848. case import_filetype_detect(data)
  3849. when :amap_log
  3850. import_amap_log(args.merge(:data => data))
  3851. when :amap_mlog
  3852. import_amap_mlog(args.merge(:data => data))
  3853. else
  3854. raise DBImportError.new("Could not determine file type")
  3855. end
  3856. end
  3857. def import_amap_log(args={}, &block)
  3858. data = args[:data]
  3859. wspace = args[:wspace] || workspace
  3860. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3861. data.each_line do |line|
  3862. next if line =~ /^#/
  3863. next if line !~ /^Protocol on ([^:]+):([^\x5c\x2f]+)[\x5c\x2f](tcp|udp) matches (.*)$/
  3864. addr = $1
  3865. next if bl.include? addr
  3866. port = $2.to_i
  3867. proto = $3.downcase
  3868. name = $4
  3869. host = find_or_create_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
  3870. next if not host
  3871. yield(:address,addr) if block
  3872. info = {
  3873. :workspace => wspace,
  3874. :host => host,
  3875. :proto => proto,
  3876. :port => port
  3877. }
  3878. if name != "unidentified"
  3879. info[:name] = name
  3880. end
  3881. service = find_or_create_service(info)
  3882. end
  3883. end
  3884. def import_amap_mlog(args={}, &block)
  3885. data = args[:data]
  3886. wspace = args[:wspace] || workspace
  3887. bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  3888. data.each_line do |line|
  3889. next if line =~ /^#/
  3890. r = line.split(':')
  3891. next if r.length < 6
  3892. addr = r[0]
  3893. next if bl.include? addr
  3894. port = r[1].to_i
  3895. proto = r[2].downcase
  3896. status = r[3]
  3897. name = r[5]
  3898. next if status != "open"
  3899. host = find_or_create_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
  3900. next if not host
  3901. yield(:address,addr) if block
  3902. info = {
  3903. :workspace => wspace,
  3904. :host => host,
  3905. :proto => proto,
  3906. :port => port
  3907. }
  3908. if name != "unidentified"
  3909. info[:name] = name
  3910. end
  3911. service = find_or_create_service(info)
  3912. end
  3913. end
  3914. def unserialize_object(xml_elem, allow_yaml = false)
  3915. string = xml_elem.text.to_s.strip
  3916. return string unless string.is_a?(String)
  3917. return nil if not string
  3918. return nil if string.empty?
  3919. begin
  3920. # Validate that it is properly formed base64 first
  3921. if string.gsub(/\s+/, '') =~ /^([a-z0-9A-Z\+\/=]+)$/
  3922. Marshal.load($1.unpack("m")[0])
  3923. else
  3924. if allow_yaml
  3925. begin
  3926. YAML.load(string)
  3927. rescue
  3928. dlog("Badly formatted YAML: '#{string}'")
  3929. string
  3930. end
  3931. else
  3932. string
  3933. end
  3934. end
  3935. rescue ::Exception => e
  3936. if allow_yaml
  3937. YAML.load(string) rescue string
  3938. else
  3939. string
  3940. end
  3941. end
  3942. end
  3943. def normalize_host(host)
  3944. # If the host parameter is a Session, try to extract its address
  3945. if host.respond_to?('target_host')
  3946. thost = host.target_host
  3947. tpeer = host.tunnel_peer
  3948. if tpeer and (!thost or thost.empty?)
  3949. thost = tpeer.split(":")[0]
  3950. end
  3951. host = thost
  3952. end
  3953. host
  3954. end
  3955. protected
  3956. #
  3957. # This holds all of the shared parsing/handling used by the
  3958. # Nessus NBE and NESSUS v1 methods
  3959. #
  3960. def handle_nessus(wspace, addr, port, nasl, severity, data)
  3961. # The port section looks like:
  3962. # http (80/tcp)
  3963. p = port.match(/^([^\(]+)\((\d+)\/([^\)]+)\)/)
  3964. return if not p
  3965. report_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
  3966. name = p[1].strip
  3967. port = p[2].to_i
  3968. proto = p[3].downcase
  3969. info = { :workspace => wspace, :host => addr, :port => port, :proto => proto }
  3970. if name != "unknown" and name[-1,1] != "?"
  3971. info[:name] = name
  3972. end
  3973. report_service(info)
  3974. return if not nasl
  3975. data.gsub!("\\n", "\n")
  3976. refs = []
  3977. if (data =~ /^CVE : (.*)$/)
  3978. $1.gsub(/C(VE|AN)\-/, '').split(',').map { |r| r.strip }.each do |r|
  3979. refs.push('CVE-' + r)
  3980. end
  3981. end
  3982. if (data =~ /^BID : (.*)$/)
  3983. $1.split(',').map { |r| r.strip }.each do |r|
  3984. refs.push('BID-' + r)
  3985. end
  3986. end
  3987. if (data =~ /^Other references : (.*)$/)
  3988. $1.split(',').map { |r| r.strip }.each do |r|
  3989. ref_id, ref_val = r.split(':')
  3990. ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id)
  3991. end
  3992. end
  3993. nss = 'NSS-' + nasl.to_s
  3994. vuln_info = {
  3995. :workspace => wspace,
  3996. :host => addr,
  3997. :port => port,
  3998. :proto => proto,
  3999. :name => nss,
  4000. :info => data,
  4001. :refs => refs
  4002. }
  4003. report_vuln(vuln_info)
  4004. end
  4005. #
  4006. # NESSUS v2 file format has a dramatically different layout
  4007. # for ReportItem data
  4008. #
  4009. def handle_nessus_v2(wspace,addr,port,proto,name,nasl,severity,description,cve,bid,xref,msf)
  4010. report_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
  4011. info = { :workspace => wspace, :host => addr, :port => port, :proto => proto }
  4012. if name != "unknown" and name[-1,1] != "?"
  4013. info[:name] = name
  4014. end
  4015. if port.to_i != 0
  4016. report_service(info)
  4017. end
  4018. return if nasl == "0"
  4019. refs = []
  4020. cve.each do |r|
  4021. r.to_s.gsub!(/C(VE|AN)\-/, '')
  4022. refs.push('CVE-' + r.to_s)
  4023. end if cve
  4024. bid.each do |r|
  4025. refs.push('BID-' + r.to_s)
  4026. end if bid
  4027. xref.each do |r|
  4028. ref_id, ref_val = r.to_s.split(':')
  4029. ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id)
  4030. end if xref
  4031. msfref = "MSF-" << msf if msf
  4032. refs.push msfref if msfref
  4033. nss = 'NSS-' + nasl
  4034. vuln = {
  4035. :workspace => wspace,
  4036. :host => addr,
  4037. :name => nss,
  4038. :info => description ? description : "",
  4039. :refs => refs
  4040. }
  4041. if port.to_i != 0
  4042. vuln[:port] = port
  4043. vuln[:proto] = proto
  4044. end
  4045. report_vuln(vuln)
  4046. end
  4047. #
  4048. # IP360 v3 vuln
  4049. #
  4050. def handle_ip360_v3_svc(wspace,addr,port,proto,hname)
  4051. report_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
  4052. info = { :workspace => wspace, :host => addr, :port => port, :proto => proto }
  4053. if hname != "unknown" and hname[-1,1] != "?"
  4054. info[:name] = hname
  4055. end
  4056. if port.to_i != 0
  4057. report_service(info)
  4058. end
  4059. end #handle_ip360_v3_svc
  4060. #
  4061. # IP360 v3 vuln
  4062. #
  4063. def handle_ip360_v3_vuln(wspace,addr,port,proto,hname,vulnid,vulnname,cves,bids)
  4064. report_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
  4065. info = { :workspace => wspace, :host => addr, :port => port, :proto => proto }
  4066. if hname != "unknown" and hname[-1,1] != "?"
  4067. info[:name] = hname
  4068. end
  4069. if port.to_i != 0
  4070. report_service(info)
  4071. end
  4072. refs = []
  4073. cves.split(/,/).each do |cve|
  4074. refs.push(cve.to_s)
  4075. end if cves
  4076. bids.split(/,/).each do |bid|
  4077. refs.push('BID-' + bid.to_s)
  4078. end if bids
  4079. description = nil # not working yet
  4080. vuln = {
  4081. :workspace => wspace,
  4082. :host => addr,
  4083. :name => vulnname,
  4084. :info => description ? description : "",
  4085. :refs => refs
  4086. }
  4087. if port.to_i != 0
  4088. vuln[:port] = port
  4089. vuln[:proto] = proto
  4090. end
  4091. report_vuln(vuln)
  4092. end #handle_ip360_v3_vuln
  4093. #
  4094. # Qualys report parsing/handling
  4095. #
  4096. def handle_qualys(wspace, addr, port, protocol, qid, severity, refs, name=nil)
  4097. port = port.to_i
  4098. info = { :workspace => wspace, :host => addr, :port => port, :proto => protocol }
  4099. if name and name != 'unknown'
  4100. info[:name] = name
  4101. end
  4102. if info[:host] && info[:port] && info[:proto]
  4103. report_service(info)
  4104. end
  4105. return if qid == 0
  4106. if addr
  4107. report_vuln(
  4108. :workspace => wspace,
  4109. :host => addr,
  4110. :port => port,
  4111. :proto => protocol,
  4112. :name => 'QUALYS-' + qid,
  4113. :refs => refs
  4114. )
  4115. end
  4116. end
  4117. def process_nexpose_data_sxml_refs(vuln)
  4118. refs = []
  4119. vid = vuln.attributes['id'].to_s.downcase
  4120. vry = vuln.attributes['resultCode'].to_s.upcase
  4121. # Only process vuln-exploitable and vuln-version statuses
  4122. return if vry !~ /^V[VE]$/
  4123. refs = []
  4124. vuln.elements.each('id') do |ref|
  4125. rtyp = ref.attributes['type'].to_s.upcase
  4126. rval = ref.text.to_s.strip
  4127. case rtyp
  4128. when 'CVE'
  4129. refs << rval.gsub('CAN', 'CVE')
  4130. when 'MS' # obsolete?
  4131. refs << "MSB-MS-#{rval}"
  4132. else
  4133. refs << "#{rtyp}-#{rval}"
  4134. end
  4135. end
  4136. refs << "NEXPOSE-#{vid}"
  4137. refs
  4138. end
  4139. #
  4140. # NeXpose vuln lookup
  4141. #
  4142. def nexpose_vuln_lookup(wspace, doc, vid, refs, host, serv=nil)
  4143. doc.elements.each("/NexposeReport/VulnerabilityDefinitions/vulnerability[@id = '#{vid}']]") do |vulndef|
  4144. title = vulndef.attributes['title']
  4145. pciSeverity = vulndef.attributes['pciSeverity']
  4146. cvss_score = vulndef.attributes['cvssScore']
  4147. cvss_vector = vulndef.attributes['cvssVector']
  4148. vulndef.elements['references'].elements.each('reference') do |ref|
  4149. if ref.attributes['source'] == 'BID'
  4150. refs[ 'BID-' + ref.text ] = true
  4151. elsif ref.attributes['source'] == 'CVE'
  4152. # ref.text is CVE-$ID
  4153. refs[ ref.text ] = true
  4154. elsif ref.attributes['source'] == 'MS'
  4155. refs[ 'MSB-MS-' + ref.text ] = true
  4156. end
  4157. end
  4158. refs[ 'NEXPOSE-' + vid.downcase ] = true
  4159. vuln = find_or_create_vuln(
  4160. :workspace => wspace,
  4161. :host => host,
  4162. :service => serv,
  4163. :name => 'NEXPOSE-' + vid.downcase,
  4164. :info => title)
  4165. rids = []
  4166. refs.keys.each do |r|
  4167. rids << find_or_create_ref(:name => r)
  4168. end
  4169. vuln.refs << (rids - vuln.refs)
  4170. end
  4171. end
  4172. end
  4173. end