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

/lib/msf/core/db.rb

https://bitbucket.org/technopunk2099/metasploit-framework
Ruby | 6413 lines | 5194 code | 481 blank | 738 comment | 493 complexity | b628b679e2f04ffd13c5ea362d7a9e0e MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, LGPL-2.1, GPL-2.0, MIT

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

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

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