dradis-netsparker 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>