diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a076db..1fc1dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v4.12.0 (Month 2024) + - Format UnorderedList/OrderedList to textile + v4.12.0 (May 2024) - Migrate integration to use Mappings Manager - Update Dradis links in README diff --git a/lib/dradis/plugins/nexpose.rb b/lib/dradis/plugins/nexpose.rb index d062ceb..85a385c 100644 --- a/lib/dradis/plugins/nexpose.rb +++ b/lib/dradis/plugins/nexpose.rb @@ -10,3 +10,4 @@ module Nexpose require 'dradis/plugins/nexpose/importer' require 'dradis/plugins/nexpose/mapping' require 'dradis/plugins/nexpose/version' +require 'dradis/plugins/nexpose/xml_formatter' diff --git a/lib/dradis/plugins/nexpose/xml_formatter.rb b/lib/dradis/plugins/nexpose/xml_formatter.rb new file mode 100644 index 0000000..5879977 --- /dev/null +++ b/lib/dradis/plugins/nexpose/xml_formatter.rb @@ -0,0 +1,81 @@ +module Dradis::Plugins::Nexpose + class XmlFormatter + def format_html_content(source) + result = format_list(source) + + cleanup_html(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!(/ /, '') + 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 + + private + + def format_list(source) + if source.xpath('./UnorderedList | ./OrderedList').any? + format_nexpose_list(source) + else + source + end + end + + def format_nexpose_list(xml, depth = 1) + xml.xpath('./UnorderedList | ./OrderedList').map do |list| + list_item_element = list.name == 'UnorderedList' ? '*' : '#' + + list.xpath('./ListItem').map do |list_item| + paragraphs = list_item.xpath('./Paragraph') + + list_item_text = + if paragraphs.any? + paragraphs.map do |paragraph| + # nodes can either have more lists or just text + if paragraph.xpath('./UnorderedList | ./OrderedList').any? + format_nexpose_list(paragraph, depth + 1) + else + cleanup_html(paragraph.to_s).chomp + end + end.join("\n") + else + list_item.text + end + + ''.ljust(depth, list_item_element) + ' ' + list_item_text + end.join("\n") + end.join("\n") + end + end +end diff --git a/lib/nexpose/vulnerability.rb b/lib/nexpose/vulnerability.rb index 6363f83..cc1866b 100644 --- a/lib/nexpose/vulnerability.rb +++ b/lib/nexpose/vulnerability.rb @@ -20,7 +20,7 @@ def initialize(xml_node) def supported_tags [ # attributes - :added, :cvss_score, :cvss_vector, :modified, :nexpose_id, :pci_severity, + :added, :cvss_score, :cvss_vector, :modified, :nexpose_id, :pci_severity, :published, :risk_score, :severity, :title, # simple tags @@ -34,10 +34,9 @@ def supported_tags ] end - # This allows external callers (and specs) to check for implemented # properties - def respond_to?(method, include_private=false) + def respond_to?(method, include_private = false) return true if supported_tags.include?(method.to_sym) super end @@ -49,7 +48,6 @@ def respond_to?(method, include_private=false) # 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 @@ -62,11 +60,11 @@ def method_missing(method, *args) # 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', - :risk_score => 'riskScore', - :cvss_score => 'cvssScore', - :cvss_vector =>'cvssVector' + nexpose_id: 'id', + pci_severity: 'pciSeverity', + risk_score: 'riskScore', + cvss_score: 'cvssScore', + cvss_vector: 'cvssVector' } method_name = translations_table.fetch(method, method.to_s) @@ -78,13 +76,14 @@ def method_missing(method, *args) nest = @xml.xpath("./#{method_name}").first # We need to clean up tags that have HTML content in them + formatter = Dradis::Plugins::Nexpose::XmlFormatter.new if tags_with_html_content.include?(method) - result = cleanup_html(tag) + result = formatter.format_html_content(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) + return formatter.cleanup_nested(nest) else return tag end @@ -96,7 +95,7 @@ def method_missing(method, *args) return @xml.xpath("//test[@id='#{vuln_id}']/Paragraph"). text.split("\n"). collect(&:strip). - reject{|line| line.empty?}.join("\n") + reject { |line| line.empty? }.join("\n") end nil @@ -106,46 +105,7 @@ def method_missing(method, *args) 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.gsub!(/\n(.*?)!(.*?)/) { "\nbc. #{ $1 }!#{ $2 }\n" } result end @@ -156,6 +116,5 @@ def tags_with_html_content def tags_with_nested_content [:references, :tags] end - end end diff --git a/spec/fixtures/files/full.xml b/spec/fixtures/files/full.xml index a8868dc..3e25d86 100644 --- a/spec/fixtures/files/full.xml +++ b/spec/fixtures/files/full.xml @@ -118,11 +118,27 @@ + Microsoft Windows - You can remove inode information from the ETag header by adding the following directive to your Apache config: - FileETag MTime Size + + Open the Windows Control Panel. + Select "Administrative Tools". + + + Microsoft Windows + + + Open the "Performance and Maintenance" control panel. + Select "Administrative Tools". + Restart the system for the changes to take effect. + + Microsoft Windows + + + Open the "Administrative Tools" control panel. + Restart the system for the changes to take effect. OpenBSD Download and apply the patch from: diff --git a/spec/fixtures/files/lists.xml b/spec/fixtures/files/lists.xml new file mode 100644 index 0000000..ef7b87d --- /dev/null +++ b/spec/fixtures/files/lists.xml @@ -0,0 +1,26 @@ + + + + Microsoft Windows + + + Open the Windows Control Panel. + Select "Administrative Tools". + + + + + Microsoft Windows + + + Open the "Performance and Maintenance" control panel. + Select "Administrative Tools". + Restart the system for the changes to take effect. + + Microsoft Windows + + + Open the "Administrative Tools" control panel. + Restart the system for the changes to take effect. + + diff --git a/spec/nexpose_upload_spec.rb b/spec/nexpose_upload_spec.rb index 78f40eb..cebf1c5 100644 --- a/spec/nexpose_upload_spec.rb +++ b/spec/nexpose_upload_spec.rb @@ -187,6 +187,22 @@ @importer.import(file: @fixtures_dir + '/full.xml') end + + it 'formats the list to textile lists' do + expect(@content_service).to receive(:create_issue) do |args| + expect(args[:text]).to include("#[Title]#\nApache HTTPD: error responses can expose cookies (CVE-2012-0053)") + OpenStruct.new(args) + end.once + + expect(@content_service).to receive(:create_issue) do |args| + expect(args[:text]).to include('* Microsoft Windows') + expect(args[:text]).to include('## Open the Windows Control Panel.') + expect(args[:text]).to include('## Select "Administrative Tools".') + OpenStruct.new(args) + end.once + + @importer.import(file: @fixtures_dir + '/full.xml') + end end describe 'Importer: Full with duplicate nodes' do diff --git a/spec/xml_formatter_spec.rb b/spec/xml_formatter_spec.rb new file mode 100644 index 0000000..d6a0a85 --- /dev/null +++ b/spec/xml_formatter_spec.rb @@ -0,0 +1,23 @@ +# Run the spec by running the command: rspec spec/xml_formatter_spec.rb" + +require 'spec_helper' + +describe Dradis::Plugins::Nexpose::XmlFormatter do + let(:source) { File.read(File.expand_path('../fixtures/files/lists.xml', __FILE__)) } + + it 'parses the and elements' do + xml = Nokogiri::XML(source).at_xpath('./ContainerBlockElement') + expect(Dradis::Plugins::Nexpose::XmlFormatter.new.format_html_content(xml)).to eq( + "* Microsoft Windows\n"\ + "## Open the Windows Control Panel.\n"\ + "## Select \"Administrative Tools\".\n"\ + "* Microsoft Windows\n"\ + "## Open the \"Performance and Maintenance\" control panel.\n"\ + "## Select \"Administrative Tools\".\n"\ + "## Restart the system for the changes to take effect.\n"\ + "* Microsoft Windows\n"\ + "## Open the \"Administrative Tools\" control panel.\n"\ + '## Restart the system for the changes to take effect.' + ) + end +end