dradis-netsparker 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ # hook to the framework base clases
2
+ require 'dradis-plugins'
3
+
4
+ # load this add-on's engine
5
+ require 'dradis/plugins/netsparker'
6
+
7
+ # load supporting Netsparker classes
8
+ require 'netsparker/vulnerability'
@@ -0,0 +1,11 @@
1
+ module Dradis
2
+ module Plugins
3
+ module Netsparker
4
+ end
5
+ end
6
+ end
7
+
8
+ require 'dradis/plugins/netsparker/engine'
9
+ require 'dradis/plugins/netsparker/field_processor'
10
+ require 'dradis/plugins/netsparker/importer'
11
+ require 'dradis/plugins/netsparker/version'
@@ -0,0 +1,9 @@
1
+ module Dradis::Plugins::Netsparker
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Dradis::Plugins::Netsparker
4
+
5
+ include ::Dradis::Plugins::Base
6
+ description 'Processes Netsparker XML format'
7
+ provides :upload
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module Dradis::Plugins::Netsparker
2
+ # This processor defers to ::Netsparker::Vulnerability for the issue and
3
+ # evidence templates.
4
+ class FieldProcessor < Dradis::Plugins::Upload::FieldProcessor
5
+
6
+ def post_initialize(args={})
7
+ @netsparker_object = Netsparker::Vulnerability.new(data)
8
+ end
9
+
10
+ def value(args={})
11
+ field = args[:field]
12
+
13
+ # fields in the template are of the form <foo>.<field>, where <foo>
14
+ # is common across all fields for a given template (and meaningless).
15
+ _, name = field.split('.')
16
+
17
+ @netsparker_object.try(name) || 'n/a'
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,19 @@
1
+ module Dradis
2
+ module Plugins
3
+ module Netsparker
4
+ # Returns the version of the currently loaded Netsparker as a <tt>Gem::Version</tt>
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 3
11
+ MINOR = 8
12
+ TINY = 0
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,64 @@
1
+ module Dradis::Plugins::Netsparker
2
+ class Importer < Dradis::Plugins::Upload::Importer
3
+
4
+ # The framework will call this function if the user selects this plugin from
5
+ # the dropdown list and uploads a file.
6
+ # @returns true if the operation was successful, false otherwise
7
+ def import(params={})
8
+ file_content = File.read( params.fetch(:file) )
9
+
10
+ logger.info{'Parsing Netsparker output file...'}
11
+ @doc = Nokogiri::XML( file_content )
12
+ logger.info{'Done.'}
13
+
14
+ if @doc.xpath('/netsparker').empty?
15
+ error = "No scan results were detected in the uploaded file (/netsparker). Ensure you uploaded an Netsparker XML report."
16
+ logger.fatal{ error }
17
+ content_service.create_note text: error
18
+ return false
19
+ end
20
+
21
+ @doc.xpath('/netsparker/target').each do |xml_host|
22
+ process_report_host(xml_host)
23
+ end
24
+
25
+ return true
26
+ end # /import
27
+
28
+ private
29
+
30
+ def process_report_host(xml_host)
31
+ # Create Nodes from the <url> tags
32
+ host_node_label = xml_host.at_xpath('./url').text
33
+ host_node_label = URI.parse(host_node_label).host rescue host_node_label
34
+ logger.info{ "\t\t => Creating new host: #{host_node_label}" }
35
+ host_node = content_service.create_node(label: host_node_label, type: :host)
36
+
37
+ @doc.xpath('/netsparker/vulnerability').each do |xml_vuln|
38
+ process_vuln(xml_vuln, host_node)
39
+ end
40
+
41
+ end
42
+
43
+ def process_vuln(xml_vuln, host_node)
44
+ type = xml_vuln.at_xpath('./type').text()
45
+
46
+ # Create Issues using the Issue template
47
+ logger.info{ "\t\t => Creating new Issue: #{type}" }
48
+
49
+ issue_text = template_service.process_template(template: 'issue', data: xml_vuln)
50
+ issue = content_service.create_issue(text: issue_text, id: type)
51
+
52
+ # Create Evidence using the Evidence template
53
+ # Associate the Evidence with the Node and Issue
54
+ logger.info{ "\t\t => Creating new evidence" }
55
+ evidence_content = template_service.process_template(
56
+ template: 'evidence', data: xml_vuln
57
+ )
58
+ content_service.create_evidence(
59
+ issue: issue, node: host_node, content: evidence_content
60
+ )
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'gem_version'
2
+
3
+ module Dradis
4
+ module Plugins
5
+ module Netsparker
6
+ # Returns the version of the currently loaded Netsparker as a
7
+ # <tt>Gem::Version</tt>.
8
+ def self.version
9
+ gem_version
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,130 @@
1
+ module Netsparker
2
+ # This class represents each of the /netsparker/vulnerability elements in the
3
+ # Netsparker XML document.
4
+ #
5
+ # It provides a convenient way to access the information scattered all over
6
+ # the XML in attributes and nested tags.
7
+ #
8
+ # Instead of providing separate methods for each supported property we rely
9
+ # on Ruby's #method_missing to do most of the work.
10
+ class Vulnerability
11
+ attr_accessor :xml
12
+
13
+ # Accepts an XML node from Nokogiri::XML.
14
+ def initialize(xml_node)
15
+ @xml = xml_node
16
+ end
17
+
18
+ # List of supported tags. They can be attributes, simple descendans or
19
+ # collections.
20
+ def supported_tags
21
+ [
22
+ # made-up tags
23
+ :title,
24
+
25
+ # simple tags
26
+ :certainty, :description, :rawrequest, :rawresponse, :remedy, :severity,
27
+ :type, :url,
28
+
29
+ # tags that correspond to Evidence
30
+
31
+ # nested tags
32
+ :classification_capec, :classification_cwe, :classification_hipaa,
33
+ :classification_owasp2013, :classification_owasppc,
34
+ :classification_pci31, :classification_pci32, :classification_wasc,
35
+
36
+ # multiple tags
37
+ ]
38
+ end
39
+
40
+ # This allows external callers (and specs) to check for implemented
41
+ # properties
42
+ def respond_to?(method, include_private=false)
43
+ return true if supported_tags.include?(method.to_sym)
44
+ super
45
+ end
46
+
47
+ # This method is invoked by Ruby when a method that is not defined in this
48
+ # instance is called.
49
+ #
50
+ # In our case we inspect the @method@ parameter and try to find the
51
+ # attribute, simple descendent or collection that it maps to in the XML
52
+ # tree.
53
+ def method_missing(method, *args)
54
+
55
+ # We could remove this check and return nil for any non-recognized tag.
56
+ # The problem would be that it would make tricky to debug problems with
57
+ # typos. For instance: <>.potr would return nil instead of raising an
58
+ # exception
59
+ unless supported_tags.include?(method)
60
+ super
61
+ return
62
+ end
63
+
64
+ # Any fields where a simple .camelcase() won't work we need to translate,
65
+ # this includes acronyms (e.g. :cwe would become 'Cwe') and simple nested
66
+ # tags.
67
+ translations_table = {
68
+ classification_capec: 'classification/CAPEC',
69
+ classification_cwe: 'classification/CWE',
70
+ classification_hipaa: 'classification/HIPAA',
71
+ classification_owasp2013: 'classification/OWASP2013',
72
+ classification_owasppc: 'classification/OWASPPC',
73
+ classification_pci31: 'classification/PCI31',
74
+ classification_pci32: 'classification/PCI32',
75
+ classification_wasc: 'classification/WASC'
76
+ }
77
+ method_name = translations_table.fetch(method, method.to_s)
78
+
79
+ # We've got a virtual method :title which isn't provided by Netsparker
80
+ # but that most users will be expecting.
81
+ return type.underscore.humanize if method == :title
82
+
83
+ # first we try the attributes:
84
+ # return @xml.attributes[method_name].value if @xml.attributes.key?(method_name)
85
+
86
+ # then we try the children tags
87
+ tag = xml.at_xpath("./#{method_name}")
88
+ if tag && !tag.text.blank?
89
+ text = tag.text
90
+ return tags_with_html_content.include?(method) ? cleanup_html(text) : text
91
+ else
92
+ return 'n/a'
93
+ end
94
+
95
+ # nothing found
96
+ return nil
97
+ end
98
+
99
+ private
100
+
101
+ def cleanup_html(source)
102
+ result = source.dup
103
+ result.gsub!(/&quot;/, '"')
104
+ result.gsub!(/&amp;/, '&')
105
+ result.gsub!(/&lt;/, '<')
106
+ result.gsub!(/&gt;/, '>')
107
+
108
+ result.gsub!(/<b>(.*?)<\/b>/) { "*#{$1.strip}*" }
109
+ result.gsub!(/<br\/>/, "\n")
110
+ result.gsub!(/<code><pre.*?>(.*?)<\/pre><\/code>/m){|m| "\n\nbc.. #{$1.strip}\n\np. \n" }
111
+ result.gsub!(/<div>(.*?)<\/div>/, '\1 ')
112
+ result.gsub!(/<em>(.*?)<\/em>/, '\1')
113
+ result.gsub!(/<font.*?>(.*?)<\/font>/m, '\1')
114
+ result.gsub!(/<h2>(.*?)<\/h2>/) { "*#{$1.strip}*" }
115
+ result.gsub!(/<i>(.*?)<\/i>/, '\1')
116
+ result.gsub!(/<li.*>(.*)?<\/li>/, '* \1')
117
+ result.gsub!(/<p>(.*?)<\/p>/, '\1 ')
118
+ result.gsub!(/<pre.*?>(.*?)<\/pre>/m){|m| "\n\nbc.. #{$1.strip}\n\np. \n" }
119
+ result.gsub!(/<span.*>(.*?)<\/span>/, '\1')
120
+ result.gsub!(/<ul>(.*?)<\/ul>/m, '\1')
121
+
122
+ result
123
+ end
124
+
125
+ # Some of the values have embedded HTML conent that we need to strip
126
+ def tags_with_html_content
127
+ [:description, :remedy]
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,20 @@
1
+ class NetsparkerTasks < Thor
2
+ include Rails.application.config.dradis.thor_helper_module
3
+
4
+ namespace "dradis:plugins:netsparker"
5
+
6
+ desc "upload FILE", "upload Netsparker XML results"
7
+ def upload(file_path)
8
+ require 'config/environment'
9
+
10
+ unless File.exists?(file_path)
11
+ $stderr.puts "** the file [#{file_path}] does not exist"
12
+ exit(-1)
13
+ end
14
+
15
+ detect_and_set_project_scope
16
+ importer = Dradis::Plugins::Netsparker::Importer.new(task_options)
17
+ importer.import(file: file_path)
18
+ end
19
+
20
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ module Dradis::Plugins
5
+ describe 'Netsparker upload plugin' do
6
+ before(:each) do
7
+ # Stub template service
8
+ templates_dir = File.expand_path('../../templates', __FILE__)
9
+ expect_any_instance_of(TemplateService).to \
10
+ receive(:default_templates_dir).and_return(templates_dir)
11
+
12
+ plugin = Dradis::Plugins::Netsparker
13
+
14
+ @content_service = Dradis::Plugins::ContentService::Base.new(
15
+ logger: Logger.new(STDOUT),
16
+ plugin: plugin
17
+ )
18
+
19
+ @importer = Dradis::Plugins::Netsparker::Importer.new(
20
+ content_service: @content_service
21
+ )
22
+
23
+ # Stub dradis-plugins methods
24
+ #
25
+ # They return their argument hashes as objects mimicking
26
+ # Nodes, Issues, etc
27
+ allow(@content_service).to receive(:create_node) do |args|
28
+ OpenStruct.new(args)
29
+ end
30
+ allow(@content_service).to receive(:create_note) do |args|
31
+ OpenStruct.new(args)
32
+ end
33
+ allow(@content_service).to receive(:create_issue) do |args|
34
+ OpenStruct.new(args)
35
+ end
36
+ allow(@content_service).to receive(:create_evidence) do |args|
37
+ OpenStruct.new(args)
38
+ end
39
+ end
40
+
41
+ it "creates the expected Node, Issue, and Evidence from the example file" do
42
+ expect(@content_service).to receive(:create_node).with(hash_including label: 'localhost', type: :host)
43
+
44
+ expect(@content_service).to receive(:create_issue) do |args|
45
+ expect(args[:text]).to include("#[Title]#\nPassword over http")
46
+ expect(args[:id]).to eq("PasswordOverHttp")
47
+ OpenStruct.new(args)
48
+ end
49
+
50
+ expect(@content_service).to receive(:create_evidence) do |args|
51
+ expect(args[:content]).to include("#[Request]#\nbc.. GET /login HTTP/1.1")
52
+ expect(args[:issue].id).to eq("PasswordOverHttp")
53
+ expect(args[:node].label).to eq("localhost")
54
+ end
55
+
56
+ @importer.import(file: 'spec/fixtures/files/example.xml')
57
+ end
58
+
59
+ it "creates than one instance of Evidence for a single Issue" do
60
+ expect(@content_service).to receive(:create_node).with(hash_including label: 'snorby.org', type: :host)
61
+
62
+ expect(@content_service).to receive(:create_issue) do |args|
63
+ expect(args[:text]).to include("#[Title]#\nEmail disclosure")
64
+ expect(args[:id]).to eq("EmailDisclosure")
65
+ OpenStruct.new(args)
66
+ end
67
+
68
+ expect(@content_service).to receive(:create_evidence) do |args|
69
+ expect(args[:content]).to include("#[URL]#\nhttps://snorby.org/foo")
70
+ expect(args[:issue].id).to eq("EmailDisclosure")
71
+ expect(args[:node].label).to eq("snorby.org")
72
+ end.once
73
+
74
+ expect(@content_service).to receive(:create_evidence) do |args|
75
+ expect(args[:content]).to include("#[URL]#\nhttps://snorby.org/bar")
76
+ expect(args[:issue].id).to eq("EmailDisclosure")
77
+ expect(args[:node].label).to eq("snorby.org")
78
+ end.once
79
+
80
+ @importer.import(file: 'spec/fixtures/files/example-evidence.xml')
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,85 @@
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <?xml-stylesheet href="vulnerabilities-list.xsl" type="text/xsl" ?>
3
+
4
+ <netsparker generated="1.2.2017 12:34:45">
5
+ <target>
6
+ <url>https://snorby.org/</url>
7
+ <scantime>1470</scantime>
8
+ </target>
9
+ <vulnerability confirmed="False">
10
+ <url>https://snorby.org/foo</url>
11
+ <type>EmailDisclosure</type>
12
+ <severity>Information</severity>
13
+ <certainty>95</certainty>
14
+
15
+ <rawrequest><rawrequest><![CDATA[REDACTED}]]></rawrequest></rawrequest>
16
+ <rawresponse><rawrequest><![CDATA[REDACTED}]]></rawrequest></rawresponse>
17
+ <extrainformation>
18
+ </extrainformation>
19
+
20
+ <proofs></proofs>
21
+
22
+
23
+ <classification>
24
+ <OWASP2013></OWASP2013>
25
+ <WASC>13</WASC>
26
+ <CWE>200</CWE>
27
+ <CAPEC>118</CAPEC>
28
+ <PCI31></PCI31>
29
+ <PCI32></PCI32>
30
+ <HIPAA></HIPAA>
31
+ <OWASPPC>C7</OWASPPC>
32
+
33
+ <CVSS>
34
+ <vector>CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N</vector>
35
+
36
+ <score>
37
+ <type>Base</type>
38
+ <value>5.3</value>
39
+ <severity>Medium</severity>
40
+ </score>
41
+ <score>
42
+ <type>Temporal</type>
43
+ <value>5.3</value>
44
+ <severity>Medium</severity>
45
+ </score>
46
+ <score>
47
+ <type>Environmental</type>
48
+ <value>5.3</value>
49
+ <severity>Medium</severity>
50
+ </score>
51
+
52
+ </CVSS>
53
+ </classification>
54
+
55
+ </vulnerability>
56
+ <vulnerability confirmed="False">
57
+ <url>https://snorby.org/bar</url>
58
+ <type>EmailDisclosure</type>
59
+ <severity>Information</severity>
60
+ <certainty>95</certainty>
61
+
62
+ <rawrequest><rawrequest><![CDATA[REDACTED}]]></rawrequest></rawrequest>
63
+ <rawresponse><rawrequest><![CDATA[REDACTED}]]></rawrequest></rawresponse>
64
+ <extrainformation>
65
+ <info name="Email Address(es)"><![CDATA[info@snorby.org]]></info>
66
+ </extrainformation>
67
+
68
+ <proofs></proofs>
69
+
70
+
71
+ <classification>
72
+ <OWASP2013></OWASP2013>
73
+ <WASC>13</WASC>
74
+ <CWE>200</CWE>
75
+ <CAPEC>118</CAPEC>
76
+ <PCI31></PCI31>
77
+ <PCI32></PCI32>
78
+ <HIPAA></HIPAA>
79
+ <OWASPPC>C7</OWASPPC>
80
+
81
+ </classification>
82
+
83
+ </vulnerability>
84
+
85
+ </netsparker>