module Netsparker # This class represents each of the /netsparker/vulnerability elements in the # Netsparker 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 attr_accessor :xml # 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. def supported_tags [ # made-up tags :title, # simple tags :actions_to_take, :certainty, :description, :external_references, :extrainformation, :impact, :rawrequest, :rawresponse, :remedy, :remedy_references, :required_skills_for_exploitation, :severity, :type, :url, # tags that correspond to Evidence # nested tags :classification_capec, :classification_cvss_vector, :classification_cvss_base_value, :classification_cvss_base_severity, :classification_cvss_environmental_value, :classification_cvss_environmental_severity, :classification_cvss_temporal_value, :classification_cvss_temporal_severity, :classification_cwe, :classification_hipaa, :classification_owasp2013, :classification_owasppc, :classification_pci31, :classification_pci32, :classification_wasc, # multiple tags ] 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 # Any fields where a simple .camelcase() won't work we need to translate, # this includes acronyms (e.g. :cwe would become 'Cwe') and simple nested # tags. translations_table = { actions_to_take: 'actionsToTake', classification_capec: 'classification/CAPEC', classification_cwe: 'classification/CWE', classification_cvss_vector: 'classification/CVSS/vector', classification_cvss_base_value: "classification/CVSS/score/type[text()='Base']/../value", classification_cvss_base_severity: "classification/CVSS/score/type[text()='Base']/../severity", classification_cvss_environmental_value: "classification/CVSS/score/type[text()='Environmental']/../value", classification_cvss_environmental_severity: "classification/CVSS/score/type[text()='Environmental']/../severity", classification_cvss_temporal_value: "classification/CVSS/score/type[text()='Temporal']/../value", classification_cvss_temporal_severity: "classification/CVSS/score/type[text()='Temporal']/../severity", classification_hipaa: 'classification/HIPAA', classification_owasp2013: 'classification/OWASP2013', classification_owasppc: 'classification/OWASPPC', classification_pci31: 'classification/PCI31', classification_pci32: 'classification/PCI32', classification_wasc: 'classification/WASC', external_references: 'externalReferences', remedy_references: 'remedyReferences', required_skills_for_exploitation: 'requiredSkillsForExploitation' } method_name = translations_table.fetch(method, method.to_s) # We've got a virtual method :title which isn't provided by Netsparker # but that most users will be expecting. return type.underscore.humanize if method == :title # first we try the attributes: # return @xml.attributes[method_name].value if @xml.attributes.key?(method_name) # then we try the children tags tag = xml.at_xpath("./#{method_name}") if tag && !tag.text.blank? text = tag.text return tags_with_html_content.include?(method) ? cleanup_html(text) : text else return 'n/a' end # nothing found return nil end private def cleanup_html(source) result = source.dup result.gsub!(/"/, '"') result.gsub!(/&/, '&') result.gsub!(/</, '<') result.gsub!(/>/, '>') result.gsub!(/'/, '\'') result.gsub!(/\<\!\[CDATA\[(.*?)\]\]\>/m, '\1') result.gsub!(/(.*?)<\/b>/) { "*#{$1}*" } result.gsub!(/(.*?)<\/i>/) { "_#{$1}_" } result.gsub!(/(.*?)<\/em>/) { "*#{$1}*" } result.gsub!(/

(.*?)<\/h2>/) { "*#{$1}*" } result.gsub!(/(.*?)<\/strong>/) { "*#{$1}*" } result.gsub!(/(
)|()/, "\n") result.gsub!(/(
)|(<\/div>)/, "") result.gsub!(/(.*?)<\/font>/m, '\1') result.gsub!(/

(.*?)<\/p>/) { "\n#{$2}\n" } result.gsub!(/(.*?)<\/span>/, '\2') result.gsub!(/(

)|(<\/p>)/, "\n") result.gsub!(/\n[a-z]\. /, "\n\# ") result.gsub!(/(.*?)<\/a>/i) { "\"#{$3.strip}\":#{$1.strip}" } result.gsub!(/(.*?)<\/a>/i) { "\"#{$2.strip}\":#{$1.strip}" } result.gsub!(/(.*?)<\/pre><\/code>/m) {|m| "\n\nbc.. #{$1}\n\np. \n" } result.gsub!(/(.*?)<\/pre>/m) {|m| "\n\nbc.. #{$1}\n\np. \n" } result.gsub!(/(.*?)<\/code>/m) {|m| "\n\nbc.. #{$1}\n\np. \n" } result.gsub!(/(