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-3des-ciphers ssl-static-key-ciphers].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!(/(.*?)<\/Paragraph>/m){|m| "\nbc. #{ $1 }\n\n"}
result.gsub!(/(.*?)<\/Paragraph>/m){|m| "#{ $1 }\n"}
result.gsub!(//, '')
result.gsub!(/<\/Paragraph>/, '')
result.gsub!(/(.*?)<\/UnorderedList>/m){|m| "#{ $1 }"}
result.gsub!(/(.*?)<\/ListItem>/m){|m| "#{ $1 }\n"}
result.gsub!(/ /, '')
result.gsub!(/\t\t/, '')
result.gsub!(//i) { "\"#{$1.strip}\":#{$2.strip} " }
result.gsub!(//i) { "\"#{$2.strip}\":#{$1.strip} " }
result.gsub!(/(.*?)<\/URLLink>/m) {|m| "\"#{$4.strip}\":#{$2.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