PageRenderTime 55ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 2ms

/lib/msf/core/db.rb

https://github.com/Jonono2/metasploit-framework
Ruby | 6402 lines | 4512 code | 773 blank | 1117 comment | 827 complexity | 13bed6f306f6acd1ae352cf8c7fb84a6 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, GPL-3.0, LGPL-2.1, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. # -*- coding: binary -*-
  2. #
  3. # Standard Library
  4. #
  5. require 'csv'
  6. require 'tmpdir'
  7. require 'uri'
  8. require 'zip'
  9. #
  10. #
  11. # Gems
  12. #
  13. #
  14. #
  15. # PacketFu
  16. #
  17. require 'packetfu'
  18. #
  19. # Rex
  20. #
  21. require 'rex/socket'
  22. # Check Rex::Parser.nokogiri_loaded for status of the Nokogiri parsers
  23. require 'rex/parser/acunetix_nokogiri'
  24. require 'rex/parser/appscan_nokogiri'
  25. require 'rex/parser/burp_session_nokogiri'
  26. require 'rex/parser/ci_nokogiri'
  27. require 'rex/parser/foundstone_nokogiri'
  28. require 'rex/parser/fusionvm_nokogiri'
  29. require 'rex/parser/mbsa_nokogiri'
  30. require 'rex/parser/nexpose_raw_nokogiri'
  31. require 'rex/parser/nexpose_simple_nokogiri'
  32. require 'rex/parser/nmap_nokogiri'
  33. require 'rex/parser/openvas_nokogiri'
  34. require 'rex/parser/wapiti_nokogiri'
  35. require 'rex/parser/outpost24_nokogiri'
  36. # Legacy XML parsers -- these will be converted some day
  37. require 'rex/parser/ip360_aspl_xml'
  38. require 'rex/parser/ip360_xml'
  39. require 'rex/parser/nessus_xml'
  40. require 'rex/parser/netsparker_xml'
  41. require 'rex/parser/nexpose_xml'
  42. require 'rex/parser/nmap_xml'
  43. require 'rex/parser/retina_xml'
  44. #
  45. # Project
  46. #
  47. require 'msf/core/db_manager/import_msf_xml'
  48. module Msf
  49. ###
  50. #
  51. # The states that a host can be in.
  52. #
  53. ###
  54. module HostState
  55. #
  56. # The host is alive.
  57. #
  58. Alive = "alive"
  59. #
  60. # The host is dead.
  61. #
  62. Dead = "down"
  63. #
  64. # The host state is unknown.
  65. #
  66. Unknown = "unknown"
  67. end
  68. ###
  69. #
  70. # The states that a service can be in.
  71. #
  72. ###
  73. module ServiceState
  74. Open = "open"
  75. Closed = "closed"
  76. Filtered = "filtered"
  77. Unknown = "unknown"
  78. end
  79. ###
  80. #
  81. # Events that can occur in the host/service database.
  82. #
  83. ###
  84. module DatabaseEvent
  85. #
  86. # Called when an existing host's state changes
  87. #
  88. def on_db_host_state(host, ostate)
  89. end
  90. #
  91. # Called when an existing service's state changes
  92. #
  93. def on_db_service_state(host, port, ostate)
  94. end
  95. #
  96. # Called when a new host is added to the database. The host parameter is
  97. # of type Host.
  98. #
  99. def on_db_host(host)
  100. end
  101. #
  102. # Called when a new client is added to the database. The client
  103. # parameter is of type Client.
  104. #
  105. def on_db_client(client)
  106. end
  107. #
  108. # Called when a new service is added to the database. The service
  109. # parameter is of type Service.
  110. #
  111. def on_db_service(service)
  112. end
  113. #
  114. # Called when an applicable vulnerability is found for a service. The vuln
  115. # parameter is of type Vuln.
  116. #
  117. def on_db_vuln(vuln)
  118. end
  119. #
  120. # Called when a new reference is created.
  121. #
  122. def on_db_ref(ref)
  123. end
  124. end
  125. class DBImportError < RuntimeError
  126. end
  127. ###
  128. #
  129. # The DB module ActiveRecord definitions for the DBManager
  130. #
  131. ###
  132. class DBManager
  133. include Msf::DBManager::ImportMsfXml
  134. def rfc3330_reserved(ip)
  135. case ip.class.to_s
  136. when "PacketFu::Octets"
  137. ip_x = ip.to_x
  138. ip_i = ip.to_i
  139. when "String"
  140. if ipv46_validator(ip)
  141. ip_x = ip
  142. ip_i = Rex::Socket.addr_atoi(ip)
  143. else
  144. raise ArgumentError, "Invalid IP address: #{ip.inspect}"
  145. end
  146. when "Fixnum"
  147. if (0..2**32-1).include? ip
  148. ip_x = Rex::Socket.addr_itoa(ip)
  149. ip_i = ip
  150. else
  151. raise ArgumentError, "Invalid IP address: #{ip.inspect}"
  152. end
  153. else
  154. raise ArgumentError, "Invalid IP address: #{ip.inspect}"
  155. end
  156. return true if Rex::Socket::RangeWalker.new("0.0.0.0-0.255.255.255").include? ip_x
  157. return true if Rex::Socket::RangeWalker.new("127.0.0.0-127.255.255.255").include? ip_x
  158. return true if Rex::Socket::RangeWalker.new("169.254.0.0-169.254.255.255").include? ip_x
  159. return true if Rex::Socket::RangeWalker.new("224.0.0.0-239.255.255.255").include? ip_x
  160. return true if Rex::Socket::RangeWalker.new("255.255.255.255-255.255.255.255").include? ip_x
  161. return false
  162. end
  163. def ipv46_validator(addr)
  164. ipv4_validator(addr) or ipv6_validator(addr)
  165. end
  166. def ipv4_validator(addr)
  167. return false unless addr.kind_of? String
  168. Rex::Socket.is_ipv4?(addr)
  169. end
  170. def ipv6_validator(addr)
  171. Rex::Socket.is_ipv6?(addr)
  172. end
  173. # Takes a space-delimited set of ips and ranges, and subjects
  174. # them to RangeWalker for validation. Returns true or false.
  175. def validate_ips(ips)
  176. ret = true
  177. begin
  178. ips.split(/\s+/).each {|ip|
  179. unless Rex::Socket::RangeWalker.new(ip).ranges
  180. ret = false
  181. break
  182. end
  183. }
  184. rescue
  185. ret = false
  186. end
  187. return ret
  188. end
  189. #
  190. # Determines if the database is functional
  191. #
  192. def check
  193. ::ActiveRecord::Base.connection_pool.with_connection {
  194. res = ::Mdm::Host.find(:first)
  195. }
  196. end
  197. def default_workspace
  198. ::ActiveRecord::Base.connection_pool.with_connection {
  199. ::Mdm::Workspace.default
  200. }
  201. end
  202. def find_workspace(name)
  203. ::ActiveRecord::Base.connection_pool.with_connection {
  204. ::Mdm::Workspace.find_by_name(name)
  205. }
  206. end
  207. #
  208. # Creates a new workspace in the database
  209. #
  210. def add_workspace(name)
  211. ::ActiveRecord::Base.connection_pool.with_connection {
  212. ::Mdm::Workspace.find_or_create_by_name(name)
  213. }
  214. end
  215. def workspaces
  216. ::ActiveRecord::Base.connection_pool.with_connection {
  217. ::Mdm::Workspace.find(:all)
  218. }
  219. end
  220. #
  221. # Wait for all pending write to finish
  222. #
  223. def sync
  224. # There is no more queue.
  225. end
  226. #
  227. # Find a host. Performs no database writes.
  228. #
  229. def get_host(opts)
  230. if opts.kind_of? ::Mdm::Host
  231. return opts
  232. elsif opts.kind_of? String
  233. raise RuntimeError, "This invokation of get_host is no longer supported: #{caller}"
  234. else
  235. address = opts[:addr] || opts[:address] || opts[:host] || return
  236. return address if address.kind_of? ::Mdm::Host
  237. end
  238. ::ActiveRecord::Base.connection_pool.with_connection {
  239. wspace = opts.delete(:workspace) || workspace
  240. if wspace.kind_of? String
  241. wspace = find_workspace(wspace)
  242. end
  243. address = normalize_host(address)
  244. return wspace.hosts.find_by_address(address)
  245. }
  246. end
  247. #
  248. # Exactly like report_host but waits for the database to create a host and returns it.
  249. #
  250. def find_or_create_host(opts)
  251. report_host(opts)
  252. end
  253. #
  254. # Report a host's attributes such as operating system and service pack
  255. #
  256. # The opts parameter MUST contain
  257. # +:host+:: -- the host's ip address
  258. #
  259. # The opts parameter can contain:
  260. # +:state+:: -- one of the Msf::HostState constants
  261. # +:os_name+:: -- one of the Msf::OperatingSystems constants
  262. # +:os_flavor+:: -- something like "XP" or "Gentoo"
  263. # +:os_sp+:: -- something like "SP2"
  264. # +:os_lang+:: -- something like "English", "French", or "en-US"
  265. # +:arch+:: -- one of the ARCH_* constants
  266. # +:mac+:: -- the host's MAC address
  267. # +:scope+:: -- interface identifier for link-local IPv6
  268. # +:virtual_host+:: -- the name of the VM host software, eg "VMWare", "QEMU", "Xen", etc.
  269. #
  270. def report_host(opts)
  271. return if not active
  272. addr = opts.delete(:host) || return
  273. # Sometimes a host setup through a pivot will see the address as "Remote Pipe"
  274. if addr.eql? "Remote Pipe"
  275. return
  276. end
  277. ::ActiveRecord::Base.connection_pool.with_connection {
  278. wspace = opts.delete(:workspace) || workspace
  279. if wspace.kind_of? String
  280. wspace = find_workspace(wspace)
  281. end
  282. ret = { }
  283. if not addr.kind_of? ::Mdm::Host
  284. addr = normalize_host(addr)
  285. addr, scope = addr.split('%', 2)
  286. opts[:scope] = scope if scope
  287. unless ipv46_validator(addr)
  288. raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
  289. end
  290. if opts[:comm] and opts[:comm].length > 0
  291. host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm])
  292. else
  293. host = wspace.hosts.find_or_initialize_by_address(addr)
  294. end
  295. else
  296. host = addr
  297. end
  298. # Truncate the info field at the maximum field length
  299. if opts[:info]
  300. opts[:info] = opts[:info][0,65535]
  301. end
  302. # Truncate the name field at the maximum field length
  303. if opts[:name]
  304. opts[:name] = opts[:name][0,255]
  305. end
  306. opts.each { |k,v|
  307. if (host.attribute_names.include?(k.to_s))
  308. unless host.attribute_locked?(k.to_s)
  309. host[k] = v.to_s.gsub(/[\x00-\x1f]/n, '')
  310. end
  311. else
  312. dlog("Unknown attribute for ::Mdm::Host: #{k}")
  313. end
  314. }
  315. host.info = host.info[0,::Mdm::Host.columns_hash["info"].limit] if host.info
  316. # Set default fields if needed
  317. host.state = HostState::Alive if not host.state
  318. host.comm = '' if not host.comm
  319. host.workspace = wspace if not host.workspace
  320. if host.changed?
  321. msf_import_timestamps(opts,host)
  322. host.save!
  323. end
  324. if opts[:task]
  325. Mdm::TaskHost.create(
  326. :task => opts[:task],
  327. :host => host
  328. )
  329. end
  330. host
  331. }
  332. end
  333. #
  334. # Update a host's attributes via semi-standardized sysinfo hash (Meterpreter)
  335. #
  336. # The opts parameter MUST contain the following entries
  337. # +:host+:: -- the host's ip address
  338. # +:info+:: -- the information hash
  339. # * 'Computer' -- the host name
  340. # * 'OS' -- the operating system string
  341. # * 'Architecture' -- the hardware architecture
  342. # * 'System Language' -- the system language
  343. #
  344. # The opts parameter can contain:
  345. # +:workspace+:: -- the workspace for this host
  346. #
  347. def update_host_via_sysinfo(opts)
  348. return if not active
  349. addr = opts.delete(:host) || return
  350. info = opts.delete(:info) || return
  351. # Sometimes a host setup through a pivot will see the address as "Remote Pipe"
  352. if addr.eql? "Remote Pipe"
  353. return
  354. end
  355. ::ActiveRecord::Base.connection_pool.with_connection {
  356. wspace = opts.delete(:workspace) || workspace
  357. if wspace.kind_of? String
  358. wspace = find_workspace(wspace)
  359. end
  360. if not addr.kind_of? ::Mdm::Host
  361. addr = normalize_host(addr)
  362. addr, scope = addr.split('%', 2)
  363. opts[:scope] = scope if scope
  364. unless ipv46_validator(addr)
  365. raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
  366. end
  367. if opts[:comm] and opts[:comm].length > 0
  368. host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm])
  369. else
  370. host = wspace.hosts.find_or_initialize_by_address(addr)
  371. end
  372. else
  373. host = addr
  374. end
  375. res = {}
  376. if info['Computer']
  377. res[:name] = info['Computer']
  378. end
  379. if info['Architecture']
  380. res[:arch] = info['Architecture'].split(/\s+/).first
  381. end
  382. if info['OS'] =~ /^Windows\s*([^\(]+)\(([^\)]+)\)/i
  383. res[:os_name] = "Microsoft Windows"
  384. res[:os_flavor] = $1.strip
  385. build = $2.strip
  386. if build =~ /Service Pack (\d+)/
  387. res[:os_sp] = "SP" + $1
  388. else
  389. res[:os_sp] = "SP0"
  390. end
  391. end
  392. if info["System Language"]
  393. case info["System Language"]
  394. when /^en_/
  395. res[:os_lang] = "English"
  396. end
  397. end
  398. # Truncate the info field at the maximum field length
  399. if res[:info]
  400. res[:info] = res[:info][0,65535]
  401. end
  402. # Truncate the name field at the maximum field length
  403. if res[:name]
  404. res[:name] = res[:name][0,255]
  405. end
  406. res.each { |k,v|
  407. if (host.attribute_names.include?(k.to_s))
  408. unless host.attribute_locked?(k.to_s)
  409. host[k] = v.to_s.gsub(/[\x00-\x1f]/n, '')
  410. end
  411. else
  412. dlog("Unknown attribute for Host: #{k}")
  413. end
  414. }
  415. # Set default fields if needed
  416. host.state = HostState::Alive if not host.state
  417. host.comm = '' if not host.comm
  418. host.workspace = wspace if not host.workspace
  419. if host.changed?
  420. host.save!
  421. end
  422. host
  423. }
  424. end
  425. #
  426. # Iterates over the hosts table calling the supplied block with the host
  427. # instance of each entry.
  428. #
  429. def each_host(wspace=workspace, &block)
  430. ::ActiveRecord::Base.connection_pool.with_connection {
  431. wspace.hosts.each do |host|
  432. block.call(host)
  433. end
  434. }
  435. end
  436. #
  437. # Returns a list of all hosts in the database
  438. #
  439. def hosts(wspace = workspace, only_up = false, addresses = nil)
  440. ::ActiveRecord::Base.connection_pool.with_connection {
  441. conditions = {}
  442. conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if only_up
  443. conditions[:address] = addresses if addresses
  444. wspace.hosts.where(conditions).order(:address)
  445. }
  446. end
  447. def find_or_create_service(opts)
  448. report_service(opts)
  449. end
  450. #
  451. # Record a service in the database.
  452. #
  453. # opts MUST contain
  454. # +:host+:: the host where this service is running
  455. # +:port+:: the port where this service listens
  456. # +:proto+:: the transport layer protocol (e.g. tcp, udp)
  457. #
  458. # opts may contain
  459. # +:name+:: the application layer protocol (e.g. ssh, mssql, smb)
  460. # +:sname+:: an alias for the above
  461. #
  462. def report_service(opts)
  463. return if not active
  464. ::ActiveRecord::Base.connection_pool.with_connection { |conn|
  465. addr = opts.delete(:host) || return
  466. hname = opts.delete(:host_name)
  467. hmac = opts.delete(:mac)
  468. host = nil
  469. wspace = opts.delete(:workspace) || workspace
  470. hopts = {:workspace => wspace, :host => addr}
  471. hopts[:name] = hname if hname
  472. hopts[:mac] = hmac if hmac
  473. # Other report_* methods take :sname to mean the service name, so we
  474. # map it here to ensure it ends up in the right place despite not being
  475. # a real column.
  476. if opts[:sname]
  477. opts[:name] = opts.delete(:sname)
  478. end
  479. if addr.kind_of? ::Mdm::Host
  480. host = addr
  481. addr = host.address
  482. else
  483. host = report_host(hopts)
  484. end
  485. if opts[:port].to_i.zero?
  486. dlog("Skipping port zero for service '%s' on host '%s'" % [opts[:name],host.address])
  487. return nil
  488. end
  489. ret = {}
  490. =begin
  491. host = get_host(:workspace => wspace, :address => addr)
  492. if host
  493. host.updated_at = host.created_at
  494. host.state = HostState::Alive
  495. host.save!
  496. end
  497. =end
  498. proto = opts[:proto] || 'tcp'
  499. service = host.services.find_or_initialize_by_port_and_proto(opts[:port].to_i, proto)
  500. opts.each { |k,v|
  501. if (service.attribute_names.include?(k.to_s))
  502. service[k] = ((v and k == :name) ? v.to_s.downcase : v)
  503. else
  504. dlog("Unknown attribute for Service: #{k}")
  505. end
  506. }
  507. service.state ||= ServiceState::Open
  508. service.info ||= ""
  509. if (service and service.changed?)
  510. msf_import_timestamps(opts,service)
  511. service.save!
  512. end
  513. if opts[:task]
  514. Mdm::TaskService.create(
  515. :task => opts[:task],
  516. :service => service
  517. )
  518. end
  519. ret[:service] = service
  520. }
  521. end
  522. def get_service(wspace, host, proto, port)
  523. ::ActiveRecord::Base.connection_pool.with_connection {
  524. host = get_host(:workspace => wspace, :address => host)
  525. return if not host
  526. return host.services.find_by_proto_and_port(proto, port)
  527. }
  528. end
  529. #
  530. # Iterates over the services table calling the supplied block with the
  531. # service instance of each entry.
  532. #
  533. def each_service(wspace=workspace, &block)
  534. ::ActiveRecord::Base.connection_pool.with_connection {
  535. services(wspace).each do |service|
  536. block.call(service)
  537. end
  538. }
  539. end
  540. #
  541. # Returns a list of all services in the database
  542. #
  543. def services(wspace = workspace, only_up = false, proto = nil, addresses = nil, ports = nil, names = nil)
  544. ::ActiveRecord::Base.connection_pool.with_connection {
  545. conditions = {}
  546. conditions[:state] = [ServiceState::Open] if only_up
  547. conditions[:proto] = proto if proto
  548. conditions["hosts.address"] = addresses if addresses
  549. conditions[:port] = ports if ports
  550. conditions[:name] = names if names
  551. wspace.services.includes(:host).where(conditions).order("hosts.address, port")
  552. }
  553. end
  554. # Returns a session based on opened_time, host address, and workspace
  555. # (or returns nil)
  556. def get_session(opts)
  557. return if not active
  558. ::ActiveRecord::Base.connection_pool.with_connection {
  559. wspace = opts[:workspace] || opts[:wspace] || workspace
  560. addr = opts[:addr] || opts[:address] || opts[:host] || return
  561. host = get_host(:workspace => wspace, :host => addr)
  562. time = opts[:opened_at] || opts[:created_at] || opts[:time] || return
  563. ::Mdm::Session.find_by_host_id_and_opened_at(host.id, time)
  564. }
  565. end
  566. # @note The Mdm::Session#desc will be truncated to 255 characters.
  567. # @todo https://www.pivotaltracker.com/story/show/48249739
  568. #
  569. # @overload report_session(opts)
  570. # Creates an Mdm::Session from Msf::Session. If +via_exploit+ is set on the
  571. # +session+, then an Mdm::Vuln and Mdm::ExploitAttempt is created for the
  572. # session's host. The Mdm::Host for the +session_host+ is created using
  573. # The session.session_host, +session.arch+ (if +session+ responds to arch),
  574. # and the workspace derived from opts or the +session+. The Mdm::Session is
  575. # assumed to be +last_seen+ and +opened_at+ at the time report_session is
  576. # called. +session.exploit_datastore['ParentModule']+ is used for the
  577. # Mdm::Session#via_exploit if +session.via_exploit+ is
  578. # 'exploit/multi/handler'.
  579. #
  580. # @param opts [Hash{Symbol => Object}] options
  581. # @option opt [Msf::Session, #datastore, #platform, #type, #via_exploit, #via_payload] :session
  582. # The in-memory session to persist to the database.
  583. # @option opts [Mdm::Workspace] :workspace The workspace for in which the
  584. # :session host is contained. Also used as the workspace for the
  585. # Mdm::ExploitAttempt and Mdm::Vuln. Defaults to Mdm::Worksapce with
  586. # Mdm::Workspace#name equal to +session.workspace+.
  587. # @return [nil] if {Msf::DBManager#active} is +false+.
  588. # @return [Mdm::Session] if session is saved
  589. # @raise [ArgumentError] if :session is not an {Msf::Session}.
  590. # @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be
  591. # saved, in which case, the Mdm::ExploitAttempt and Mdm::Vuln will not be
  592. # created, but the Mdm::Host will have been. (There is no transaction
  593. # to rollback the Mdm::Host creation.)
  594. # @see #find_or_create_host
  595. # @see #normalize_host
  596. # @see #report_exploit_success
  597. # @see #report_vuln
  598. #
  599. # @overload report_session(opts)
  600. # Creates an Mdm::Session from Mdm::Host.
  601. #
  602. # @param opts [Hash{Symbol => Object}] options
  603. # @option opts [DateTime, Time] :closed_at The date and time the sesion was
  604. # closed.
  605. # @option opts [String] :close_reason Reason the session was closed.
  606. # @option opts [Hash] :datastore {Msf::DataStore#to_h}.
  607. # @option opts [String] :desc Session description. Will be truncated to 255
  608. # characters.
  609. # @option opts [Mdm::Host] :host The host on which the session was opened.
  610. # @option opts [DateTime, Time] :last_seen The last date and time the
  611. # session was seen to be open. Defaults to :closed_at's value.
  612. # @option opts [DateTime, Time] :opened_at The date and time that the
  613. # session was opened.
  614. # @option opts [String] :platform The platform of the host.
  615. # @option opts [Array] :routes ([]) The routes through the session for
  616. # pivoting.
  617. # @option opts [String] :stype Session type.
  618. # @option opts [String] :via_exploit The {Msf::Module#fullname} of the
  619. # exploit that was used to open the session.
  620. # @option option [String] :via_payload the {MSf::Module#fullname} of the
  621. # payload sent to the host when the exploit was successful.
  622. # @return [nil] if {Msf::DBManager#active} is +false+.
  623. # @return [Mdm::Session] if session is saved.
  624. # @raise [ArgumentError] if :host is not an Mdm::Host.
  625. # @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be
  626. # saved.
  627. #
  628. # @raise ArgumentError if :host and :session is +nil+
  629. def report_session(opts)
  630. return if not active
  631. ::ActiveRecord::Base.connection_pool.with_connection {
  632. if opts[:session]
  633. raise ArgumentError.new("Invalid :session, expected Msf::Session") unless opts[:session].kind_of? Msf::Session
  634. session = opts[:session]
  635. wspace = opts[:workspace] || find_workspace(session.workspace)
  636. h_opts = { }
  637. h_opts[:host] = normalize_host(session)
  638. h_opts[:arch] = session.arch if session.respond_to?(:arch) and session.arch
  639. h_opts[:workspace] = wspace
  640. host = find_or_create_host(h_opts)
  641. sess_data = {
  642. :host_id => host.id,
  643. :stype => session.type,
  644. :desc => session.info,
  645. :platform => session.platform,
  646. :via_payload => session.via_payload,
  647. :via_exploit => session.via_exploit,
  648. :routes => [],
  649. :datastore => session.exploit_datastore.to_h,
  650. :port => session.session_port,
  651. :opened_at => Time.now.utc,
  652. :last_seen => Time.now.utc,
  653. :local_id => session.sid
  654. }
  655. elsif opts[:host]
  656. raise ArgumentError.new("Invalid :host, expected Host object") unless opts[:host].kind_of? ::Mdm::Host
  657. host = opts[:host]
  658. sess_data = {
  659. :host_id => host.id,
  660. :stype => opts[:stype],
  661. :desc => opts[:desc],
  662. :platform => opts[:platform],
  663. :via_payload => opts[:via_payload],
  664. :via_exploit => opts[:via_exploit],
  665. :routes => opts[:routes] || [],
  666. :datastore => opts[:datastore],
  667. :opened_at => opts[:opened_at],
  668. :closed_at => opts[:closed_at],
  669. :last_seen => opts[:last_seen] || opts[:closed_at],
  670. :close_reason => opts[:close_reason],
  671. }
  672. else
  673. raise ArgumentError.new("Missing option :session or :host")
  674. end
  675. ret = {}
  676. # Truncate the session data if necessary
  677. if sess_data[:desc]
  678. sess_data[:desc] = sess_data[:desc][0,255]
  679. end
  680. # In the case of multi handler we cannot yet determine the true
  681. # exploit responsible. But we can at least show the parent versus
  682. # just the generic handler:
  683. if session and session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
  684. sess_data[:via_exploit] = sess_data[:datastore]['ParentModule']
  685. end
  686. s = ::Mdm::Session.new(sess_data)
  687. s.save!
  688. if session and session.exploit_task and session.exploit_task.record
  689. session_task = session.exploit_task.record
  690. if session_task.class == Mdm::Task
  691. Mdm::TaskSession.create(:task => session_task, :session => s )
  692. end
  693. end
  694. if opts[:session]
  695. session.db_record = s
  696. end
  697. # If this is a live session, we know the host is vulnerable to something.
  698. if opts[:session] and session.via_exploit
  699. mod = framework.modules.create(session.via_exploit)
  700. if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
  701. mod_fullname = sess_data[:datastore]['ParentModule']
  702. mod_name = ::Mdm::Module::Detail.find_by_fullname(mod_fullname).name
  703. else
  704. mod_name = mod.name
  705. mod_fullname = mod.fullname
  706. end
  707. vuln_info = {
  708. :host => host.address,
  709. :name => mod_name,
  710. :refs => mod.references,
  711. :workspace => wspace,
  712. :exploited_at => Time.now.utc,
  713. :info => "Exploited by #{mod_fullname} to create Session #{s.id}"
  714. }
  715. port = session.exploit_datastore["RPORT"]
  716. service = (port ? host.services.find_by_port(port.to_i) : nil)
  717. vuln_info[:service] = service if service
  718. vuln = framework.db.report_vuln(vuln_info)
  719. if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
  720. via_exploit = sess_data[:datastore]['ParentModule']
  721. else
  722. via_exploit = session.via_exploit
  723. end
  724. attempt_info = {
  725. :timestamp => Time.now.utc,
  726. :workspace => wspace,
  727. :module => via_exploit,
  728. :username => session.username,
  729. :refs => mod.references,
  730. :session_id => s.id,
  731. :host => host,
  732. :service => service,
  733. :vuln => vuln
  734. }
  735. framework.db.report_exploit_success(attempt_info)
  736. end
  737. s
  738. }
  739. end
  740. #
  741. # Record a session event in the database
  742. #
  743. # opts MUST contain one of:
  744. # +:session+:: the Msf::Session OR the ::Mdm::Session we are reporting
  745. # +:etype+:: event type, enum: command, output, upload, download, filedelete
  746. #
  747. # opts may contain
  748. # +:output+:: the data for an output event
  749. # +:command+:: the data for an command event
  750. # +:remote_path+:: path to the associated file for upload, download, and filedelete events
  751. # +:local_path+:: path to the associated file for upload, and download
  752. #
  753. def report_session_event(opts)
  754. return if not active
  755. raise ArgumentError.new("Missing required option :session") if opts[:session].nil?
  756. raise ArgumentError.new("Expected an :etype") unless opts[:etype]
  757. session = nil
  758. ::ActiveRecord::Base.connection_pool.with_connection {
  759. if opts[:session].respond_to? :db_record
  760. session = opts[:session].db_record
  761. if session.nil?
  762. # The session doesn't have a db_record which means
  763. # a) the database wasn't connected at session registration time
  764. # or
  765. # b) something awful happened and the report_session call failed
  766. #
  767. # Either way, we can't do anything with this session as is, so
  768. # log a warning and punt.
  769. wlog("Warning: trying to report a session_event for a session with no db_record (#{opts[:session].sid})")
  770. return
  771. end
  772. event_data = { :created_at => Time.now }
  773. else
  774. session = opts[:session]
  775. event_data = { :created_at => opts[:created_at] }
  776. end
  777. event_data[:session_id] = session.id
  778. [:remote_path, :local_path, :output, :command, :etype].each do |attr|
  779. event_data[attr] = opts[attr] if opts[attr]
  780. end
  781. s = ::Mdm::SessionEvent.create(event_data)
  782. }
  783. end
  784. def report_session_route(session, route)
  785. return if not active
  786. if session.respond_to? :db_record
  787. s = session.db_record
  788. else
  789. s = session
  790. end
  791. unless s.respond_to?(:routes)
  792. raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}")
  793. end
  794. ::ActiveRecord::Base.connection_pool.with_connection {
  795. subnet, netmask = route.split("/")
  796. s.routes.create(:subnet => subnet, :netmask => netmask)
  797. }
  798. end
  799. def report_session_route_remove(session, route)
  800. return if not active
  801. if session.respond_to? :db_record
  802. s = session.db_record
  803. else
  804. s = session
  805. end
  806. unless s.respond_to?(:routes)
  807. raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}")
  808. end
  809. ::ActiveRecord::Base.connection_pool.with_connection {
  810. subnet, netmask = route.split("/")
  811. r = s.routes.find_by_subnet_and_netmask(subnet, netmask)
  812. r.destroy if r
  813. }
  814. end
  815. def report_exploit_success(opts)
  816. ::ActiveRecord::Base.connection_pool.with_connection {
  817. wspace = opts.delete(:workspace) || workspace
  818. mrefs = opts.delete(:refs) || return
  819. host = opts.delete(:host)
  820. port = opts.delete(:port)
  821. prot = opts.delete(:proto)
  822. svc = opts.delete(:service)
  823. vuln = opts.delete(:vuln)
  824. timestamp = opts.delete(:timestamp)
  825. username = opts.delete(:username)
  826. mname = opts.delete(:module)
  827. # Look up or generate the host as appropriate
  828. if not (host and host.kind_of? ::Mdm::Host)
  829. if svc.kind_of? ::Mdm::Service
  830. host = svc.host
  831. else
  832. host = report_host(:workspace => wspace, :address => host )
  833. end
  834. end
  835. # Bail if we dont have a host object
  836. return if not host
  837. # Look up or generate the service as appropriate
  838. if port and svc.nil?
  839. svc = report_service(:workspace => wspace, :host => host, :port => port, :proto => prot ) if port
  840. end
  841. if not vuln
  842. # Create a references map from the module list
  843. ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref|
  844. if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val)
  845. "#{ref.ctx_id}-#{ref.ctx_val}"
  846. else
  847. ref.to_s
  848. end
  849. })
  850. # Try find a matching vulnerability
  851. vuln = find_vuln_by_refs(ref_objs, host, svc)
  852. end
  853. # We have match, lets create a vuln_attempt record
  854. if vuln
  855. attempt_info = {
  856. :vuln_id => vuln.id,
  857. :attempted_at => timestamp || Time.now.utc,
  858. :exploited => true,
  859. :username => username || "unknown",
  860. :module => mname
  861. }
  862. attempt_info[:session_id] = opts[:session_id] if opts[:session_id]
  863. attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id]
  864. vuln.vuln_attempts.create(attempt_info)
  865. # Correct the vuln's associated service if necessary
  866. if svc and vuln.service_id.nil?
  867. vuln.service = svc
  868. vuln.save
  869. end
  870. end
  871. # Report an exploit attempt all the same
  872. attempt_info = {
  873. :attempted_at => timestamp || Time.now.utc,
  874. :exploited => true,
  875. :username => username || "unknown",
  876. :module => mname
  877. }
  878. attempt_info[:vuln_id] = vuln.id if vuln
  879. attempt_info[:session_id] = opts[:session_id] if opts[:session_id]
  880. attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id]
  881. if svc
  882. attempt_info[:port] = svc.port
  883. attempt_info[:proto] = svc.proto
  884. end
  885. if port and svc.nil?
  886. attempt_info[:port] = port
  887. attempt_info[:proto] = prot || "tcp"
  888. end
  889. host.exploit_attempts.create(attempt_info)
  890. }
  891. end
  892. def report_exploit_failure(opts)
  893. ::ActiveRecord::Base.connection_pool.with_connection {
  894. wspace = opts.delete(:workspace) || workspace
  895. mrefs = opts.delete(:refs) || return
  896. host = opts.delete(:host)
  897. port = opts.delete(:port)
  898. prot = opts.delete(:proto)
  899. svc = opts.delete(:service)
  900. vuln = opts.delete(:vuln)
  901. timestamp = opts.delete(:timestamp)
  902. freason = opts.delete(:fail_reason)
  903. fdetail = opts.delete(:fail_detail)
  904. username = opts.delete(:username)
  905. mname = opts.delete(:module)
  906. # Look up the host as appropriate
  907. if not (host and host.kind_of? ::Mdm::Host)
  908. if svc.kind_of? ::Mdm::Service
  909. host = svc.host
  910. else
  911. host = get_host( :workspace => wspace, :address => host )
  912. end
  913. end
  914. # Bail if we dont have a host object
  915. return if not host
  916. # Look up the service as appropriate
  917. if port and svc.nil?
  918. prot ||= "tcp"
  919. svc = get_service(wspace, host, prot, port) if port
  920. end
  921. if not vuln
  922. # Create a references map from the module list
  923. ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref|
  924. if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val)
  925. "#{ref.ctx_id}-#{ref.ctx_val}"
  926. else
  927. ref.to_s
  928. end
  929. })
  930. # Try find a matching vulnerability
  931. vuln = find_vuln_by_refs(ref_objs, host, svc)
  932. end
  933. # Report a vuln_attempt if we found a match
  934. if vuln
  935. attempt_info = {
  936. :attempted_at => timestamp || Time.now.utc,
  937. :exploited => false,
  938. :fail_reason => freason,
  939. :fail_detail => fdetail,
  940. :username => username || "unknown",
  941. :module => mname
  942. }
  943. vuln.vuln_attempts.create(attempt_info)
  944. end
  945. # Report an exploit attempt all the same
  946. attempt_info = {
  947. :attempted_at => timestamp || Time.now.utc,
  948. :exploited => false,
  949. :username => username || "unknown",
  950. :module => mname,
  951. :fail_reason => freason,
  952. :fail_detail => fdetail
  953. }
  954. attempt_info[:vuln_id] = vuln.id if vuln
  955. if svc
  956. attempt_info[:port] = svc.port
  957. attempt_info[:proto] = svc.proto
  958. end
  959. if port and svc.nil?
  960. attempt_info[:port] = port
  961. attempt_info[:proto] = prot || "tcp"
  962. end
  963. host.exploit_attempts.create(attempt_info)
  964. }
  965. end
  966. def report_vuln_attempt(vuln, opts)
  967. ::ActiveRecord::Base.connection_pool.with_connection {
  968. return if not vuln
  969. info = {}
  970. # Opts can be keyed by strings or symbols
  971. ::Mdm::VulnAttempt.column_names.each do |kn|
  972. k = kn.to_sym
  973. next if ['id', 'vuln_id'].include?(kn)
  974. info[k] = opts[kn] if opts[kn]
  975. info[k] = opts[k] if opts[k]
  976. end
  977. return unless info[:attempted_at]
  978. vuln.vuln_attempts.create(info)
  979. }
  980. end
  981. def report_exploit_attempt(host, opts)
  982. ::ActiveRecord::Base.connection_pool.with_connection {
  983. return if not host
  984. info = {}
  985. # Opts can be keyed by strings or symbols
  986. ::Mdm::VulnAttempt.column_names.each do |kn|
  987. k = kn.to_sym
  988. next if ['id', 'host_id'].include?(kn)
  989. info[k] = opts[kn] if opts[kn]
  990. info[k] = opts[k] if opts[k]
  991. end
  992. host.exploit_attempts.create(info)
  993. }
  994. end
  995. def get_client(opts)
  996. ::ActiveRecord::Base.connection_pool.with_connection {
  997. wspace = opts.delete(:workspace) || workspace
  998. host = get_host(:workspace => wspace, :host => opts[:host]) || return
  999. client = host.clients.where({:ua_string => opts[:ua_string]}).first()
  1000. return client
  1001. }
  1002. end
  1003. def find_or_create_client(opts)
  1004. report_client(opts)
  1005. end
  1006. #
  1007. # Report a client running on a host.
  1008. #
  1009. # opts MUST contain
  1010. # +:ua_string+:: the value of the User-Agent header
  1011. # +:host+:: the host where this client connected from, can be an ip address or a Host object
  1012. #
  1013. # opts can contain
  1014. # +:ua_name+:: one of the Msf::HttpClients constants
  1015. # +:ua_ver+:: detected version of the given client
  1016. # +:campaign+:: an id or Campaign object
  1017. #
  1018. # Returns a Client.
  1019. #
  1020. def report_client(opts)
  1021. return if not active
  1022. ::ActiveRecord::Base.connection_pool.with_connection {
  1023. addr = opts.delete(:host) || return
  1024. wspace = opts.delete(:workspace) || workspace
  1025. report_host(:workspace => wspace, :host => addr)
  1026. ret = {}
  1027. host = get_host(:workspace => wspace, :host => addr)
  1028. client = host.clients.find_or_initialize_by_ua_string(opts[:ua_string])
  1029. opts[:ua_string] = opts[:ua_string].to_s
  1030. campaign = opts.delete(:campaign)
  1031. if campaign
  1032. case campaign
  1033. when Campaign
  1034. opts[:campaign_id] = campaign.id
  1035. else
  1036. opts[:campaign_id] = campaign
  1037. end
  1038. end
  1039. opts.each { |k,v|
  1040. if (client.attribute_names.include?(k.to_s))
  1041. client[k] = v
  1042. else
  1043. dlog("Unknown attribute for Client: #{k}")
  1044. end
  1045. }
  1046. if (client and client.changed?)
  1047. client.save!
  1048. end
  1049. ret[:client] = client
  1050. }
  1051. end
  1052. #
  1053. # This method iterates the vulns table calling the supplied block with the
  1054. # vuln instance of each entry.
  1055. #
  1056. def each_vuln(wspace=workspace,&block)
  1057. ::ActiveRecord::Base.connection_pool.with_connection {
  1058. wspace.vulns.each do |vulns|
  1059. block.call(vulns)
  1060. end
  1061. }
  1062. end
  1063. #
  1064. # This methods returns a list of all vulnerabilities in the database
  1065. #
  1066. def vulns(wspace=workspace)
  1067. ::ActiveRecord::Base.connection_pool.with_connection {
  1068. wspace.vulns
  1069. }
  1070. end
  1071. #
  1072. # This methods returns a list of all credentials in the database
  1073. #
  1074. def creds(wspace=workspace)
  1075. ::ActiveRecord::Base.connection_pool.with_connection {
  1076. Mdm::Cred.includes({:service => :host}).where("hosts.workspace_id = ?", wspace.id)
  1077. }
  1078. end
  1079. #
  1080. # This method returns a list of all exploited hosts in the database.
  1081. #
  1082. def exploited_hosts(wspace=workspace)
  1083. ::ActiveRecord::Base.connection_pool.with_connection {
  1084. wspace.exploited_hosts
  1085. }
  1086. end
  1087. #
  1088. # This method iterates the notes table calling the supplied block with the
  1089. # note instance of each entry.
  1090. #
  1091. def each_note(wspace=workspace, &block)
  1092. ::ActiveRecord::Base.connection_pool.with_connection {
  1093. wspace.notes.each do |note|
  1094. block.call(note)
  1095. end
  1096. }
  1097. end
  1098. #
  1099. # Find or create a note matching this type/data
  1100. #
  1101. def find_or_create_note(opts)
  1102. report_note(opts)
  1103. end
  1104. #
  1105. # Report a Note to the database. Notes can be tied to a ::Mdm::Workspace, Host, or Service.
  1106. #
  1107. # opts MUST contain
  1108. # +:type+:: The type of note, e.g. smb_peer_os
  1109. #
  1110. # opts can contain
  1111. # +:workspace+:: the workspace to associate with this Note
  1112. # +:host+:: an IP address or a Host object to associate with this Note
  1113. # +:service+:: a Service object to associate with this Note
  1114. # +:data+:: whatever it is you're making a note of
  1115. # +:port+:: along with +:host+ and +:proto+, a service to associate with this Note
  1116. # +:proto+:: along with +:host+ and +:port+, a service to associate with this Note
  1117. # +:update+:: what to do in case a similar Note exists, see below
  1118. #
  1119. # The +:update+ option can have the following values:
  1120. # +:unique+:: allow only a single Note per +:host+/+:type+ pair
  1121. # +:unique_data+:: like +:uniqe+, but also compare +:data+
  1122. # +:insert+:: always insert a new Note even if one with identical values exists
  1123. #
  1124. # If the provided +:host+ is an IP address and does not exist in the
  1125. # database, it will be created. If +:workspace+, +:host+ and +:service+
  1126. # are all omitted, the new Note will be associated with the current
  1127. # workspace.
  1128. #
  1129. def report_note(opts)
  1130. return if not active
  1131. ::ActiveRecord::Base.connection_pool.with_connection {
  1132. wspace = opts.delete(:workspace) || workspace
  1133. if wspace.kind_of? String
  1134. wspace = find_workspace(wspace)
  1135. end
  1136. seen = opts.delete(:seen) || false
  1137. crit = opts.delete(:critical) || false
  1138. host = nil
  1139. addr = nil
  1140. # Report the host so it's there for the Proc to use below
  1141. if opts[:host]
  1142. if opts[:host].kind_of? ::Mdm::Host
  1143. host = opts[:host]
  1144. else
  1145. addr = normalize_host(opts[:host])
  1146. host = report_host({:workspace => wspace, :host => addr})
  1147. end
  1148. # Do the same for a service if that's also included.
  1149. if (opts[:port])
  1150. proto = nil
  1151. sname = nil
  1152. case opts[:proto].to_s.downcase # Catch incorrect usages
  1153. when 'tcp','udp'
  1154. proto = opts[:proto]
  1155. sname = opts[:sname] if opts[:sname]
  1156. when 'dns','snmp','dhcp'
  1157. proto = 'udp'
  1158. sname = opts[:proto]
  1159. else
  1160. proto = 'tcp'
  1161. sname = opts[:proto]
  1162. end
  1163. sopts = {
  1164. :workspace => wspace,
  1165. :host => host,
  1166. :port => opts[:port],
  1167. :proto => proto
  1168. }
  1169. sopts[:name] = sname if sname
  1170. report_service(sopts)
  1171. end
  1172. end
  1173. # Update Modes can be :unique, :unique_data, :insert
  1174. mode = opts[:update] || :unique
  1175. ret = {}
  1176. if addr and not host
  1177. host = get_host(:workspace => wspace, :host => addr)
  1178. end
  1179. if host and (opts[:port] and opts[:proto])
  1180. service = get_service(wspace, host, opts[:proto], opts[:port])
  1181. elsif opts[:service] and opts[:service].kind_of? ::Mdm::Service
  1182. service = opts[:service]
  1183. end
  1184. =begin
  1185. if host
  1186. host.updated_at = host.created_at
  1187. host.state = HostState::Alive
  1188. host.save!
  1189. end
  1190. =end
  1191. ntype = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required")
  1192. data = opts[:data]
  1193. method = nil
  1194. args = []
  1195. note = nil
  1196. conditions = { :ntype => ntype }
  1197. conditions[:host_id] = host[:id] if host
  1198. conditions[:service_id] = service[:id] if service
  1199. case mode
  1200. when :unique
  1201. notes = wspace.notes.where(conditions)
  1202. # Only one note of this type should exist, make a new one if it
  1203. # isn't there. If it is, grab it and overwrite its data.
  1204. if notes.empty?
  1205. note = wspace.notes.new(conditions)
  1206. else
  1207. note = notes[0]
  1208. end
  1209. note.data = data
  1210. when :unique_data
  1211. notes = wspace.notes.where(conditions)
  1212. # Don't make a new Note with the same data as one that already
  1213. # exists for the given: type and (host or service)
  1214. notes.each do |n|
  1215. # Compare the deserialized data from the table to the raw
  1216. # data we're looking for. Because of the serialization we
  1217. # can't do this easily or reliably in SQL.
  1218. if n.data == data
  1219. note = n
  1220. break
  1221. end
  1222. end
  1223. if not note
  1224. # We didn't find one with the data we're looking for, make
  1225. # a new one.
  1226. note = wspace.notes.new(conditions.merge(:data => data))
  1227. end
  1228. else
  1229. # Otherwise, assume :insert, which means always make a new one
  1230. note = wspace.notes.new
  1231. if host
  1232. note.host_id = host[:id]
  1233. end
  1234. if opts[:service] and opts[:service].kind_of? ::Mdm::Service
  1235. note.service_id = opts[:service][:id]
  1236. end
  1237. note.seen = seen
  1238. note.critical = crit
  1239. note.ntype = ntype
  1240. note.data = data
  1241. end
  1242. msf_import_timestamps(opts,note)
  1243. note.save!
  1244. ret[:note] = note
  1245. }
  1246. end
  1247. #
  1248. # This methods returns a list of all notes in the database
  1249. #
  1250. def notes(wspace=workspace)
  1251. ::ActiveRecord::Base.connection_pool.with_connection {
  1252. wspace.notes
  1253. }
  1254. end
  1255. # This is only exercised by MSF3 XML importing for now. Needs the wait
  1256. # conditions and return hash as well.
  1257. def report_host_tag(opts)
  1258. name = opts.delete(:name)
  1259. raise DBImportError.new("Missing required option :name") unless name
  1260. addr = opts.delete(:addr)
  1261. raise DBImportError.new("Missing required option :addr") unless addr
  1262. wspace = opts.delete(:wspace)
  1263. raise DBImportError.new("Missing required option :wspace") unless wspace
  1264. ::ActiveRecord::Base.connection_pool.with_connection {
  1265. if wspace.kind_of? String
  1266. wspace = find_workspace(wspace)
  1267. end
  1268. host = nil
  1269. report_host(:workspace => wspace, :address => addr)
  1270. host = get_host(:workspace => wspace, :address => addr)
  1271. desc = opts.delete(:desc)
  1272. summary = opts.delete(:summary)
  1273. detail = opts.delete(:detail)
  1274. crit = opts.delete(:crit)
  1275. possible_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and tags.name = ?", wspace.id, name).order("tags.id DESC").limit(1)
  1276. tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first)
  1277. tag.name = name
  1278. tag.desc = desc
  1279. tag.report_summary = !!summary
  1280. tag.report_detail = !!detail
  1281. tag.critical = !!crit
  1282. tag.hosts = tag.hosts | [host]
  1283. tag.save! if tag.changed?
  1284. }
  1285. end
  1286. #
  1287. # Store a set of credentials in the database.
  1288. #
  1289. # report_auth_info used to create a note, now it creates
  1290. # an entry in the creds table. It's much more akin to
  1291. # report_vuln() now.
  1292. #
  1293. # opts MUST contain
  1294. # +:host+:: an IP address or Host object reference
  1295. # +:port+:: a port number
  1296. #
  1297. # opts can contain
  1298. # +:user+:: the username
  1299. # +:pass+:: the password, or path to ssh_key
  1300. # +:ptype+:: the type of password (password(ish), hash, or ssh_key)
  1301. # +:proto+:: a transport name for the port
  1302. # +:sname+:: service name
  1303. # +:active+:: by default, a cred is active, unless explicitly false
  1304. # +:proof+:: data used to prove the account is actually active.
  1305. #
  1306. # Sources: Credentials can be sourced from another credential, or from
  1307. # a vulnerability. For example, if an exploit was used to dump the
  1308. # smb_hashes, and this credential comes from there, the source_id would
  1309. # be the Vuln id (as reported by report_vuln) and the type would be "Vuln".
  1310. #
  1311. # +:source_id+:: The Vuln or Cred id of the source of this cred.
  1312. # +:source_type+:: Either Vuln or Cred
  1313. #
  1314. # TODO: This is written somewhat host-centric, when really the
  1315. # Service is the thing. Need to revisit someday.
  1316. def report_auth_info(opts={})
  1317. return if not active
  1318. raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
  1319. raise ArgumentError.new("Missing required option :port") if (opts[:port].nil? and opts[:service].nil?)
  1320. if (not opts[:host].kind_of?(::Mdm::Host)) and (not validate_ips(opts[:host]))
  1321. raise ArgumentError.new("Invalid address or object for :host (#{opts[:host].inspect})")
  1322. end
  1323. ::ActiveRecord::Base.connection_pool.with_connection {
  1324. host = opts.delete(:host)
  1325. ptype = opts.delete(:type) || "password"
  1326. token = [opts.delete(:user), opts.delete(:pass)]
  1327. sname = opts.delete(:sname)
  1328. port = opts.delete(:port)
  1329. proto = opts.delete(:proto) || "tcp"
  1330. proof = opts.delete(:proof)
  1331. source_id = opts.delete(:source_id)
  1332. source_type = opts.delete(:source_type)
  1333. duplicate_ok = opts.delete(:duplicate_ok)
  1334. # Nil is true for active.
  1335. active = (opts[:active] || opts[:active].nil?) ? true : false
  1336. wspace = opts.delete(:workspace) || workspace
  1337. # Service management; assume the user knows what
  1338. # he's talking about.
  1339. service = opts.delete(:service) || report_service(:host => host, :port => port, :proto => proto, :name => sname, :workspace => wspace)
  1340. # Non-US-ASCII usernames are tripping up the database at the moment, this is a temporary fix until we update the tables
  1341. if (token[0])
  1342. # convert the token to US-ASCII from UTF-8 to prevent an error
  1343. token[0] = token[0].unpack("C*").pack("C*")
  1344. token[0] = token[0].gsub(/[\x00-\x1f\x7f-\xff]/n){|m| "\\x%.2x" % m.unpack("C")[0] }
  1345. end
  1346. if (token[1])
  1347. token[1] = token[1].unpack("C*").pack("C*")
  1348. token[1] = token[1].gsub(/[\x00-\x1f\x7f-\xff]/n){|m| "\\x%.2x" % m.unpack("C")[0] }
  1349. end
  1350. ret = {}
  1351. # Check to see if the creds already exist. We look also for a downcased username with the
  1352. # same password because we can fairly safely assume they are not in fact two seperate creds.
  1353. # this allows us to hedge against duplication of creds in the DB.
  1354. if duplicate_ok
  1355. # If duplicate usernames are okay, find by both user and password (allows
  1356. # for actual duplicates to get modified updated_at, sources, etc)
  1357. if token[0].nil? or token[0].empty?
  1358. cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
  1359. else
  1360. cred = service.creds.find_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
  1361. unless cred
  1362. dcu = token[0].downcase
  1363. cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "")
  1364. unless cred
  1365. cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
  1366. end
  1367. end
  1368. end
  1369. else
  1370. # Create the cred by username only (so we can change passwords)
  1371. if token[0].nil? or token[0].empty?
  1372. cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype)
  1373. else
  1374. cred = service.creds.find_by_user_and_ptype(token[0] || "", ptype)
  1375. unless cred
  1376. dcu = token[0].downcase
  1377. cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "")
  1378. unless cred
  1379. cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype)
  1380. end
  1381. end
  1382. end
  1383. end
  1384. # Update with the password
  1385. cred.pass = (token[1] || "")
  1386. # Annotate the credential
  1387. cred.ptype = ptype
  1388. cred.active = active
  1389. # Update the source ID only if there wasn't already one.
  1390. if source_id and !cred.source_id
  1391. cred.source_id = source_id
  1392. cred.source_type = source_type if source_type
  1393. end
  1394. # Safe proof (lazy way) -- doesn't chop expanded
  1395. # characters correctly, but shouldn't ever be a problem.
  1396. unless proof.nil?
  1397. proof = Rex::Text.to_hex_ascii(proof)
  1398. proof = proof[0,4096]
  1399. end
  1400. cred.proof = proof
  1401. # Update the timestamp
  1402. if cred.changed?
  1403. msf_import_timestamps(opts,cred)
  1404. cred.save!
  1405. end
  1406. # Ensure the updated_at is touched any time report_auth_info is called
  1407. # except when it's set explicitly (as it is for imports)
  1408. unless opts[:updated_at] || opts["updated_at"]
  1409. cred.updated_at = Time.now.utc
  1410. cred.save!
  1411. end
  1412. if opts[:task]
  1413. Mdm::TaskCred.create(
  1414. :task => opts[:task],
  1415. :cred => cred
  1416. )
  1417. end
  1418. ret[:cred] = cred
  1419. }
  1420. end
  1421. alias :report_cred :report_auth_info
  1422. alias :report_auth :report_auth_info
  1423. #
  1424. # Find or create a credential matching this type/data
  1425. #
  1426. def find_or_create_cred(opts)
  1427. report_auth_info(opts)
  1428. end
  1429. #
  1430. # This method iterates the creds table calling the supplied block with the
  1431. # cred instance of each entry.
  1432. #
  1433. def each_cred(wspace=workspace,&block)
  1434. ::ActiveRecord::Base.connection_pool.with_connection {
  1435. wspace.creds.each do |cred|
  1436. block.call(cred)
  1437. end
  1438. }
  1439. end
  1440. def each_exploited_host(wspace=workspace,&block)
  1441. ::ActiveRecord::Base.connection_pool.with_connection {
  1442. wspace.exploited_hosts.each do |eh|
  1443. block.call(eh)
  1444. end
  1445. }
  1446. end
  1447. #
  1448. # Find or create a vuln matching this service/name
  1449. #
  1450. def find_or_create_vuln(opts)
  1451. report_vuln(opts)
  1452. end
  1453. #
  1454. # opts MUST contain
  1455. # +:host+:: the host where this vulnerability resides
  1456. # +:name+:: the friendly name for this vulnerability (title)
  1457. #
  1458. # opts can contain
  1459. # +:info+:: a human readable description of the vuln, free-form text
  1460. # +:refs+:: an array of Ref objects or string names of references
  1461. # +:details:: a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields
  1462. #
  1463. def report_vuln(opts)
  1464. return if not active
  1465. raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
  1466. raise ArgumentError.new("Deprecated data column for vuln, use .info instead") if opts[:data]
  1467. name = opts[:name] || return
  1468. info = opts[:info]
  1469. ::ActiveRecord::Base.connection_pool.with_connection {
  1470. wspace = opts.delete(:workspace) || workspace
  1471. exploited_at = opts[:exploited_at] || opts["exploited_at"]
  1472. details = opts.delete(:details)
  1473. rids = opts.delete(:ref_ids)
  1474. if opts[:refs]
  1475. rids ||= []
  1476. opts[:refs].each do |r|
  1477. if (r.respond_to?(:ctx_id)) and (r.respon

Large files files are truncated, but you can click here to view the full file