dradis-burp 3.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.github/issue_template.md +16 -0
  3. data/.github/pull_request_template.md +36 -0
  4. data/.gitignore +10 -0
  5. data/.rspec +2 -0
  6. data/CHANGELOG.md +57 -0
  7. data/CONTRIBUTING.md +3 -0
  8. data/Gemfile +23 -0
  9. data/LICENSE +339 -0
  10. data/README.md +29 -0
  11. data/Rakefile +1 -0
  12. data/dradis-burp.gemspec +34 -0
  13. data/lib/burp/html/issue.rb +157 -0
  14. data/lib/burp/issue.rb +43 -0
  15. data/lib/burp/xml/issue.rb +127 -0
  16. data/lib/dradis-burp.rb +10 -0
  17. data/lib/dradis/plugins/burp.rb +12 -0
  18. data/lib/dradis/plugins/burp/engine.rb +25 -0
  19. data/lib/dradis/plugins/burp/field_processor.rb +27 -0
  20. data/lib/dradis/plugins/burp/gem_version.rb +19 -0
  21. data/lib/dradis/plugins/burp/html/importer.rb +144 -0
  22. data/lib/dradis/plugins/burp/version.rb +13 -0
  23. data/lib/dradis/plugins/burp/xml/importer.rb +144 -0
  24. data/lib/tasks/thorfile.rb +30 -0
  25. data/spec/burp_upload_spec.rb +220 -0
  26. data/spec/fixtures/files/burp.html +229 -0
  27. data/spec/fixtures/files/burp.xml +100 -0
  28. data/spec/fixtures/files/burp_issue_severity.xml +118 -0
  29. data/spec/fixtures/files/invalid-utf-issue.xml +21 -0
  30. data/spec/fixtures/files/without-base64.xml +709 -0
  31. data/spec/spec_helper.rb +9 -0
  32. data/templates/evidence.fields +8 -0
  33. data/templates/evidence.sample +76 -0
  34. data/templates/evidence.template +20 -0
  35. data/templates/html_evidence.fields +13 -0
  36. data/templates/html_evidence.sample +36 -0
  37. data/templates/html_evidence.template +50 -0
  38. data/templates/issue.fields +8 -0
  39. data/templates/issue.sample +23 -0
  40. data/templates/issue.template +30 -0
  41. 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,13 @@
1
+ require_relative 'gem_version'
2
+
3
+ module Dradis
4
+ module Plugins
5
+ module Burp
6
+ # Returns the version of the currently loaded Action Mailer 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,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