Mirror of metasploit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

db_export.rb 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. # -*- coding: binary -*-
  2. module Msf
  3. ##
  4. #
  5. # This class provides export capabilities
  6. #
  7. ##
  8. class DBManager
  9. class Export
  10. attr_accessor :workspace
  11. def initialize(workspace)
  12. self.workspace = workspace
  13. end
  14. def myworkspace
  15. self.workspace
  16. end
  17. def myusername
  18. @username ||= (ENV['LOGNAME'] || ENV['USERNAME'] || ENV['USER'] || "unknown").to_s.strip.gsub(/[^A-Za-z0-9\x20]/n,"_")
  19. end
  20. # Hosts are always allowed. This is really just a stub.
  21. def host_allowed?(arg)
  22. true
  23. end
  24. # Performs an export of the workspace's `Metasploit::Credential::Login` objects in pwdump format
  25. # @param path [String] the path on the local filesystem where the exported data will be written
  26. # @return [void]
  27. def to_pwdump_file(path, &block)
  28. exporter = Metasploit::Credential::Exporter::Pwdump.new(workspace: workspace)
  29. File.open(path, 'w') do |file|
  30. file << exporter.rendered_output
  31. end
  32. true
  33. end
  34. def to_xml_file(path, &block)
  35. yield(:status, "start", "report") if block_given?
  36. extract_target_entries
  37. report_file = ::File.open(path, "wb")
  38. report_file.write %Q|<?xml version="1.0" encoding="UTF-8"?>\n|
  39. report_file.write %Q|<MetasploitV5>\n|
  40. report_file.write %Q|<generated time="#{Time.now.utc}" user="#{myusername}" project="#{myworkspace.name.gsub(/[^A-Za-z0-9\x20]/n,"_")}" product="framework"/>\n|
  41. yield(:status, "start", "hosts") if block_given?
  42. report_file.write %Q|<hosts>\n|
  43. report_file.flush
  44. extract_host_info(report_file)
  45. report_file.write %Q|</hosts>\n|
  46. yield(:status, "start", "events") if block_given?
  47. report_file.write %Q|<events>\n|
  48. report_file.flush
  49. extract_event_info(report_file)
  50. report_file.write %Q|</events>\n|
  51. yield(:status, "start", "services") if block_given?
  52. report_file.write %Q|<services>\n|
  53. report_file.flush
  54. extract_service_info(report_file)
  55. report_file.write %Q|</services>\n|
  56. yield(:status, "start", "web sites") if block_given?
  57. report_file.write %Q|<web_sites>\n|
  58. report_file.flush
  59. extract_web_site_info(report_file)
  60. report_file.write %Q|</web_sites>\n|
  61. yield(:status, "start", "web pages") if block_given?
  62. report_file.write %Q|<web_pages>\n|
  63. report_file.flush
  64. extract_web_page_info(report_file)
  65. report_file.write %Q|</web_pages>\n|
  66. yield(:status, "start", "web forms") if block_given?
  67. report_file.write %Q|<web_forms>\n|
  68. report_file.flush
  69. extract_web_form_info(report_file)
  70. report_file.write %Q|</web_forms>\n|
  71. yield(:status, "start", "web vulns") if block_given?
  72. report_file.write %Q|<web_vulns>\n|
  73. report_file.flush
  74. extract_web_vuln_info(report_file)
  75. report_file.write %Q|</web_vulns>\n|
  76. yield(:status, "start", "module details") if block_given?
  77. report_file.write %Q|<module_details>\n|
  78. report_file.flush
  79. extract_module_detail_info(report_file)
  80. report_file.write %Q|</module_details>\n|
  81. report_file.write %Q|</MetasploitV5>\n|
  82. report_file.flush
  83. report_file.close
  84. yield(:status, "complete", "report") if block_given?
  85. true
  86. end
  87. # A convenience function that bundles together host, event, and service extraction.
  88. def extract_target_entries
  89. extract_host_entries
  90. extract_event_entries
  91. extract_service_entries
  92. extract_note_entries
  93. extract_vuln_entries
  94. extract_web_entries
  95. end
  96. # Extracts all the hosts from a project, storing them in @hosts and @owned_hosts
  97. def extract_host_entries
  98. @owned_hosts = []
  99. @hosts = myworkspace.hosts
  100. @hosts.each do |host|
  101. if host.notes.where(ntype: 'pro.system.compromise').first
  102. @owned_hosts << host
  103. end
  104. end
  105. end
  106. # Extracts all events from a project, storing them in @events
  107. def extract_event_entries
  108. @events = myworkspace.events.order('created_at ASC')
  109. end
  110. # Extracts all services from a project, storing them in @services
  111. def extract_service_entries
  112. @services = myworkspace.services
  113. end
  114. # Extracts all credentials from a project, storing them in @creds
  115. def extract_credential_entries
  116. @creds = Metasploit::Credential::Core.with_logins.with_public.with_private.workspace_id(myworkspace.id)
  117. end
  118. # Extracts all notes from a project, storing them in @notes
  119. def extract_note_entries
  120. @notes = myworkspace.notes
  121. end
  122. # Extracts all vulns from a project, storing them in @vulns
  123. def extract_vuln_entries
  124. @vulns = myworkspace.vulns
  125. end
  126. # Extract all web entries, storing them in instance variables
  127. def extract_web_entries
  128. @web_sites = myworkspace.web_sites
  129. @web_pages = myworkspace.web_pages
  130. @web_forms = myworkspace.web_forms
  131. @web_vulns = myworkspace.web_vulns
  132. end
  133. # Simple marshalling, for now. Can I use ActiveRecord::ConnectionAdapters::Quoting#quote
  134. # directly? Is it better to just marshal everything and destroy readability? Howabout
  135. # XML safety?
  136. def marshalize(obj)
  137. case obj
  138. when String
  139. obj.strip
  140. when TrueClass, FalseClass, Float, Fixnum, Bignum, Time
  141. obj.to_s.strip
  142. when BigDecimal
  143. obj.to_s("F")
  144. when NilClass
  145. "NULL"
  146. else
  147. [Marshal.dump(obj)].pack("m").gsub(/\s+/,"")
  148. end
  149. end
  150. def create_xml_element(key,value,skip_encoding=false)
  151. tag = key.gsub("_","-")
  152. el = REXML::Element.new(tag)
  153. if value
  154. unless skip_encoding
  155. data = marshalize(value)
  156. data.force_encoding(Encoding::BINARY) if data.respond_to?('force_encoding')
  157. data.gsub!(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xFF])/n){ |x| "\\x%.2x" % x.unpack("C*")[0] }
  158. el << REXML::Text.new(data)
  159. else
  160. el << value
  161. end
  162. end
  163. return el
  164. end
  165. # @note there is no single root element output by
  166. # {#extract_module_detail_info}, so if calling {#extract_module_detail_info}
  167. # directly, it is the caller's responsibility to add an opening and closing
  168. # tag to report_file around the call to {#extract_module_detail_info}.
  169. #
  170. # Writes a module_detail element to the report_file for each
  171. # Mdm::Module::Detail.
  172. #
  173. # @param report_file [#write, #flush] IO stream to which to write the
  174. # module_detail elements.
  175. # @return [void]
  176. def extract_module_detail_info(report_file)
  177. Mdm::Module::Detail.all.each do |m|
  178. report_file.write("<module_detail>\n")
  179. #m_id = m.attributes["id"]
  180. # Module attributes
  181. m.attributes.each_pair do |k,v|
  182. el = create_xml_element(k,v)
  183. report_file.write(" #{el}\n") # Not checking types
  184. end
  185. # Authors sub-elements
  186. # @todo https://www.pivotaltracker.com/story/show/48451001
  187. report_file.write(" <module_authors>\n")
  188. m.authors.each do |d|
  189. d.attributes.each_pair do |k,v|
  190. el = create_xml_element(k,v)
  191. report_file.write(" #{el}\n")
  192. end
  193. end
  194. report_file.write(" </module_authors>\n")
  195. # Refs sub-elements
  196. # @todo https://www.pivotaltracker.com/story/show/48451001
  197. report_file.write(" <module_refs>\n")
  198. m.refs.each do |d|
  199. d.attributes.each_pair do |k,v|
  200. el = create_xml_element(k,v)
  201. report_file.write(" #{el}\n")
  202. end
  203. end
  204. report_file.write(" </module_refs>\n")
  205. # Archs sub-elements
  206. # @todo https://www.pivotaltracker.com/story/show/48451001
  207. report_file.write(" <module_archs>\n")
  208. m.archs.each do |d|
  209. d.attributes.each_pair do |k,v|
  210. el = create_xml_element(k,v)
  211. report_file.write(" #{el}\n")
  212. end
  213. end
  214. report_file.write(" </module_archs>\n")
  215. # Platforms sub-elements
  216. # @todo https://www.pivotaltracker.com/story/show/48451001
  217. report_file.write(" <module_platforms>\n")
  218. m.platforms.each do |d|
  219. d.attributes.each_pair do |k,v|
  220. el = create_xml_element(k,v)
  221. report_file.write(" #{el}\n")
  222. end
  223. end
  224. report_file.write(" </module_platforms>\n")
  225. # Targets sub-elements
  226. # @todo https://www.pivotaltracker.com/story/show/48451001
  227. report_file.write(" <module_targets>\n")
  228. m.targets.each do |d|
  229. d.attributes.each_pair do |k,v|
  230. el = create_xml_element(k,v)
  231. report_file.write(" #{el}\n")
  232. end
  233. end
  234. report_file.write(" </module_targets>\n")
  235. # Actions sub-elements
  236. # @todo https://www.pivotaltracker.com/story/show/48451001
  237. report_file.write(" <module_actions>\n")
  238. m.actions.each do |d|
  239. d.attributes.each_pair do |k,v|
  240. el = create_xml_element(k,v)
  241. report_file.write(" #{el}\n")
  242. end
  243. end
  244. report_file.write(" </module_actions>\n")
  245. # Mixins sub-elements
  246. # @todo https://www.pivotaltracker.com/story/show/48451001
  247. report_file.write(" <module_mixins>\n")
  248. m.mixins.each do |d|
  249. d.attributes.each_pair do |k,v|
  250. el = create_xml_element(k,v)
  251. report_file.write(" #{el}\n")
  252. end
  253. end
  254. report_file.write(" </module_mixins>\n")
  255. report_file.write("</module_detail>\n")
  256. end
  257. report_file.flush
  258. end
  259. # ActiveRecord's to_xml is easy and wrong. This isn't, on both counts.
  260. def extract_host_info(report_file)
  261. @hosts.each do |h|
  262. report_file.write(" <host>\n")
  263. host_id = h.attributes["id"]
  264. # Host attributes
  265. h.attributes.each_pair do |k,v|
  266. # Convert IPAddr -> String
  267. v = v.to_s if k == 'address'
  268. el = create_xml_element(k,v)
  269. report_file.write(" #{el}\n") # Not checking types
  270. end
  271. # Host details sub-elements
  272. report_file.write(" <host_details>\n")
  273. h.host_details.each do |d|
  274. report_file.write(" <host_detail>\n")
  275. d.attributes.each_pair do |k,v|
  276. el = create_xml_element(k,v)
  277. report_file.write(" #{el}\n")
  278. end
  279. report_file.write(" </host_detail>\n")
  280. end
  281. report_file.write(" </host_details>\n")
  282. # Host exploit attempts sub-elements
  283. report_file.write(" <exploit_attempts>\n")
  284. h.exploit_attempts.each do |d|
  285. report_file.write(" <exploit_attempt>\n")
  286. d.attributes.each_pair do |k,v|
  287. el = create_xml_element(k,v)
  288. report_file.write(" #{el}\n")
  289. end
  290. report_file.write(" </exploit_attempt>\n")
  291. end
  292. report_file.write(" </exploit_attempts>\n")
  293. # Service sub-elements
  294. report_file.write(" <services>\n")
  295. @services.where(host_id: host_id).each do |e|
  296. report_file.write(" <service>\n")
  297. e.attributes.each_pair do |k,v|
  298. el = create_xml_element(k,v)
  299. report_file.write(" #{el}\n")
  300. end
  301. report_file.write(" </service>\n")
  302. end
  303. report_file.write(" </services>\n")
  304. # Notes sub-elements
  305. report_file.write(" <notes>\n")
  306. @notes.where(host_id: host_id).each do |e|
  307. report_file.write(" <note>\n")
  308. e.attributes.each_pair do |k,v|
  309. el = create_xml_element(k,v)
  310. report_file.write(" #{el}\n")
  311. end
  312. report_file.write(" </note>\n")
  313. end
  314. report_file.write(" </notes>\n")
  315. # Vulns sub-elements
  316. report_file.write(" <vulns>\n")
  317. @vulns.where(host_id: host_id).each do |e|
  318. report_file.write(" <vuln>\n")
  319. e.attributes.each_pair do |k,v|
  320. el = create_xml_element(k,v)
  321. report_file.write(" #{el}\n")
  322. end
  323. # Notes attached to vulns instead of the host
  324. report_file.write(" <notes>\n")
  325. @notes.where(vuln_id: e.id).each do |note|
  326. report_file.write(" <note>\n")
  327. note.attributes.each_pair do |k,v|
  328. el = create_xml_element(k,v)
  329. report_file.write(" #{el}\n")
  330. end
  331. report_file.write(" </note>\n")
  332. end
  333. report_file.write(" </notes>\n")
  334. # References
  335. report_file.write(" <refs>\n")
  336. e.refs.each do |ref|
  337. el = create_xml_element("ref",ref.name)
  338. report_file.write(" #{el}\n")
  339. end
  340. report_file.write(" </refs>\n")
  341. # Vuln details sub-elements
  342. report_file.write(" <vuln_details>\n")
  343. e.vuln_details.each do |d|
  344. report_file.write(" <vuln_detail>\n")
  345. d.attributes.each_pair do |k,v|
  346. el = create_xml_element(k,v)
  347. report_file.write(" #{el}\n")
  348. end
  349. report_file.write(" </vuln_detail>\n")
  350. end
  351. report_file.write(" </vuln_details>\n")
  352. # Vuln attempts sub-elements
  353. report_file.write(" <vuln_attempts>\n")
  354. e.vuln_attempts.each do |d|
  355. report_file.write(" <vuln_attempt>\n")
  356. d.attributes.each_pair do |k,v|
  357. el = create_xml_element(k,v)
  358. report_file.write(" #{el}\n")
  359. end
  360. report_file.write(" </vuln_attempt>\n")
  361. end
  362. report_file.write(" </vuln_attempts>\n")
  363. report_file.write(" </vuln>\n")
  364. end
  365. report_file.write(" </vulns>\n")
  366. report_file.write(" </host>\n")
  367. end
  368. report_file.flush
  369. end
  370. # Extract event data from @events
  371. def extract_event_info(report_file)
  372. @events.each do |e|
  373. report_file.write(" <event>\n")
  374. e.attributes.each_pair do |k,v|
  375. el = create_xml_element(k,v)
  376. report_file.write(" #{el}\n")
  377. end
  378. report_file.write(" </event>\n")
  379. report_file.write("\n")
  380. end
  381. report_file.flush
  382. end
  383. # Extract service data from @services
  384. def extract_service_info(report_file)
  385. @services.each do |e|
  386. report_file.write(" <service>\n")
  387. e.attributes.each_pair do |k,v|
  388. el = create_xml_element(k,v)
  389. report_file.write(" #{el}\n")
  390. end
  391. report_file.write(" </service>\n")
  392. report_file.write("\n")
  393. end
  394. report_file.flush
  395. end
  396. # Extract service data from @services
  397. def extract_service_info(report_file)
  398. @services.each do |e|
  399. report_file.write(" <service>\n")
  400. e.attributes.each_pair do |k,v|
  401. el = create_xml_element(k,v)
  402. report_file.write(" #{el}\n")
  403. end
  404. report_file.write(" </service>\n")
  405. report_file.write("\n")
  406. end
  407. report_file.flush
  408. end
  409. # Extract web site data from @web_sites
  410. def extract_web_site_info(report_file)
  411. @web_sites.each do |e|
  412. report_file.write(" <web_site>\n")
  413. e.attributes.each_pair do |k,v|
  414. el = create_xml_element(k,v)
  415. report_file.write(" #{el}\n")
  416. end
  417. site = e
  418. el = create_xml_element("host", site.service.host.address)
  419. report_file.write(" #{el}\n")
  420. el = create_xml_element("port", site.service.port)
  421. report_file.write(" #{el}\n")
  422. el = create_xml_element("ssl", site.service.name == "https")
  423. report_file.write(" #{el}\n")
  424. report_file.write(" </web_site>\n")
  425. end
  426. report_file.flush
  427. end
  428. # Extract web pages, forms, and vulns
  429. def extract_web_info(report_file, tag, entries)
  430. entries.each do |e|
  431. report_file.write(" <#{tag}>\n")
  432. e.attributes.each_pair do |k,v|
  433. el = create_xml_element(k,v)
  434. report_file.write(" #{el}\n")
  435. end
  436. site = e.web_site
  437. el = create_xml_element("vhost", site.vhost)
  438. report_file.write(" #{el}\n")
  439. el = create_xml_element("host", site.service.host.address)
  440. report_file.write(" #{el}\n")
  441. el = create_xml_element("port", site.service.port)
  442. report_file.write(" #{el}\n")
  443. el = create_xml_element("ssl", site.service.name == "https")
  444. report_file.write(" #{el}\n")
  445. report_file.write(" </#{tag}>\n")
  446. end
  447. report_file.flush
  448. end
  449. # Extract web pages
  450. def extract_web_page_info(report_file)
  451. extract_web_info(report_file, "web_page", @web_pages)
  452. end
  453. # Extract web forms
  454. def extract_web_form_info(report_file)
  455. extract_web_info(report_file, "web_form", @web_forms)
  456. end
  457. # Extract web vulns
  458. def extract_web_vuln_info(report_file)
  459. extract_web_info(report_file, "web_vuln", @web_vulns)
  460. end
  461. end
  462. end
  463. end