module Nexpose # This class represents each of the /NexposeReport[@version='1.0']/VulnerabilityDefinitions/vulnerability # elements in the Nexpose Full XML document. # # It provides a convenient way to access the information scattered all over # the XML in attributes and nested tags. # # Instead of providing separate methods for each supported property we rely # on Ruby's #method_missing to do most of the work. class Vulnerability SSL_CIPHER_VULN_IDS = %w[ssl-anon-ciphers ssl-des-ciphers ssl-3des-ciphers ssl-export-ciphers ssl-null-ciphers ssl-only-weak-ciphers ssl-static-key-ciphers ssl-weak-message-authentication-code-algorithms rc4-cve-2013-2566 ssl-cve-2016-2183-sweet32 tls-dhe-export-ciphers-cve-2015-4000].freeze # Accepts an XML node from Nokogiri::XML. def initialize(xml_node) @xml = xml_node end # List of supported tags. They can be attributes, simple descendans or # collections (e.g. , ) def supported_tags [ # attributes :nexpose_id, :title, :severity, :pci_severity, :cvss_score, :cvss_vector, :published, :added, :modified, # simple tags :description, :solution, # multiple tags :references, :tags, # evidence tag :details ] end # This allows external callers (and specs) to check for implemented # properties def respond_to?(method, include_private=false) return true if supported_tags.include?(method.to_sym) super end # This method is invoked by Ruby when a method that is not defined in this # instance is called. # # In our case we inspect the @method@ parameter and try to find the # attribute, simple descendent or collection that it maps to in the XML # tree. def method_missing(method, *args) # We could remove this check and return nil for any non-recognized tag. # The problem would be that it would make tricky to debug problems with # typos. For instance: <>.potr would return nil instead of raising an # exception unless supported_tags.include?(method) super return end # First we try the attributes. In Ruby we use snake_case, but in XML # CamelCase is used for some attributes translations_table = { :nexpose_id => 'id', :pci_severity => 'pciSeverity', :cvss_score => 'cvssScore', :cvss_vector =>'cvssVector' } method_name = translations_table.fetch(method, method.to_s) return @xml.attributes[method_name].value if @xml.attributes.key?(method_name) # Then we try simple children tags: description, solution tag = @xml.xpath("./#{method_name}/ContainerBlockElement").first # Then we try the tags with nested content nest = @xml.xpath("./#{method_name}").first # We need to clean up tags that have HTML content in them if tags_with_html_content.include?(method) result = cleanup_html(tag) result = add_bc_to_ssl_cipher_list(result) if SSL_CIPHER_VULN_IDS.include?(@xml.attributes['id'].value) return result # And we need to clean up the tags with nested content in them elsif tags_with_nested_content.include?(method) return cleanup_nested(nest) else return tag end # Handle evidence creation if method_name == 'details' vuln_id = @xml.attributes['id'].value return @xml.xpath("//test[@id='#{vuln_id}']/Paragraph"). text.split("\n"). collect(&:strip). reject{|line| line.empty?}.join("\n") end nil end private def add_bc_to_ssl_cipher_list(source) result = source.to_s result.gsub!(/\n(.*?)!(.*?)/){"\nbc. #{ $1 }!#{ $2 }\n"} result end def cleanup_html(source) result = source.to_s result.gsub!(/(.*?)<\/ContainerBlockElement>/m){|m| "#{ $1 }"} result.gsub!(/(\s*)(.*?)<\/Paragraph>(\s*)<\/Paragraph>/mi) do text = $2 text[/\n/] ? "\nbc.. #{ text }\n\np. " : "@#{text}@" end result.gsub!(/(.*?)<\/Paragraph>/mi) do text = $1 text[/\n/] ? "\nbc.. #{ text }\n\np. " : "@#{text}@" end result.gsub!(/(.*?)<\/Paragraph>/m){|m| "#{ $1 }\n"} result.gsub!(/|<\/Paragraph>/, '') result.gsub!(/(.*?)<\/UnorderedList>/m){|m| "#{ $2 }"} result.gsub!(/(.*?)<\/OrderedList>/m){|m| "#{ $2 }"} result.gsub!(/|<\/ListItem>/, '') result.gsub!(/ /, '') result.gsub!(/ /, '') result.gsub!(/\t\t/, '') result.gsub!(/(.*?)<\/URLLink>/im) { "\"#{$4.strip}\":#{$2.strip} " } result.gsub!(//i) { "\"#{$1.strip}\":#{$3.strip} " } result.gsub!(//i) { "\"#{$3.strip}\":#{$1.strip} " } result.gsub!(/>/, '>') result.gsub!(/</, '<') result end def cleanup_nested(source) result = source.to_s result.gsub!(//, '') result.gsub!(/<\/references>/, '') result.gsub!(/(.*?)<\/reference>/i) {"#{$1.strip}: #{$2.strip}\n"} result.gsub!(//, '') result.gsub!(/<\/tags>/, '') result.gsub!(/(.*?)<\/tag>/) {"#{$1}\n"} result.gsub!(/ /, '') result end def tags_with_html_content [:description, :solution] end def tags_with_nested_content [:references, :tags] end end end