dradis-burp 3.18.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.
- checksums.yaml +7 -0
- data/.github/issue_template.md +16 -0
- data/.github/pull_request_template.md +36 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +57 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +23 -0
- data/LICENSE +339 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/dradis-burp.gemspec +34 -0
- data/lib/burp/html/issue.rb +157 -0
- data/lib/burp/issue.rb +43 -0
- data/lib/burp/xml/issue.rb +127 -0
- data/lib/dradis-burp.rb +10 -0
- data/lib/dradis/plugins/burp.rb +12 -0
- data/lib/dradis/plugins/burp/engine.rb +25 -0
- data/lib/dradis/plugins/burp/field_processor.rb +27 -0
- data/lib/dradis/plugins/burp/gem_version.rb +19 -0
- data/lib/dradis/plugins/burp/html/importer.rb +144 -0
- data/lib/dradis/plugins/burp/version.rb +13 -0
- data/lib/dradis/plugins/burp/xml/importer.rb +144 -0
- data/lib/tasks/thorfile.rb +30 -0
- data/spec/burp_upload_spec.rb +220 -0
- data/spec/fixtures/files/burp.html +229 -0
- data/spec/fixtures/files/burp.xml +100 -0
- data/spec/fixtures/files/burp_issue_severity.xml +118 -0
- data/spec/fixtures/files/invalid-utf-issue.xml +21 -0
- data/spec/fixtures/files/without-base64.xml +709 -0
- data/spec/spec_helper.rb +9 -0
- data/templates/evidence.fields +8 -0
- data/templates/evidence.sample +76 -0
- data/templates/evidence.template +20 -0
- data/templates/html_evidence.fields +13 -0
- data/templates/html_evidence.sample +36 -0
- data/templates/html_evidence.template +50 -0
- data/templates/issue.fields +8 -0
- data/templates/issue.sample +23 -0
- data/templates/issue.template +30 -0
- metadata +174 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Dradis
|
|
2
|
+
module Plugins
|
|
3
|
+
module Burp
|
|
4
|
+
class FieldProcessor < Dradis::Plugins::Upload::FieldProcessor
|
|
5
|
+
|
|
6
|
+
def post_initialize(args={})
|
|
7
|
+
@burp_object =
|
|
8
|
+
if data.is_a?(Nokogiri::XML::Element)
|
|
9
|
+
::Burp::Xml::Issue.new(data)
|
|
10
|
+
elsif data.is_a?(Nokogiri::XML::NodeSet)
|
|
11
|
+
::Burp::Html::Issue.new(data)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def value(args={})
|
|
16
|
+
field = args[:field]
|
|
17
|
+
# fields in the template are of the form <foo>.<field>, where <foo>
|
|
18
|
+
# is common across all fields for a given template (and meaningless).
|
|
19
|
+
_, name = field.split('.')
|
|
20
|
+
|
|
21
|
+
@burp_object.try(name) || 'n/a'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Dradis
|
|
2
|
+
module Plugins
|
|
3
|
+
module Burp
|
|
4
|
+
# Returns the version of the currently loaded Frontend 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 = 18
|
|
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,144 @@
|
|
|
1
|
+
module Dradis::Plugins::Burp
|
|
2
|
+
# This module knows how to parse Burp HTML format.
|
|
3
|
+
module Html
|
|
4
|
+
def self.meta
|
|
5
|
+
package = Dradis::Plugins::Burp
|
|
6
|
+
{
|
|
7
|
+
name: package::Engine::plugin_name,
|
|
8
|
+
description: 'Upload Burp Scanner output file (.html)',
|
|
9
|
+
version: package.version
|
|
10
|
+
}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Importer < Dradis::Plugins::Upload::Importer
|
|
14
|
+
def initialize(args={})
|
|
15
|
+
args[:plugin] = Dradis::Plugins::Burp
|
|
16
|
+
super(args)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def import(params = {})
|
|
20
|
+
logger.info { 'Parsing Burp Scanner HTML output file...' }
|
|
21
|
+
@doc = Nokogiri::HTML(File.read(params[:file]))
|
|
22
|
+
logger.info { 'Done.' }
|
|
23
|
+
|
|
24
|
+
# Issue headers are like: <span class="BODH0" id="X">
|
|
25
|
+
issue_headers = @doc.xpath("//span[contains(@class, 'BODH0')]")
|
|
26
|
+
|
|
27
|
+
if issue_headers.count.zero?
|
|
28
|
+
error = "Document doesn't seem to be in the Burp Scanner HTML format."
|
|
29
|
+
logger.fatal { error }
|
|
30
|
+
content_service.create_note text: error
|
|
31
|
+
return false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
issue_headers.each do |header|
|
|
35
|
+
issue_id = header.attr('id')
|
|
36
|
+
html = extract_html_fragment_for(issue_id)
|
|
37
|
+
process_html_issue(html)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
logger.info { 'Burp Scanner results successfully imported' }
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def process_html_issue(html_issue)
|
|
45
|
+
header = html_issue.first
|
|
46
|
+
title = header.text.gsub(/^\d+\.\S/, '')
|
|
47
|
+
burp_id =
|
|
48
|
+
if (link = header.css('a').first)
|
|
49
|
+
link.attr('href')[/\/([0-9a-f]+)_.*/, 1].to_i(16)
|
|
50
|
+
else
|
|
51
|
+
title
|
|
52
|
+
end
|
|
53
|
+
issue_id = html_issue.attr('id').value
|
|
54
|
+
issue_text =
|
|
55
|
+
template_service.process_template(
|
|
56
|
+
template: 'issue',
|
|
57
|
+
data: html_issue
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
logger.info { "Processing issue #{issue_id}: #{title}" }
|
|
61
|
+
issue = content_service.create_issue(text: issue_text, id: burp_id)
|
|
62
|
+
|
|
63
|
+
# Evidence headers are like:
|
|
64
|
+
# <span class="BODH1" id="X.Y">
|
|
65
|
+
# where:
|
|
66
|
+
# X is the issue index
|
|
67
|
+
# Y is the evidence index
|
|
68
|
+
evidence_headers = html_issue.xpath(
|
|
69
|
+
"//span[contains(@class, 'BODH1') and starts-with(@id, '#{issue_id}.')]"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# If there are no evidence headers inside this issue, this is a
|
|
73
|
+
# "single evidence" case: our evidence html is the issue html itself
|
|
74
|
+
if evidence_headers.count.zero?
|
|
75
|
+
process_html_evidence(html_issue, issue)
|
|
76
|
+
else
|
|
77
|
+
evidence_headers.each do |header|
|
|
78
|
+
evidence_id = header.attr('id')
|
|
79
|
+
html = extract_html_fragment_for(evidence_id)
|
|
80
|
+
process_html_evidence(html, issue)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def process_html_evidence(html_evidence, issue)
|
|
86
|
+
evidence_id = html_evidence.attr('id').value
|
|
87
|
+
logger.info { "Processing evidence #{evidence_id}" }
|
|
88
|
+
|
|
89
|
+
host_td = html_evidence.search("td:starts-with('Host:')").first
|
|
90
|
+
host_label = host_td.next_element.text.split('//').last
|
|
91
|
+
host = content_service.create_node(label: host_label, type: :host)
|
|
92
|
+
|
|
93
|
+
evidence_text =
|
|
94
|
+
template_service.process_template(
|
|
95
|
+
template: 'html_evidence',
|
|
96
|
+
data: html_evidence
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
content_service.create_evidence(
|
|
100
|
+
issue: issue,
|
|
101
|
+
node: host,
|
|
102
|
+
content: evidence_text
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Html for an issue and evidence is not nested inside an html element.
|
|
107
|
+
#
|
|
108
|
+
# An issue is the html fragment from <span id="X"> (where X is a single
|
|
109
|
+
# integer number: 1, 2, 3...) until the next span like that or the end of
|
|
110
|
+
# the file.
|
|
111
|
+
#
|
|
112
|
+
# An evidence is the html fragment from <span id="X.Y"> (where X is the
|
|
113
|
+
# issue index and Y the evidence index: 1.1, 1.2,...,2.1, 2.2,...) until
|
|
114
|
+
# the next evidence span (id="X.Z"), the next issue span (id="Y"), or the
|
|
115
|
+
# end of the file.
|
|
116
|
+
#
|
|
117
|
+
# This method extracts all the html related to as specific issue id or
|
|
118
|
+
# evidence id.
|
|
119
|
+
def extract_html_fragment_for(id)
|
|
120
|
+
next_id = if /\d+\.\d+/ =~ id
|
|
121
|
+
id_parts = id.split('.')
|
|
122
|
+
"#{id_parts[0]}.#{id_parts[1].to_i + 1}"
|
|
123
|
+
else
|
|
124
|
+
id.to_i + 1
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
start_element = @doc.xpath("//span[@id='#{id}']")
|
|
128
|
+
return nil if start_element.empty?
|
|
129
|
+
|
|
130
|
+
ending_element = @doc.xpath("//span[@id='#{next_id}']")
|
|
131
|
+
if ending_element.empty? && /\d+\.\d+/ =~ id
|
|
132
|
+
next_id = id.split('.')[0].to_i + 1
|
|
133
|
+
ending_element = @doc.xpath("//span[@id='#{next_id}']")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
xpath = "//*[preceding-sibling::span[@id='#{id}']"
|
|
137
|
+
xpath += " and following-sibling::span[@id='#{next_id}']" unless ending_element.empty?
|
|
138
|
+
xpath += ']'
|
|
139
|
+
|
|
140
|
+
start_element + @doc.xpath(xpath)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
module Dradis::Plugins::Burp
|
|
2
|
+
|
|
3
|
+
# This module knows how to parse Burp XML format.
|
|
4
|
+
module Xml
|
|
5
|
+
def self.meta
|
|
6
|
+
package = Dradis::Plugins::Burp
|
|
7
|
+
{
|
|
8
|
+
name: package::Engine::plugin_name,
|
|
9
|
+
description: 'Upload Burp Scanner output file (.xml)',
|
|
10
|
+
version: package.version
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Importer < Dradis::Plugins::Upload::Importer
|
|
15
|
+
BURP_EXTENSION_TYPE = '134217728'.freeze
|
|
16
|
+
BURP_SEVERITIES = ['Information', 'Low', 'Medium', 'High'].freeze
|
|
17
|
+
|
|
18
|
+
def initialize(args={})
|
|
19
|
+
args[:plugin] = Dradis::Plugins::Burp
|
|
20
|
+
super(args)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def import(params = {})
|
|
24
|
+
file_content = File.read(params[:file])
|
|
25
|
+
|
|
26
|
+
if file_content =~ /base64="false"/
|
|
27
|
+
error = "Burp input contains HTTP request / response data that hasn't been Base64-encoded.\n"
|
|
28
|
+
error << 'Please re-export your scanner results making sure the Base-64 encode option is selected.'
|
|
29
|
+
|
|
30
|
+
logger.fatal{ error }
|
|
31
|
+
content_service.create_note text: error
|
|
32
|
+
return false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
logger.info { 'Parsing Burp Scanner XML output file...' }
|
|
36
|
+
doc = Nokogiri::XML(file_content)
|
|
37
|
+
logger.info { 'Done.' }
|
|
38
|
+
|
|
39
|
+
if doc.root.name != 'issues'
|
|
40
|
+
error = 'Document doesn\'t seem to be in the Burp Scanner XML format.'
|
|
41
|
+
logger.fatal { error }
|
|
42
|
+
content_service.create_note text: error
|
|
43
|
+
return false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# This will be filled in by the Processor while iterating over the issues
|
|
47
|
+
@issues = []
|
|
48
|
+
@severities = Hash.new(0)
|
|
49
|
+
|
|
50
|
+
# We need to look ahead through all issues to bring the highest severity
|
|
51
|
+
# of each instance to the Issue level.
|
|
52
|
+
doc.xpath('issues/issue').each do |xml_issue|
|
|
53
|
+
issue_id = issue_id_for(xml_issue)
|
|
54
|
+
issue_severity = BURP_SEVERITIES.index(xml_issue.at('severity').text)
|
|
55
|
+
|
|
56
|
+
@severities[issue_id] = issue_severity if issue_severity > @severities[issue_id]
|
|
57
|
+
@issues << xml_issue
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@issues.each { |xml_issue| process_issue(xml_issue) }
|
|
61
|
+
|
|
62
|
+
logger.info { 'Burp Scanner results successfully imported' }
|
|
63
|
+
true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
def create_issue(affected_host:, id:, xml_issue:)
|
|
68
|
+
xml_evidence = xml_issue.clone
|
|
69
|
+
|
|
70
|
+
# Ensure that the Issue contains the highest Severity value
|
|
71
|
+
xml_issue.at('severity').content = BURP_SEVERITIES[@severities[id]]
|
|
72
|
+
|
|
73
|
+
issue_text =
|
|
74
|
+
template_service.process_template(
|
|
75
|
+
template: 'issue',
|
|
76
|
+
data: xml_issue
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if issue_text.include?(::Burp::INVALID_UTF_REPLACE)
|
|
80
|
+
logger.info do
|
|
81
|
+
"\tdetected invalid UTF-8 bytes in your issue. " \
|
|
82
|
+
"Replacing them with '#{::Burp::INVALID_UTF_REPLACE}'."
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
issue = content_service.create_issue(text: issue_text, id: id)
|
|
87
|
+
|
|
88
|
+
logger.info do
|
|
89
|
+
"\tadding evidence for this instance to #{affected_host.label}."
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
evidence_text =
|
|
93
|
+
template_service.process_template(
|
|
94
|
+
template: 'evidence',
|
|
95
|
+
data: xml_evidence
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if evidence_text.include?(::Burp::INVALID_UTF_REPLACE)
|
|
99
|
+
logger.info do
|
|
100
|
+
"\tdetected invalid UTF-8 bytes in your evidence. " \
|
|
101
|
+
"Replacing them with '#{::Burp::INVALID_UTF_REPLACE}'."
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
content_service.create_evidence(
|
|
106
|
+
issue: issue,
|
|
107
|
+
node: affected_host,
|
|
108
|
+
content: evidence_text
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Burp extensions don't follow the "unique type for every Issue" logic
|
|
113
|
+
# so we have to deal with them separately
|
|
114
|
+
def issue_id_for(xml_issue)
|
|
115
|
+
if xml_issue.at('type').text == BURP_EXTENSION_TYPE
|
|
116
|
+
xml_issue.at('name').text.gsub!(' ', '')
|
|
117
|
+
else
|
|
118
|
+
xml_issue.at('type').text.to_i
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Creates the Nodes/properties
|
|
123
|
+
def process_issue(xml_issue)
|
|
124
|
+
host_url = xml_issue.at('host').text
|
|
125
|
+
host_label = xml_issue.at('host')['ip']
|
|
126
|
+
host_label = host_url if host_label.empty?
|
|
127
|
+
issue_id = issue_id_for(xml_issue)
|
|
128
|
+
|
|
129
|
+
affected_host = content_service.create_node(label: host_label, type: :host)
|
|
130
|
+
affected_host.set_property(:hostname, host_url)
|
|
131
|
+
affected_host.save
|
|
132
|
+
|
|
133
|
+
logger.info { "Adding #{xml_issue.at('name').text} (#{issue_id})"}
|
|
134
|
+
logger.info { "\taffects: #{host_label}" }
|
|
135
|
+
|
|
136
|
+
create_issue(
|
|
137
|
+
affected_host: affected_host,
|
|
138
|
+
id: issue_id,
|
|
139
|
+
xml_issue: xml_issue
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class BurpTasks < Thor
|
|
2
|
+
include Rails.application.config.dradis.thor_helper_module
|
|
3
|
+
|
|
4
|
+
namespace "dradis:plugins:burp"
|
|
5
|
+
|
|
6
|
+
desc "upload FILE", "upload Burp XML or HTML 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
|
+
|
|
17
|
+
importer =
|
|
18
|
+
if File.extname(file_path) == '.xml'
|
|
19
|
+
Dradis::Plugins::Burp::Xml::Importer.new(task_options)
|
|
20
|
+
elsif File.extname(file_path) == '.html'
|
|
21
|
+
Dradis::Plugins::Burp::Html::Importer.new(task_options)
|
|
22
|
+
else
|
|
23
|
+
$stderr.puts "** Unsupported file. Must be .xml or .html"
|
|
24
|
+
exit(-2)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
importer.import(file: file_path)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'ostruct'
|
|
3
|
+
|
|
4
|
+
describe 'Burp upload plugin' do
|
|
5
|
+
|
|
6
|
+
describe Burp::Xml::Issue do
|
|
7
|
+
it 'handles invalid utf-8 bytes' do
|
|
8
|
+
doc = Nokogiri::XML(File.read('spec/fixtures/files/invalid-utf-issue.xml'))
|
|
9
|
+
xml_issue = doc.xpath('issues/issue').first
|
|
10
|
+
issue = Burp::Xml::Issue.new(xml_issue)
|
|
11
|
+
|
|
12
|
+
expect{ issue.request.encode('utf-8') }.to_not raise_error
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe Dradis::Plugins::Burp::Xml::Importer do
|
|
17
|
+
before(:each) do
|
|
18
|
+
# Stub template service
|
|
19
|
+
templates_dir = File.expand_path('../../templates', __FILE__)
|
|
20
|
+
expect_any_instance_of(Dradis::Plugins::TemplateService)
|
|
21
|
+
.to receive(:default_templates_dir).and_return(templates_dir)
|
|
22
|
+
|
|
23
|
+
# Init services
|
|
24
|
+
plugin = Dradis::Plugins::Burp::Xml
|
|
25
|
+
|
|
26
|
+
@content_service = Dradis::Plugins::ContentService::Base.new(
|
|
27
|
+
logger: Logger.new(STDOUT),
|
|
28
|
+
plugin: plugin
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
@importer = plugin::Importer.new(
|
|
32
|
+
content_service: @content_service,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Stub dradis-plugins methods
|
|
36
|
+
#
|
|
37
|
+
# They return their argument hashes as objects mimicking
|
|
38
|
+
# Nodes, Issues, etc
|
|
39
|
+
allow(@content_service).to receive(:create_node) do |args|
|
|
40
|
+
obj = OpenStruct.new(args)
|
|
41
|
+
obj.define_singleton_method(:set_property) { |_, __| }
|
|
42
|
+
obj
|
|
43
|
+
end
|
|
44
|
+
allow(@content_service).to receive(:create_issue) do |args|
|
|
45
|
+
OpenStruct.new(args)
|
|
46
|
+
end
|
|
47
|
+
allow(@content_service).to receive(:create_evidence) do |args|
|
|
48
|
+
OpenStruct.new(args)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'creates nodes, issues, and evidence as needed' do
|
|
53
|
+
|
|
54
|
+
# Host node
|
|
55
|
+
#
|
|
56
|
+
# create_node should be called once for each issue in the xml,
|
|
57
|
+
# but ContentService knows it's already created and NOOPs
|
|
58
|
+
expect(@content_service).to receive(:create_node)
|
|
59
|
+
.with(hash_including label: '10.0.0.1')
|
|
60
|
+
.exactly(4).times
|
|
61
|
+
|
|
62
|
+
# create_issue should be called once for each issue in the xml
|
|
63
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
|
64
|
+
expect(args[:text]).to include("#[Title]#\nIssue 1")
|
|
65
|
+
expect(args[:id]).to eq(8781630)
|
|
66
|
+
OpenStruct.new(args)
|
|
67
|
+
end.once
|
|
68
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
|
69
|
+
expect(args[:content]).to include("Lorem ipsum dolor sit amet")
|
|
70
|
+
expect(args[:issue].text).to include("#[Title]#\nIssue 1")
|
|
71
|
+
expect(args[:node].label).to eq("10.0.0.1")
|
|
72
|
+
end.once
|
|
73
|
+
|
|
74
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
|
75
|
+
expect(args[:text]).to include("#[Title]#\nIssue 2")
|
|
76
|
+
expect(args[:id]).to eq(8781631)
|
|
77
|
+
OpenStruct.new(args)
|
|
78
|
+
end.once
|
|
79
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
|
80
|
+
expect(args[:content]).to include("Lorem ipsum dolor sit amet")
|
|
81
|
+
expect(args[:issue].text).to include("#[Title]#\nIssue 2")
|
|
82
|
+
expect(args[:node].label).to eq("10.0.0.1")
|
|
83
|
+
end.once
|
|
84
|
+
|
|
85
|
+
# Issue 3 is an Extension finding so we need to confirm
|
|
86
|
+
# that it triggers process_extension_issues instead of process_burp_issues
|
|
87
|
+
# and the plugin_id is not set to the Type (134217728)
|
|
88
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
|
89
|
+
expect(args[:text]).to include("#[Title]#\nIssue 3")
|
|
90
|
+
expect(args[:id]).to eq("Issue3")
|
|
91
|
+
OpenStruct.new(args)
|
|
92
|
+
end.once
|
|
93
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
|
94
|
+
expect(args[:content]).to include("Lorem ipsum dolor sit amet")
|
|
95
|
+
expect(args[:issue].text).to include("#[Title]#\nIssue 3")
|
|
96
|
+
expect(args[:node].label).to eq("10.0.0.1")
|
|
97
|
+
end.once
|
|
98
|
+
|
|
99
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
|
100
|
+
expect(args[:text]).to include("#[Title]#\nIssue 4")
|
|
101
|
+
expect(args[:id]).to eq(8781633)
|
|
102
|
+
OpenStruct.new(args)
|
|
103
|
+
end.once
|
|
104
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
|
105
|
+
expect(args[:content]).to include("Lorem ipsum dolor sit amet")
|
|
106
|
+
expect(args[:issue].text).to include("#[Title]#\nIssue 4")
|
|
107
|
+
expect(args[:node].label).to eq("10.0.0.1")
|
|
108
|
+
end.once
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Run the import
|
|
112
|
+
@importer.import(file: 'spec/fixtures/files/burp.xml')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'returns the highest <severity> at the Issue level' do
|
|
116
|
+
|
|
117
|
+
# create_issue should be called once for each issue in the xml
|
|
118
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
|
119
|
+
expect(args[:id]).to eq(8781630)
|
|
120
|
+
expect(args[:text]).to include("#[Title]#\nIssue 1")
|
|
121
|
+
expect(args[:text]).to include("#[Severity]#\nCritical")
|
|
122
|
+
OpenStruct.new(args)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
|
126
|
+
expect(args[:content]).to include("#[Severity]#\nInformation")
|
|
127
|
+
expect(args[:issue].text).to include("#[Title]#\nIssue 1")
|
|
128
|
+
expect(args[:node].label).to eq('10.0.0.1')
|
|
129
|
+
end.once
|
|
130
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
|
131
|
+
expect(args[:content]).to include("#[Severity]#\nHigh")
|
|
132
|
+
expect(args[:issue].text).to include("#[Title]#\nIssue 1")
|
|
133
|
+
expect(args[:node].label).to eq('10.0.0.1')
|
|
134
|
+
OpenStruct.new(args)
|
|
135
|
+
end.once
|
|
136
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
|
137
|
+
expect(args[:content]).to include("#[Severity]#\nMedium")
|
|
138
|
+
expect(args[:issue].text).to include("#[Title]#\nIssue 1")
|
|
139
|
+
expect(args[:node].label).to eq('10.0.0.1')
|
|
140
|
+
end.once
|
|
141
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
|
142
|
+
expect(args[:content]).to include("#[Severity]#\nCritical")
|
|
143
|
+
expect(args[:issue].text).to include("#[Title]#\nIssue 1")
|
|
144
|
+
expect(args[:node].label).to eq('10.0.0.1')
|
|
145
|
+
end.once
|
|
146
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
|
147
|
+
expect(args[:content]).to include("#[Severity]#\nLow")
|
|
148
|
+
expect(args[:issue].text).to include("#[Title]#\nIssue 1")
|
|
149
|
+
expect(args[:node].label).to eq('10.0.0.1')
|
|
150
|
+
end.once
|
|
151
|
+
|
|
152
|
+
# Run the import
|
|
153
|
+
@importer.import(file: 'spec/fixtures/files/burp_issue_severity.xml')
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
describe Dradis::Plugins::Burp::Html::Importer do
|
|
158
|
+
before(:each) do
|
|
159
|
+
# Stub template service
|
|
160
|
+
templates_dir = File.expand_path('../../templates', __FILE__)
|
|
161
|
+
expect_any_instance_of(Dradis::Plugins::TemplateService)
|
|
162
|
+
.to receive(:default_templates_dir).and_return(templates_dir)
|
|
163
|
+
|
|
164
|
+
# Init services
|
|
165
|
+
plugin = Dradis::Plugins::Burp::Html
|
|
166
|
+
|
|
167
|
+
@content_service = Dradis::Plugins::ContentService::Base.new(
|
|
168
|
+
logger: Logger.new(STDOUT),
|
|
169
|
+
plugin: plugin
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
@importer = plugin::Importer.new(
|
|
173
|
+
content_service: @content_service,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Stub dradis-plugins methods
|
|
177
|
+
#
|
|
178
|
+
# They return their argument hashes as objects mimicking
|
|
179
|
+
# Nodes, Issues, etc
|
|
180
|
+
allow(@content_service).to receive(:create_node) do |args|
|
|
181
|
+
obj = OpenStruct.new(args)
|
|
182
|
+
obj.define_singleton_method(:set_property) { |_, __| }
|
|
183
|
+
obj
|
|
184
|
+
end
|
|
185
|
+
allow(@content_service).to receive(:create_issue) do |args|
|
|
186
|
+
OpenStruct.new(args)
|
|
187
|
+
end
|
|
188
|
+
allow(@content_service).to receive(:create_evidence) do |args|
|
|
189
|
+
OpenStruct.new(args)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it "creates nodes, issues, and evidence as needed" do
|
|
194
|
+
|
|
195
|
+
# Host node
|
|
196
|
+
#
|
|
197
|
+
# create_node should be called once for each issue in the xml,
|
|
198
|
+
# but ContentService knows it's already created and NOOPs
|
|
199
|
+
expect(@content_service).to receive(:create_node)
|
|
200
|
+
.with(hash_including label: 'github.com/dradis/dradis-burp')
|
|
201
|
+
.exactly(1).times
|
|
202
|
+
|
|
203
|
+
# # create_issue should be called once for each issue in the xml
|
|
204
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
|
205
|
+
expect(args[:text]).to include("#[Title]#\nStrict transport security not enforced")
|
|
206
|
+
expect(args[:id]).to eq(16777984)
|
|
207
|
+
OpenStruct.new(args)
|
|
208
|
+
end.once
|
|
209
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
|
210
|
+
expect(args[:content]).to include("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
|
211
|
+
expect(args[:issue].text).to include("#[Title]#\nStrict transport security not enforced")
|
|
212
|
+
expect(args[:node].label).to eq("github.com/dradis/dradis-burp")
|
|
213
|
+
end.once
|
|
214
|
+
|
|
215
|
+
# Run the import
|
|
216
|
+
@importer.import(file: 'spec/fixtures/files/burp.html')
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
end
|
|
220
|
+
end
|