module Dradis::Plugins::Nexpose::Formats # This module knows how to parse Nexpose Ful XML format. module Full private def process_full(doc) note_text = nil @vuln_list = [] evidence = Hash.new { |h, k| h[k] = {} } # First, extract scans scan_node = content_service.create_node(label: 'Nexpose Scan Summary') logger.info{ "\tProcessing scan summary" } doc.xpath('//scans/scan').each do |xml_scan| note_text = template_service.process_template(template: 'full_scan', data: xml_scan) content_service.create_note(node: scan_node, text: note_text) end # Second, we parse the nodes doc.xpath('//nodes/node').each do |xml_node| nexpose_node = Nexpose::Node.new(xml_node) host_node = content_service.create_node(label: nexpose_node.address, type: :host) logger.info{ "\tProcessing host: #{nexpose_node.address}" } # add the summary note for this host note_text = template_service.process_template(template: 'full_node', data: nexpose_node) content_service.create_note(node: host_node, text: note_text) if host_node.respond_to?(:properties) logger.info{ "\tAdding host properties: :ip and :hostnames"} host_node.set_property(:ip, nexpose_node.address) host_node.set_property(:hostnames, nexpose_node.names) end # inject this node's address into any vulnerabilities identified # # TODO: There is room for improvement here, we could have a hash that # linked vulns with test/service and host to create proper content for # Evidence. nexpose_node.tests.each do |node_test| test_id = node_test[:id].to_s.downcase # We can't use the straightforward version below because Nexpose uses # mixed-case some times (!) # xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[@id='#{node_test[:id]}']").first # See: # http://stackoverflow.com/questions/1625446/problem-with-upper-case-and-lower-case-xpath-functions-in-selenium-ide/1625859#1625859 xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='#{test_id}']").first xml_vuln.add_child("") unless xml_vuln.last_element_child.name == "hosts" if xml_vuln.xpath("./hosts/host[text()='#{nexpose_node.address}']").empty? xml_vuln.last_element_child.add_child("#{nexpose_node.address}") end evidence[test_id][nexpose_node.address] = node_test end nexpose_node.endpoints.each do |endpoint| # endpoint_node = content_service.create_node(label: endpoint.label, parent: host_node) logger.info{ "\t\tEndpoint: #{endpoint.label}" } if host_node.respond_to?(:properties) logger.info{ "\t\tAdding to Services table" } host_node.set_property(:services, { port: endpoint.port.to_i, protocol: endpoint.protocol, state: endpoint.status, name: endpoint.services.map(&:name).join(', ') # reason: port.reason, # product: port.try('service').try('product'), # version: port.try('service').try('version') }) end endpoint.services.each do |service| # add the summary note for this service note_text = template_service.process_template(template: 'full_service', data: service) # content_service.create_note(node: endpoint_node, text: note_text) content_service.create_note(node: host_node, text: note_text) # inject this node's address into any vulnerabilities identified service.tests.each do |service_test| test_id = service_test[:id].to_s.downcase # For some reason Nexpose fails to include the 'http-iis-0011' vulnerability definition next if test_id == 'http-iis-0011' # We can't use the straightforward version below because Nexpose uses # mixed-case some times (!) # xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[@id='#{service_test[:id]}']").first # See: # http://stackoverflow.com/questions/1625446/problem-with-upper-case-and-lower-case-xpath-functions-in-selenium-ide/1625859#1625859 # xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='#{test_id}']").first xml_vuln.add_child("") unless xml_vuln.last_element_child.name == "hosts" if xml_vuln.xpath("./hosts/host[text()='#{nexpose_node.address}']").empty? xml_vuln.last_element_child.add_child("#{nexpose_node.address}") end evidence[test_id][nexpose_node.address] = service_test end end end # add note under this node for each vulnerable ./node/test/ host_node.save end # Third, parse vulnerability definitions definitions_node = content_service.create_node(label: 'Definitions') logger.info{ "\tProcessing issue definitions:" } doc.xpath('//VulnerabilityDefinitions/vulnerability').each do |xml_vulnerability| id = xml_vulnerability['id'].downcase # if @vuln_list.include?(id) issue_text = template_service.process_template( template: 'full_vulnerability', data: xml_vulnerability ) # retrieve hosts affected by this issue (injected in step 2) # # There is no need for the below as Issues are linked to hosts via the # corresponding Evidence instance # # note_text << "\n\n#[host]#\n" # note_text << xml_vulnerability.xpath('./hosts/host').collect(&:text).join("\n") # note_text << "\n\n" # 3.1 create the Issue issue = content_service.create_issue(text: issue_text, id: id) logger.info{ "\tIssue: #{issue.fields ? issue.fields['Title'] : id}" } # 3.2 associate with the nodes via Evidence. # TODO: there is room for improvement here by providing proper Evidence content xml_vulnerability.xpath('./hosts/host').collect(&:text).each do |host_name| # if the node exists, this just returns it host_node = content_service.create_node(label: host_name, type: :host) evidence_content = template_service.process_template( template: 'full_evidence', data: evidence[id][host_name] ) content_service.create_evidence(content: evidence_content, issue: issue, node: host_node) end # end end end # /parse_nexpose_full_xml end end