Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/parser/foundstone_document.rb
Views: 11780
# -*- coding: binary -*-1require "rex/parser/nokogiri_doc_mixin"23module Rex4module Parser56# If Nokogiri is available, define Template document class.7load_nokogiri && class FoundstoneDocument < Nokogiri::XML::SAX::Document89include NokogiriDocMixin1011def start_document12@report_type_ok = true # Optimistic13end1415# Triggered every time a new element is encountered. We keep state16# ourselves with the @state variable, turning things on when we17# get here (and turning things off when we exit in end_element()).18def start_element(name=nil,attrs=[])19attrs = normalize_attrs(attrs)20block = @block21return unless @report_type_ok22@state[:current_tag][name] = true23case name24when "ReportInfo"25check_for_correct_report_type(attrs,&block)26when "Host"27record_host(attrs)28when "Service"29record_service(attrs)30when "Port", "Protocol", "Banner"31@state[:has_text] = true32when "Vuln" # under VulnsFound, ignore risk 0 things33record_vuln(attrs)34when "Risk" # for Vuln35@state[:has_text] = true36when "CVE" # Under Vuln37@state[:has_text] = true38end39end4041# When we exit a tag, this is triggered.42def end_element(name=nil)43block = @block44return unless @report_type_ok45case name46when "Host" # Wrap it up47collect_host_data48host_object = report_host &block49if host_object50db.report_import_note(@args[:workspace],host_object)51report_fingerprint(host_object)52report_services(host_object)53report_vulns(host_object)54end55# Reset the state once we close a host56@state.delete_if {|k| k != :current_tag}57@report_data = {:workspace => args[:workspace]}58when "Port"59@state[:has_text] = false60collect_port61when "Protocol"62@state[:has_text] = false63collect_protocol64when "Banner"65collect_banner66@state[:has_text] = false67when "Service"68collect_service69when "Vuln"70collect_vuln71when "Risk"72@state[:has_text] = false73collect_risk74when "CVE"75@state[:has_text] = false76collect_cve77end78@state[:current_tag].delete name79end8081# Nothing technically stopping us from parsing this as well,82# but saving this for later83def check_for_correct_report_type(attrs,&block)84report_type = attr_hash(attrs)["ReportType"]85if report_type == "Network Inventory"86@report_type_ok = true87else88if report_type == "Risk Data"89msg = "The Foundstone/Mcafee report type '#{report_type}' is not currently supported"90msg << ",\nso no data will be imported. Please use the 'Network Inventory' report instead."91else92msg = ".\nThe Foundstone/Macafee report type '#{report_type}' is unsupported."93end94db.emit(:warning,msg,&block) if block95@report_type_ok = false96end97end9899def collect_risk100return unless in_tag("VulnsFound")101return unless in_tag("HostData")102return unless in_tag("Host")103risk = @text.to_s.to_i104@state[:vuln][:risk] = risk105@text = nil106end107108def collect_cve109return unless in_tag("VulnsFound")110return unless in_tag("HostData")111return unless in_tag("Host")112cve = @text.to_s113@state[:vuln][:cves] ||= []114@state[:vuln][:cves] << cve unless cve == "CVE-MAP-NOMATCH"115@text = nil116end117118# Determines if we should keep the vuln or not. Note that119# we cannot tie them to a service.120def collect_vuln121return unless in_tag("VulnsFound")122return unless in_tag("HostData")123return unless in_tag("Host")124return if @state[:vuln][:risk] == 0125@report_data[:vulns] ||= []126vuln_hash = {}127vuln_hash[:name] = @state[:vuln]["VulnName"]128refs = []129refs << "FID-#{@state[:vuln]["id"]}"130if @state[:vuln][:cves]131@state[:vuln][:cves].each {|cve| refs << cve}132end133vuln_hash[:refs] = refs134@report_data[:vulns] << vuln_hash135end136137# These are per host.138def record_vuln(attrs)139return unless in_tag("VulnsFound")140return unless in_tag("HostData")141return unless in_tag("Host")142@state[:vulns] ||= []143144@state[:vuln] = attr_hash(attrs) # id and VulnName145end146147def record_service(attrs)148return unless in_tag("ServicesFound")149return unless in_tag("Host")150@state[:service] = attr_hash(attrs)151end152153def collect_port154return unless in_tag("Service")155return unless in_tag("ServicesFound")156return unless in_tag("Host")157return if @text.nil? || @text.empty?158@state[:service][:port] = @text.strip159@text = nil160end161162def collect_protocol163return unless in_tag("Service")164return unless in_tag("ServicesFound")165return unless in_tag("Host")166return if @text.nil? || @text.empty?167@state[:service][:proto] = @text.strip168@text = nil169end170171def collect_banner172return unless in_tag("Service")173return unless in_tag("ServicesFound")174return unless in_tag("Host")175return if @text.nil? || @text.empty?176banner = normalize_foundstone_banner(@state[:service]["ServiceName"],@text)177unless banner.nil? || banner.empty?178@state[:service][:banner] = banner179end180@text = nil181end182183def collect_service184return unless in_tag("ServicesFound")185return unless in_tag("Host")186return unless @state[:service][:port]187@report_data[:ports] ||= []188port_hash = {}189port_hash[:port] = @state[:service][:port]190port_hash[:proto] = @state[:service][:proto]191port_hash[:info] = @state[:service][:banner]192port_hash[:name] = db.nmap_msf_service_map(@state[:service]["ServiceName"])193@report_data[:ports] << port_hash194end195196def record_host(attrs)197return unless in_tag("HostData")198@state[:host] = attr_hash(attrs)199end200201def collect_host_data202@report_data[:host] = @state[:host]["IPAddress"]203if @state[:host]["NBName"] && !@state[:host]["NBName"].empty?204@report_data[:name] = @state[:host]["NBName"]205elsif @state[:host]["DNSName"] && !@state[:host]["DNSName"].empty?206@report_data[:name] = @state[:host]["DNSName"]207end208if @state[:host]["OSName"] && !@state[:host]["OSName"].empty?209@report_data[:os_fingerprint] = @state[:host]["OSName"]210end211@report_data[:state] = Msf::HostState::Alive212@report_data[:mac] = @state[:mac] if @state[:mac]213end214215def report_host(&block)216return unless in_tag("HostData")217if host_is_okay218db.emit(:address,@report_data[:host],&block) if block219host_info = @report_data.merge(:workspace => @args[:workspace])220db_report(:host,host_info)221end222end223224def report_fingerprint(host_object)225fp_note = {226:workspace => host_object.workspace,227:host => host_object,228:type => 'host.os.foundstone_fingerprint',229:data => {:os => @report_data[:os_fingerprint] }230}231db_report(:note, fp_note)232end233234def report_services(host_object)235return unless in_tag("HostData")236return unless host_object.kind_of? ::Mdm::Host237return unless @report_data[:ports]238return if @report_data[:ports].empty?239@report_data[:ports].each do |svc|240db_report(:service, svc.merge(:host => host_object))241end242end243244def report_vulns(host_object)245return unless in_tag("HostData")246return unless host_object.kind_of? ::Mdm::Host247return unless @report_data[:vulns]248return if @report_data[:vulns].empty?249@report_data[:vulns].each do |vuln|250db_report(:vuln, vuln.merge(:host => host_object))251end252end253254# Foundstone's banners are pretty free-form255# and often not just banners. Clean them up256# for the :info field, delegate off for other257# protocol data we can use.258def normalize_foundstone_banner(service,banner)259return "" if(banner.nil? || banner.strip.empty?)260if first_line_only? service261return (first_line banner)262elsif needs_more_processing? service263return process_service(service,banner)264else265return (first_line banner)266end267end268269# Services where we only care about the first270# line of the banner tag.271def first_line_only?(service)272svcs = %w{273vnc ftp ftps smtp oracle-tns nntp ssh ntp274}2759.times {|i| svcs << "vnc-#{i}"}276svcs.include? service277end278279# Services where we need to do more processing280# before handing the banner back.281def needs_more_processing?(service)282svcs = %w{283microsoft-ds loc-srv http https sunrpc netbios-ns284}285svcs.include? service286end287288def first_line(str)289str.split("\n").first.to_s.strip290end291292# XXX: Actually implement more of these293def process_service(service,banner)294meth = "process_service_#{service.gsub("-","_")}"295if self.respond_to?(meth, true)296self.send meth, banner297else298return (first_line banner)299end300end301302# XXX: Register a proper netbios note as the regular303# scanner does.304def process_service_netbios_ns(banner)305mac_regex = /[0-9A-Fa-f:]{17}/306@state[:mac] = banner[mac_regex]307first_line banner308end309310# XXX: Make this behave more like the smb scanner311def process_service_microsoft_ds(banner)312lm_regex = /Native LAN Manager/313lm_banner = nil314banner.each_line { |line|315if line[lm_regex]316lm_banner = line317break318end319}320lm_banner || first_line(banner)321end322323def process_service_http(banner)324server = nil325banner.each_line do |line|326if line =~ /^Server:\s+(.*)/327server = $1328break329end330end331server || first_line(banner)332end333334alias :process_service_https :process_service_http335alias :process_service_rtsp :process_service_http336337end338339end340end341342343344