dradis-nexpose 4.12.0 → 4.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5b342a73c3a4576df7bd68d24142393480c9abed09cd8f7a8c2f9aefe67339e
4
- data.tar.gz: 4d24a2ed95a57a001e9f909740fd95067332065804d282598c2b86d31db6b644
3
+ metadata.gz: fed2206ffe5931c65cc3b1ae1c9007091b99b3f9134a9b81b4ae8b4d5496fc5f
4
+ data.tar.gz: bf34162969ecdd0d105d551149b51f7c20795a2bfa9b1f97c741b36e5711cfe5
5
5
  SHA512:
6
- metadata.gz: '0080d88c9b79f02f1a9610d85aa350f26e3c059fae144c15a1ccb66c6acf69e69e076063c2d7451915e9a625892f1fc9a519f099aed3e9577edfa4278cb24b5d'
7
- data.tar.gz: 9231e9e9a60fe0b06316f024b0f089c76bda3c4bcb4000f0eb4079da893ee295daf59cfabe5f47b85b6f989f2e72b1f87ed988c499ccc64a880a29c9a52e8bbd
6
+ metadata.gz: 9fa49ed385686a5910de33e05f09caafbb624e5227af713c5defd4d0485b38b0d446194bd652b03b77d43861440e1d08a2eb597ea7b13b2f8ccae9a8f1440510
7
+ data.tar.gz: d84b92cc9d54926f788d78dda9b87e7f1a4262ad7682278f72af62dc4917ca4877ea9e0a8754a3d4a117bee44633dbd4ce2e539e197069b523a5c7615b2d1acf
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ v4.14.0 (October 2024)
2
+ - Separate general importer into Full & Simple importers
3
+
4
+ v4.13.0 (July 2024)
5
+ - No changes
6
+
1
7
  v4.12.0 (May 2024)
2
8
  - Migrate integration to use Mappings Manager
3
9
  - Update Dradis links in README
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.add_dependency 'dradis-plugins', '~> 4.0'
27
27
  spec.add_dependency 'nokogiri', '~> 1.3'
28
28
 
29
- spec.add_development_dependency 'bundler'
29
+ spec.add_development_dependency 'bundler', '~> 2.0'
30
30
  spec.add_development_dependency 'rake', '~> 10.0'
31
31
  spec.add_development_dependency 'rspec-rails'
32
32
  spec.add_development_dependency 'combustion', '~> 0.5.2'
@@ -5,5 +5,17 @@ module Dradis::Plugins::Nexpose
5
5
  include ::Dradis::Plugins::Base
6
6
  description 'Processes Nexpose XML format'
7
7
  provides :upload
8
+
9
+ # Because this plugin provides two export modules, we have to overwrite
10
+ # the default .uploaders() method.
11
+ #
12
+ # See:
13
+ # Dradis::Plugins::Upload::Base in dradis-plugins
14
+ def self.uploaders
15
+ [
16
+ Dradis::Plugins::Nexpose::Full,
17
+ Dradis::Plugins::Nexpose::Simple
18
+ ]
19
+ end
8
20
  end
9
21
  end
@@ -0,0 +1,204 @@
1
+ module Dradis::Plugins::Nexpose
2
+ module Full
3
+ def self.meta
4
+ package = Dradis::Plugins::Nexpose
5
+ {
6
+ name: package::Engine::plugin_name,
7
+ description: 'Upload Full NeXpose output file (.xml)',
8
+ version: package.version
9
+ }
10
+ end
11
+
12
+ class Importer < Dradis::Plugins::Upload::Importer
13
+
14
+ def self.templates
15
+ { evidence: 'full_evidence', issue: 'full_vulnerability' }
16
+ end
17
+
18
+ def initialize(args = {})
19
+ args[:plugin] = Dradis::Plugins::Nexpose
20
+ super(args)
21
+ end
22
+
23
+ # The framework will call this function if the user selects this plugin from
24
+ # the dropdown list and uploads a file.
25
+ # @returns true if the operation was successful, false otherwise
26
+ def import(params = {})
27
+ file_content = File.read(params[:file])
28
+
29
+ logger.info { 'Parsing NeXpose-Full XML output file...' }
30
+ doc = Nokogiri::XML(file_content)
31
+ logger.info { 'Done.' }
32
+
33
+ unless doc.root.name == 'NexposeReport'
34
+ error = "The document doesn't seem to be in NeXpose-Full XML format. Ensure you uploaded a NeXpose-Full XML report."
35
+ logger.fatal{ error }
36
+ content_service.create_note text: error
37
+ return false
38
+ end
39
+
40
+ process_full(doc)
41
+
42
+ logger.info { 'NeXpose-Full format successfully imported' }
43
+ true
44
+ end
45
+
46
+ private
47
+
48
+ def process_full(doc)
49
+ note_text = nil
50
+
51
+ @vuln_list = []
52
+ evidence = Hash.new { |h, k| h[k] = {} }
53
+
54
+ # First, extract scans
55
+ scan_node = content_service.create_node(label: 'Nexpose Scan Summary')
56
+ logger.info { "\tProcessing scan summary" }
57
+
58
+ doc.xpath('//scans/scan').each do |xml_scan|
59
+ note_text = mapping_service.apply_mapping(source: 'full_scan', data: xml_scan)
60
+ content_service.create_note(node: scan_node, text: note_text)
61
+ end
62
+
63
+ # Second, we parse the nodes
64
+ doc.xpath('//nodes/node').each do |xml_node|
65
+ nexpose_node = Nexpose::Node.new(xml_node)
66
+
67
+ host_node = content_service.create_node(label: nexpose_node.address, type: :host)
68
+ logger.info { "\tProcessing host: #{nexpose_node.address}" }
69
+
70
+ # add the summary note for this host
71
+ note_text = mapping_service.apply_mapping(source: 'full_node', data: nexpose_node)
72
+ content_service.create_note(node: host_node, text: note_text)
73
+
74
+ if host_node.respond_to?(:properties)
75
+ logger.info { "\tAdding host properties to #{nexpose_node.address}" }
76
+ host_node.set_property(:ip, nexpose_node.address)
77
+ host_node.set_property(:hostname, nexpose_node.names)
78
+ host_node.set_property(:os, nexpose_node.fingerprints)
79
+ host_node.set_property(:risk_score, nexpose_node.risk_score)
80
+ host_node.save
81
+ end
82
+
83
+ # inject this node's address into any vulnerabilities identified
84
+ #
85
+ # TODO: There is room for improvement here, we could have a hash that
86
+ # linked vulns with test/service and host to create proper content for
87
+ # Evidence.
88
+ nexpose_node.tests.each do |node_test|
89
+ test_id = node_test[:id].to_s.downcase
90
+
91
+ # We can't use the straightforward version below because Nexpose uses
92
+ # mixed-case some times (!)
93
+ # xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[@id='#{node_test[:id]}']").first
94
+ # See:
95
+ # http://stackoverflow.com/questions/1625446/problem-with-upper-case-and-lower-case-xpath-functions-in-selenium-ide/1625859#1625859
96
+ xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='#{test_id}']").first
97
+ xml_vuln.add_child('<hosts/>') unless xml_vuln.last_element_child.name == 'hosts'
98
+
99
+ if xml_vuln.xpath("./hosts/host[text()='#{nexpose_node.address}']").empty?
100
+ xml_vuln.last_element_child.add_child("<host>#{nexpose_node.address}</host>")
101
+ end
102
+
103
+ evidence[test_id][nexpose_node.address] ||= []
104
+ evidence[test_id][nexpose_node.address] << node_test
105
+ end
106
+
107
+ nexpose_node.endpoints.each do |endpoint|
108
+ # endpoint_node = content_service.create_node(label: endpoint.label, parent: host_node)
109
+ logger.info { "\t\tEndpoint: #{endpoint.label}" }
110
+
111
+ if host_node.respond_to?(:properties)
112
+ logger.info { "\t\tAdding to Services table" }
113
+ host_node.set_service(
114
+ port: endpoint.port.to_i,
115
+ protocol: endpoint.protocol,
116
+ state: endpoint.status,
117
+ name: endpoint.services.map(&:name).join(', '),
118
+ source: :nexpose,
119
+ # reason: port.reason,
120
+ # product: port.try('service').try('product'),
121
+ # version: port.try('service').try('version')
122
+ )
123
+ end
124
+
125
+ endpoint.services.each do |service|
126
+
127
+ # add the summary note for this service
128
+ note_text = mapping_service.apply_mapping(source: 'full_service', data: service)
129
+ # content_service.create_note(node: endpoint_node, text: note_text)
130
+ content_service.create_note(node: host_node, text: note_text)
131
+
132
+ # inject this node's address into any vulnerabilities identified
133
+ service.tests.each do |service_test|
134
+ test_id = service_test[:id].to_s.downcase
135
+
136
+ # For some reason Nexpose fails to include the 'http-iis-0011' vulnerability definition
137
+ next if test_id == 'http-iis-0011'
138
+
139
+ # We can't use the straightforward version below because Nexpose uses
140
+ # mixed-case some times (!)
141
+ # xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[@id='#{service_test[:id]}']").first
142
+ # See:
143
+ # http://stackoverflow.com/questions/1625446/problem-with-upper-case-and-lower-case-xpath-functions-in-selenium-ide/1625859#1625859
144
+ #
145
+ xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='#{test_id}']").first
146
+ xml_vuln.add_child('<hosts/>') unless xml_vuln.last_element_child.name == 'hosts'
147
+
148
+ if xml_vuln.xpath("./hosts/host[text()='#{nexpose_node.address}']").empty?
149
+ xml_vuln.last_element_child.add_child("<host>#{nexpose_node.address}</host>")
150
+ end
151
+
152
+ evidence[test_id][nexpose_node.address] ||= []
153
+ evidence[test_id][nexpose_node.address] << service_test
154
+ end
155
+ end
156
+ end
157
+
158
+ # add note under this node for each vulnerable ./node/test/
159
+ host_node.save
160
+ end
161
+
162
+ # Third, parse vulnerability definitions
163
+ logger.info { "\tProcessing issue definitions:" }
164
+
165
+ doc.xpath('//VulnerabilityDefinitions/vulnerability').each do |xml_vulnerability|
166
+ id = xml_vulnerability['id'].downcase
167
+ # if @vuln_list.include?(id)
168
+ issue_text = mapping_service.apply_mapping(
169
+ source: 'full_vulnerability',
170
+ data: xml_vulnerability
171
+ )
172
+
173
+ # retrieve hosts affected by this issue (injected in step 2)
174
+ #
175
+ # There is no need for the below as Issues are linked to hosts via the
176
+ # corresponding Evidence instance
177
+ #
178
+ # note_text << "\n\n#[host]#\n"
179
+ # note_text << xml_vulnerability.xpath('./hosts/host').collect(&:text).join("\n")
180
+ # note_text << "\n\n"
181
+
182
+ # 3.1 create the Issue
183
+ issue = content_service.create_issue(text: issue_text, id: id)
184
+ logger.info { "\tIssue: #{issue.fields ? issue.fields['Title'] : id}" }
185
+
186
+ # 3.2 associate with the nodes via Evidence.
187
+ # TODO: there is room for improvement here by providing proper Evidence content
188
+ xml_vulnerability.xpath('./hosts/host').map(&:text).each do |host_name|
189
+ # if the node exists, this just returns it
190
+ host_node = content_service.create_node(label: host_name, type: :host)
191
+
192
+ evidence[id][host_name].each do |evidence|
193
+ evidence_content = mapping_service.apply_mapping(
194
+ source: 'full_evidence',
195
+ data: evidence
196
+ )
197
+ content_service.create_evidence(content: evidence_content, issue: issue, node: host_node)
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
@@ -8,7 +8,7 @@ module Dradis
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 4
11
- MINOR = 12
11
+ MINOR = 14
12
12
  TINY = 0
13
13
  PRE = nil
14
14
 
@@ -11,7 +11,8 @@ module Dradis::Plugins::Nexpose
11
11
  'Hostname' => '{{ nexpose[node.site_name] }}',
12
12
  'Details' => "Status: {{ nexpose[node.status] }}\nDevice id: {{ nexpose[node.device_id] }}\nHW address: {{ nexpose[node.hardware_address] }}",
13
13
  'Names' => '{{ nexpose[node.names] }}',
14
- 'Software' => '{{ nexpose[node.software] }}'
14
+ 'Software' => '{{ nexpose[node.software] }}',
15
+ 'Fingerprints' => '{{ nexpose[node.fingerprints] }}'
15
16
  },
16
17
  full_scan: {
17
18
  'Title' => '{{ nexpose[scan.name] }} ({{ nexpose[scan.scan_id] }})',
@@ -0,0 +1,117 @@
1
+ module Dradis::Plugins::Nexpose
2
+ module Simple
3
+ def self.meta
4
+ package = Dradis::Plugins::Nexpose
5
+ {
6
+ name: package::Engine::plugin_name,
7
+ description: 'Upload Simple NeXpose output file (.xml)',
8
+ version: package.version
9
+ }
10
+ end
11
+
12
+ class Importer < Dradis::Plugins::Upload::Importer
13
+ def self.templates
14
+ {}
15
+ end
16
+
17
+ def initialize(args = {})
18
+ args[:plugin] = Dradis::Plugins::Nexpose
19
+ super(args)
20
+ end
21
+
22
+ # The framework will call this function if the user selects this plugin from
23
+ # the dropdown list and uploads a file.
24
+ # @returns true if the operation was successful, false otherwise
25
+ def import(params = {})
26
+ file_content = File.read( params[:file] )
27
+
28
+ logger.info { 'Parsing NeXpose output file...' }
29
+ doc = Nokogiri::XML(file_content)
30
+ logger.info { 'Done.' }
31
+
32
+ unless doc.root.name == 'NeXposeSimpleXML'
33
+ error = "The document doesn't seem to be in either NeXpose-Simple or NeXpose-Full XML format. Ensure you uploaded a Nexpose XML report."
34
+ logger.fatal{ error }
35
+ content_service.create_note text: error
36
+ return false
37
+ end
38
+
39
+ process_simple(doc)
40
+
41
+ logger.info { 'NeXpose-Simple format uploaded successfully' }
42
+ true
43
+ end
44
+
45
+ private
46
+
47
+ def process_simple(doc)
48
+ hosts = process_nexpose_simple_xml(doc)
49
+ notes_simple(hosts)
50
+ end
51
+
52
+ def notes_simple(hosts)
53
+ return if hosts.nil?
54
+
55
+ hosts.each do |host|
56
+ host_node = content_service.create_node(label: host['address'], type: :host)
57
+ content_service.create_note node: host_node, text: "Host Description : #{host['description']} \nScanner Fingerprint certainty : #{host['fingerprint']}"
58
+
59
+ generic_findings_node = content_service.create_node(label: 'Generic Findings', parent: host_node)
60
+ host['generic_vulns'].each do |id, finding|
61
+ content_service.create_note node: generic_findings_node, text: "Finding ID : #{id} \n \n Finding Refs :\n-------\n #{finding}"
62
+ end
63
+
64
+ port_text = nil
65
+ host['ports'].each do |port_label, findings|
66
+ port_node = content_service.create_node(label: port_label, parent: host_node)
67
+
68
+ findings.each do |id, finding|
69
+ port_text = mapping_service.apply_mapping(source: 'simple_port', data: {id: id, finding: finding})
70
+ port_text << "\n#[host]#\n#{host['address']}\n\n"
71
+ content_service.create_note node: port_node, text: port_text
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ def process_nexpose_simple_xml(doc)
78
+ results = doc.search('device')
79
+ hosts = Array.new
80
+ results.each do |host|
81
+ current_host = Hash.new
82
+ current_host['address'] = host['address']
83
+ current_host['fingerprint'] = host.search('fingerprint')[0].nil? ? "N/A" : host.search('fingerprint')[0]['certainty']
84
+ current_host['description'] = host.search('description')[0].nil? ? "N/A" : host.search('description')[0].text
85
+ #So there's two sets of vulns in a NeXpose simple XML report for each host
86
+ #Theres some generic ones at the top of the report
87
+ #And some service specific ones further down the report.
88
+ #So we need to get the generic ones before moving on
89
+ current_host['generic_vulns'] = Hash.new
90
+ host.xpath('vulnerabilities/vulnerability').each do |vuln|
91
+ current_host['generic_vulns'][vuln['id']] = ''
92
+ vuln.xpath('id').each do |id|
93
+ current_host['generic_vulns'][vuln['id']] << id['type'] + " : " + id.text + "\n"
94
+ end
95
+ end
96
+
97
+ current_host['ports'] = Hash.new
98
+ host.xpath('services/service').each do |service|
99
+ protocol = service['protocol']
100
+ portid = service['port']
101
+ port_label = protocol + '-' + portid
102
+ current_host['ports'][port_label] = Hash.new
103
+ service.xpath('vulnerabilities/vulnerability').each do |vuln|
104
+ current_host['ports'][port_label][vuln['id']] = ''
105
+ vuln.xpath('id').each do |id|
106
+ current_host['ports'][port_label][vuln['id']] << id['type'] + " : " + id.text + "\n"
107
+ end
108
+ end
109
+ end
110
+
111
+ hosts << current_host
112
+ end
113
+ return hosts
114
+ end
115
+ end
116
+ end
117
+ end
@@ -7,6 +7,7 @@ end
7
7
 
8
8
  require 'dradis/plugins/nexpose/engine'
9
9
  require 'dradis/plugins/nexpose/field_processor'
10
- require 'dradis/plugins/nexpose/importer'
10
+ require 'dradis/plugins/nexpose/full/importer'
11
11
  require 'dradis/plugins/nexpose/mapping'
12
+ require 'dradis/plugins/nexpose/simple/importer'
12
13
  require 'dradis/plugins/nexpose/version'
@@ -1,10 +1,27 @@
1
1
  class NexposeTasks < Thor
2
2
  include Rails.application.config.dradis.thor_helper_module
3
3
 
4
- namespace "dradis:plugins:nexpose"
4
+ namespace 'dradis:plugins:nexpose:upload'
5
5
 
6
- desc "upload FILE", "upload NeXpose results"
7
- def upload(file_path)
6
+ desc 'full FILE', 'upload NeXpose full results'
7
+ def full(file_path)
8
+ detect_and_set_project_scope
9
+
10
+ importer = Dradis::Plugins::Nexpose::Full::Importer.new(task_options)
11
+ importer.import(file: file_path)
12
+ end
13
+
14
+ desc 'simple FILE', 'upload NeXpose simple results'
15
+ def simple(file_path)
16
+ detect_and_set_project_scope
17
+
18
+ importer = Dradis::Plugins::Nexpose::Simple::Importer.new(task_options)
19
+ importer.import(file: file_path)
20
+ end
21
+
22
+ private
23
+
24
+ def process_upload(importer, file_path)
8
25
  require 'config/environment'
9
26
 
10
27
  unless File.exists?(file_path)
@@ -12,10 +29,6 @@ class NexposeTasks < Thor
12
29
  exit -1
13
30
  end
14
31
 
15
- detect_and_set_project_scope
16
-
17
- importer = Dradis::Plugins::Nexpose::Importer.new(task_options)
18
32
  importer.import(file: file_path)
19
33
  end
20
-
21
34
  end
@@ -0,0 +1,108 @@
1
+ # To run, execute from Dradis main app folder:
2
+ # bin/rspec [dradis-nexpose path]/spec/nexpose/full/importer_spec.rb
3
+ require 'rails_helper'
4
+ require 'ostruct'
5
+ require File.expand_path('../../../../../dradis-plugins/spec/support/spec_macros.rb', __FILE__)
6
+
7
+ include Dradis::Plugins::SpecMacros
8
+
9
+ module Dradis::Plugins
10
+ describe Nexpose::Full::Importer do
11
+ before do
12
+ @fixtures_dir = File.expand_path('../../../fixtures/files/', __FILE__)
13
+ end
14
+
15
+ before(:each) do
16
+ stub_content_service
17
+ @importer = described_class.new(content_service: @content_service)
18
+ end
19
+
20
+ def run_import!
21
+ @importer.import(file: @fixtures_dir + '/full.xml')
22
+ end
23
+
24
+ it 'creates nodes and associated notes as needed' do
25
+ expect(@content_service).to receive(:create_node).with(hash_including label: 'Nexpose Scan Summary').once
26
+
27
+ expect(@content_service).to receive(:create_note) do |args|
28
+ expect(args[:text]).to include("#[Title]#\nUSDA_Internal (4)")
29
+ expect(args[:node].label).to eq('Nexpose Scan Summary')
30
+ end.once
31
+
32
+ expect(@content_service).to receive(:create_node) do |args|
33
+ expect(args[:label]).to eq('1.1.1.1')
34
+ expect(args[:type]).to eq(:host)
35
+ create(:node, args.except(:type))
36
+ end
37
+
38
+ expect(@content_service).to receive(:create_note) do |args|
39
+ expect(args[:text]).to include("#[Title]#\n1.1.1.1")
40
+ expect(args[:text]).to include("#[Fingerprints]#\nIOS")
41
+ expect(args[:node].label).to eq('1.1.1.1')
42
+ end.once
43
+
44
+ expect(@content_service).to receive(:create_note) do |args|
45
+ expect(args[:text]).to include("#[Title]#\nService name: NTP")
46
+ expect(args[:node].label).to eq('1.1.1.1')
47
+ end.once
48
+
49
+ expect(@content_service).to receive(:create_note) do |args|
50
+ expect(args[:text]).to include("#[Title]#\nService name: SNMP")
51
+ expect(args[:node].label).to eq('1.1.1.1')
52
+ end.once
53
+
54
+ run_import!
55
+ end
56
+
57
+ it 'creates issues from vulnerability elements as needed' do
58
+ expect(@content_service).to receive(:create_issue) do |args|
59
+ expect(args[:text]).to include("#[Title]#\nApache HTTPD: error responses can expose cookies (CVE-2012-0053)")
60
+ expect(args[:id]).to eq('ntp-clock-variables-disclosure')
61
+ OpenStruct.new(args)
62
+ end.once
63
+
64
+ expect(@content_service).to receive(:create_issue) do |args|
65
+ expect(args[:text]).to include("#[Title]#\nApache HTTPD: ETag Inode Information Leakage (CVE-2003-1418)")
66
+ expect(args[:id]).to eq('test-02')
67
+ OpenStruct.new(args)
68
+ end.once
69
+
70
+ run_import!
71
+ end
72
+
73
+ it 'creates evidence as needed' do
74
+ expect(@content_service).to receive(:create_evidence) do |args|
75
+ expect(args[:content]).to include('The following NTP variables were found from a readvar')
76
+ expect(args[:issue].id).to eq('ntp-clock-variables-disclosure')
77
+ expect(args[:node].label).to eq('1.1.1.1')
78
+ end.once
79
+
80
+ expect(@content_service).to receive(:create_evidence) do |args|
81
+ expect(args[:content]).to include('Missing HTTP header "Content-Security-Policy"')
82
+ expect(args[:issue].id).to eq('test-02')
83
+ expect(args[:node].label).to eq('1.1.1.1')
84
+ end
85
+
86
+ run_import!
87
+ end
88
+
89
+ describe 'With duplicate nodes' do
90
+ it 'creates evidence for each instance of the node' do
91
+ expect(@content_service).to receive(:create_node).with(hash_including label: 'Nexpose Scan Summary').once
92
+ expect(@content_service).to receive(:create_node) do |args|
93
+ expect(args[:label]).to eq('1.1.1.1')
94
+ expect(args[:type]).to eq(:host)
95
+ create(:node, args.except(:type))
96
+ end
97
+
98
+ expect(@content_service).to receive(:create_evidence) do |args|
99
+ expect(args[:content]).to include("#[ID]#\nntp-clock-variables-disclosure\n\n")
100
+ expect(args[:issue].id).to eq('ntp-clock-variables-disclosure')
101
+ expect(args[:node].label).to eq('1.1.1.1')
102
+ end.twice
103
+
104
+ @importer.import(file: @fixtures_dir + '/full_with_duplicate_node.xml')
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,60 @@
1
+ # To run, execute from Dradis main app folder:
2
+ # bin/rspec [dradis-nexpose path]/spec/nexpose/simple/importer_spec.rb
3
+ require 'rails_helper'
4
+ require 'ostruct'
5
+ require File.expand_path('../../../../../dradis-plugins/spec/support/spec_macros.rb', __FILE__)
6
+
7
+ include Dradis::Plugins::SpecMacros
8
+
9
+ module Dradis::Plugins
10
+ describe Nexpose::Simple::Importer do
11
+ before do
12
+ @fixtures_dir = File.expand_path('../../../fixtures/files/', __FILE__)
13
+ end
14
+
15
+ before(:each) do
16
+ stub_content_service
17
+ @importer = described_class.new(content_service: @content_service)
18
+ end
19
+
20
+ def run_import!
21
+ @importer.import(file: @fixtures_dir + '/simple.xml')
22
+ end
23
+
24
+ it 'creates nodes and associated notes as needed' do
25
+ expect(@content_service).to receive(:create_node).with(hash_including label: '1.1.1.1', type: :host).once
26
+
27
+ expect(@content_service).to receive(:create_note) do |args|
28
+ expect(args[:text]).to include('Host Description : Linux 2.6.9-89.ELsmp')
29
+ expect(args[:text]).to include('Scanner Fingerprint certainty : 0.80')
30
+ expect(args[:node].label).to eq('1.1.1.1')
31
+ end.once
32
+
33
+ expect(@content_service).to receive(:create_node) do |args|
34
+ expect(args[:label]).to eq('Generic Findings')
35
+ expect(args[:parent].label).to eq('1.1.1.1')
36
+ OpenStruct.new(args)
37
+ end.once
38
+
39
+ expect(@content_service).to receive(:create_node) do |args|
40
+ expect(args[:label]).to eq('udp-000')
41
+ expect(args[:parent].label).to eq('1.1.1.1')
42
+ OpenStruct.new(args)
43
+ end.once
44
+
45
+ expect(@content_service).to receive(:create_note) do |args|
46
+ expect(args[:text]).to include("#[Id]#\nntpd-crypto")
47
+ expect(args[:text]).to include("#[host]#\n1.1.1.1")
48
+ expect(args[:node].label).to eq('udp-000')
49
+ end.once
50
+
51
+ expect(@content_service).to receive(:create_note) do |args|
52
+ expect(args[:text]).to include("#[Id]#\nntp-clock-radio")
53
+ expect(args[:text]).to include("#[host]#\n1.1.1.1")
54
+ expect(args[:node].label).to eq('udp-000')
55
+ end.once
56
+
57
+ run_import!
58
+ end
59
+ end
60
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dradis-nexpose
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.12.0
4
+ version: 4.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Martin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-07 00:00:00.000000000 Z
11
+ date: 2024-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dradis-plugins
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '2.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '2.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -117,11 +117,10 @@ files:
117
117
  - lib/dradis/plugins/nexpose.rb
118
118
  - lib/dradis/plugins/nexpose/engine.rb
119
119
  - lib/dradis/plugins/nexpose/field_processor.rb
120
- - lib/dradis/plugins/nexpose/formats/full.rb
121
- - lib/dradis/plugins/nexpose/formats/simple.rb
120
+ - lib/dradis/plugins/nexpose/full/importer.rb
122
121
  - lib/dradis/plugins/nexpose/gem_version.rb
123
- - lib/dradis/plugins/nexpose/importer.rb
124
122
  - lib/dradis/plugins/nexpose/mapping.rb
123
+ - lib/dradis/plugins/nexpose/simple/importer.rb
125
124
  - lib/dradis/plugins/nexpose/version.rb
126
125
  - lib/nexpose/endpoint.rb
127
126
  - lib/nexpose/node.rb
@@ -134,7 +133,8 @@ files:
134
133
  - spec/fixtures/files/full_with_duplicate_node.xml
135
134
  - spec/fixtures/files/simple.xml
136
135
  - spec/fixtures/files/ssl.xml
137
- - spec/nexpose_upload_spec.rb
136
+ - spec/nexpose/full/importer_spec.rb
137
+ - spec/nexpose/simple/importer_spec.rb
138
138
  - spec/spec_helper.rb
139
139
  - templates/full_evidence.sample
140
140
  - templates/full_node.sample
@@ -161,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
161
  - !ruby/object:Gem::Version
162
162
  version: '0'
163
163
  requirements: []
164
- rubygems_version: 3.1.4
164
+ rubygems_version: 3.5.6
165
165
  signing_key:
166
166
  specification_version: 4
167
167
  summary: Nexpose add-on for the Dradis Framework.
@@ -170,5 +170,6 @@ test_files:
170
170
  - spec/fixtures/files/full_with_duplicate_node.xml
171
171
  - spec/fixtures/files/simple.xml
172
172
  - spec/fixtures/files/ssl.xml
173
- - spec/nexpose_upload_spec.rb
173
+ - spec/nexpose/full/importer_spec.rb
174
+ - spec/nexpose/simple/importer_spec.rb
174
175
  - spec/spec_helper.rb
@@ -1,163 +0,0 @@
1
- module Dradis::Plugins::Nexpose::Formats
2
- # This module knows how to parse Nexpose Ful XML format.
3
- module Full
4
- private
5
-
6
- def process_full(doc)
7
- note_text = nil
8
-
9
- @vuln_list = []
10
- evidence = Hash.new { |h, k| h[k] = {} }
11
-
12
- # First, extract scans
13
- scan_node = content_service.create_node(label: 'Nexpose Scan Summary')
14
- logger.info { "\tProcessing scan summary" }
15
-
16
- doc.xpath('//scans/scan').each do |xml_scan|
17
- note_text = mapping_service.apply_mapping(source: 'full_scan', data: xml_scan)
18
- content_service.create_note(node: scan_node, text: note_text)
19
- end
20
-
21
- # Second, we parse the nodes
22
- doc.xpath('//nodes/node').each do |xml_node|
23
- nexpose_node = Nexpose::Node.new(xml_node)
24
-
25
- host_node = content_service.create_node(label: nexpose_node.address, type: :host)
26
- logger.info { "\tProcessing host: #{nexpose_node.address}" }
27
-
28
- # add the summary note for this host
29
- note_text = mapping_service.apply_mapping(source: 'full_node', data: nexpose_node)
30
- content_service.create_note(node: host_node, text: note_text)
31
-
32
- if host_node.respond_to?(:properties)
33
- logger.info { "\tAdding host properties to #{nexpose_node.address}" }
34
- host_node.set_property(:ip, nexpose_node.address)
35
- host_node.set_property(:hostname, nexpose_node.names)
36
- host_node.set_property(:os, nexpose_node.fingerprints)
37
- host_node.set_property(:risk_score, nexpose_node.risk_score)
38
- host_node.save
39
- end
40
-
41
- # inject this node's address into any vulnerabilities identified
42
- #
43
- # TODO: There is room for improvement here, we could have a hash that
44
- # linked vulns with test/service and host to create proper content for
45
- # Evidence.
46
- nexpose_node.tests.each do |node_test|
47
- test_id = node_test[:id].to_s.downcase
48
-
49
- # We can't use the straightforward version below because Nexpose uses
50
- # mixed-case some times (!)
51
- # xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[@id='#{node_test[:id]}']").first
52
- # See:
53
- # http://stackoverflow.com/questions/1625446/problem-with-upper-case-and-lower-case-xpath-functions-in-selenium-ide/1625859#1625859
54
- xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='#{test_id}']").first
55
- xml_vuln.add_child('<hosts/>') unless xml_vuln.last_element_child.name == 'hosts'
56
-
57
- if xml_vuln.xpath("./hosts/host[text()='#{nexpose_node.address}']").empty?
58
- xml_vuln.last_element_child.add_child("<host>#{nexpose_node.address}</host>")
59
- end
60
-
61
- evidence[test_id][nexpose_node.address] ||= []
62
- evidence[test_id][nexpose_node.address] << node_test
63
- end
64
-
65
- nexpose_node.endpoints.each do |endpoint|
66
- # endpoint_node = content_service.create_node(label: endpoint.label, parent: host_node)
67
- logger.info { "\t\tEndpoint: #{endpoint.label}" }
68
-
69
- if host_node.respond_to?(:properties)
70
- logger.info { "\t\tAdding to Services table" }
71
- host_node.set_service(
72
- port: endpoint.port.to_i,
73
- protocol: endpoint.protocol,
74
- state: endpoint.status,
75
- name: endpoint.services.map(&:name).join(', '),
76
- source: :nexpose,
77
- # reason: port.reason,
78
- # product: port.try('service').try('product'),
79
- # version: port.try('service').try('version')
80
- )
81
- end
82
-
83
- endpoint.services.each do |service|
84
-
85
- # add the summary note for this service
86
- note_text = mapping_service.apply_mapping(source: 'full_service', data: service)
87
- # content_service.create_note(node: endpoint_node, text: note_text)
88
- content_service.create_note(node: host_node, text: note_text)
89
-
90
- # inject this node's address into any vulnerabilities identified
91
- service.tests.each do |service_test|
92
- test_id = service_test[:id].to_s.downcase
93
-
94
- # For some reason Nexpose fails to include the 'http-iis-0011' vulnerability definition
95
- next if test_id == 'http-iis-0011'
96
-
97
- # We can't use the straightforward version below because Nexpose uses
98
- # mixed-case some times (!)
99
- # xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[@id='#{service_test[:id]}']").first
100
- # See:
101
- # http://stackoverflow.com/questions/1625446/problem-with-upper-case-and-lower-case-xpath-functions-in-selenium-ide/1625859#1625859
102
- #
103
- xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='#{test_id}']").first
104
- xml_vuln.add_child('<hosts/>') unless xml_vuln.last_element_child.name == 'hosts'
105
-
106
- if xml_vuln.xpath("./hosts/host[text()='#{nexpose_node.address}']").empty?
107
- xml_vuln.last_element_child.add_child("<host>#{nexpose_node.address}</host>")
108
- end
109
-
110
- evidence[test_id][nexpose_node.address] ||= []
111
- evidence[test_id][nexpose_node.address] << service_test
112
- end
113
- end
114
- end
115
-
116
- # add note under this node for each vulnerable ./node/test/
117
- host_node.save
118
- end
119
-
120
- # Third, parse vulnerability definitions
121
- logger.info { "\tProcessing issue definitions:" }
122
-
123
- doc.xpath('//VulnerabilityDefinitions/vulnerability').each do |xml_vulnerability|
124
- id = xml_vulnerability['id'].downcase
125
- # if @vuln_list.include?(id)
126
- issue_text = mapping_service.apply_mapping(
127
- source: 'full_vulnerability',
128
- data: xml_vulnerability
129
- )
130
-
131
- # retrieve hosts affected by this issue (injected in step 2)
132
- #
133
- # There is no need for the below as Issues are linked to hosts via the
134
- # corresponding Evidence instance
135
- #
136
- # note_text << "\n\n#[host]#\n"
137
- # note_text << xml_vulnerability.xpath('./hosts/host').collect(&:text).join("\n")
138
- # note_text << "\n\n"
139
-
140
- # 3.1 create the Issue
141
- issue = content_service.create_issue(text: issue_text, id: id)
142
- logger.info { "\tIssue: #{issue.fields ? issue.fields['Title'] : id}" }
143
-
144
- # 3.2 associate with the nodes via Evidence.
145
- # TODO: there is room for improvement here by providing proper Evidence content
146
- xml_vulnerability.xpath('./hosts/host').map(&:text).each do |host_name|
147
- # if the node exists, this just returns it
148
- host_node = content_service.create_node(label: host_name, type: :host)
149
-
150
- evidence[id][host_name].each do |evidence|
151
- evidence_content = mapping_service.apply_mapping(
152
- source: 'full_evidence',
153
- data: evidence
154
- )
155
- content_service.create_evidence(content: evidence_content, issue: issue, node: host_node)
156
- end
157
- end
158
-
159
- # end
160
- end
161
- end # /parse_nexpose_full_xml
162
- end
163
- end
@@ -1,76 +0,0 @@
1
- module Dradis::Plugins::Nexpose::Formats
2
-
3
- # This module knows how to parse Nexpose Simple XML format.
4
- module Simple
5
- private
6
-
7
- def process_simple(doc)
8
- hosts = process_nexpose_simple_xml(doc)
9
- notes_simple(hosts)
10
- end
11
-
12
- def notes_simple(hosts)
13
- return if hosts.nil?
14
-
15
- hosts.each do |host|
16
- host_node = content_service.create_node(label: host['address'], type: :host)
17
- content_service.create_note node: host_node, text: "Host Description : #{host['description']} \nScanner Fingerprint certainty : #{host['fingerprint']}"
18
-
19
- generic_findings_node = content_service.create_node(label: 'Generic Findings', parent: host_node)
20
- host['generic_vulns'].each do |id, finding|
21
- content_service.create_note node: generic_findings_node, text: "Finding ID : #{id} \n \n Finding Refs :\n-------\n #{finding}"
22
- end
23
-
24
- port_text = nil
25
- host['ports'].each do |port_label, findings|
26
- port_node = content_service.create_node(label: port_label, parent: host_node)
27
-
28
- findings.each do |id, finding|
29
- port_text = mapping_service.apply_mapping(source: 'simple_port', data: {id: id, finding: finding})
30
- port_text << "\n#[host]#\n#{host['address']}\n\n"
31
- content_service.create_note node: port_node, text: port_text
32
- end
33
- end
34
- end
35
- end
36
-
37
- def process_nexpose_simple_xml(doc)
38
- results = doc.search('device')
39
- hosts = Array.new
40
- results.each do |host|
41
- current_host = Hash.new
42
- current_host['address'] = host['address']
43
- current_host['fingerprint'] = host.search('fingerprint')[0].nil? ? "N/A" : host.search('fingerprint')[0]['certainty']
44
- current_host['description'] = host.search('description')[0].nil? ? "N/A" : host.search('description')[0].text
45
- #So there's two sets of vulns in a NeXpose simple XML report for each host
46
- #Theres some generic ones at the top of the report
47
- #And some service specific ones further down the report.
48
- #So we need to get the generic ones before moving on
49
- current_host['generic_vulns'] = Hash.new
50
- host.xpath('vulnerabilities/vulnerability').each do |vuln|
51
- current_host['generic_vulns'][vuln['id']] = ''
52
- vuln.xpath('id').each do |id|
53
- current_host['generic_vulns'][vuln['id']] << id['type'] + " : " + id.text + "\n"
54
- end
55
- end
56
-
57
- current_host['ports'] = Hash.new
58
- host.xpath('services/service').each do |service|
59
- protocol = service['protocol']
60
- portid = service['port']
61
- port_label = protocol + '-' + portid
62
- current_host['ports'][port_label] = Hash.new
63
- service.xpath('vulnerabilities/vulnerability').each do |vuln|
64
- current_host['ports'][port_label][vuln['id']] = ''
65
- vuln.xpath('id').each do |id|
66
- current_host['ports'][port_label][vuln['id']] << id['type'] + " : " + id.text + "\n"
67
- end
68
- end
69
- end
70
-
71
- hosts << current_host
72
- end
73
- return hosts
74
- end
75
- end
76
- end
@@ -1,38 +0,0 @@
1
- require 'dradis/plugins/nexpose/formats/full'
2
- require 'dradis/plugins/nexpose/formats/simple'
3
-
4
- module Dradis::Plugins::Nexpose
5
- class Importer < Dradis::Plugins::Upload::Importer
6
-
7
- include Formats::Full
8
- include Formats::Simple
9
-
10
- def self.templates
11
- { evidence: 'full_evidence', issue: 'full_vulnerability' }
12
- end
13
-
14
- # The framework will call this function if the user selects this plugin from
15
- # the dropdown list and uploads a file.
16
- # @returns true if the operation was successful, false otherwise
17
- def import(params={})
18
- file_content = File.read( params[:file] )
19
-
20
- logger.info { 'Parsing NeXpose output file...' }
21
- doc = Nokogiri::XML(file_content)
22
- logger.info { 'Done.' }
23
-
24
- if doc.root.name == 'NeXposeSimpleXML'
25
- logger.info { 'NeXpose-Simple format detected' }
26
- process_simple(doc)
27
- elsif doc.root.name == 'NexposeReport'
28
- logger.info { 'NeXpose-Full format detected' }
29
- process_full(doc)
30
- else
31
- error = "The document doesn't seem to be in either NeXpose-Simple or NeXpose-Full XML format. Ensure you uploaded a Nexpose XML report."
32
- logger.fatal{ error }
33
- content_service.create_note text: error
34
- return false
35
- end
36
- end
37
- end
38
- end
@@ -1,221 +0,0 @@
1
- require 'rails_helper'
2
- require 'ostruct'
3
-
4
- describe 'Nexpose upload plugin' do
5
- before do
6
- @fixtures_dir = File.expand_path('../fixtures/files/', __FILE__)
7
- end
8
-
9
- describe 'importer' do
10
- before(:each) do
11
- # Stub template service
12
- templates_dir = File.expand_path('../../templates', __FILE__)
13
- expect_any_instance_of(Dradis::Plugins::TemplateService)
14
- .to receive(:default_templates_dir).and_return(templates_dir)
15
-
16
- # Init services
17
- plugin = Dradis::Plugins::Nexpose
18
-
19
- @content_service = Dradis::Plugins::ContentService::Base.new(
20
- logger: Logger.new(STDOUT),
21
- plugin: plugin
22
- )
23
-
24
- @importer = plugin::Importer.new(
25
- content_service: @content_service,
26
- )
27
-
28
- # Stub dradis-plugins methods
29
- #
30
- # They return their argument hashes as objects mimicking
31
- # Nodes, Issues, etc
32
- allow(@content_service).to receive(:create_node) do |args|
33
- OpenStruct.new(args)
34
- end
35
- allow(@content_service).to receive(:create_note) do |args|
36
- OpenStruct.new(args)
37
- end
38
- allow(@content_service).to receive(:create_issue) do |args|
39
- OpenStruct.new(args)
40
- end
41
- allow(@content_service).to receive(:create_evidence) do |args|
42
- OpenStruct.new(args)
43
- end
44
- end
45
-
46
- describe 'Importer: Simple' do
47
- it 'creates nodes, issues, notes and an evidences as needed' do
48
-
49
- expect(@content_service).to receive(:create_node).with(hash_including label: '1.1.1.1', type: :host).once
50
-
51
- expect(@content_service).to receive(:create_note) do |args|
52
- expect(args[:text]).to include('Host Description : Linux 2.6.9-89.ELsmp')
53
- expect(args[:text]).to include('Scanner Fingerprint certainty : 0.80')
54
- expect(args[:node].label).to eq('1.1.1.1')
55
- end.once
56
-
57
- expect(@content_service).to receive(:create_node) do |args|
58
- expect(args[:label]).to eq('Generic Findings')
59
- expect(args[:parent].label).to eq('1.1.1.1')
60
- OpenStruct.new(args)
61
- end.once
62
-
63
- expect(@content_service).to receive(:create_node) do |args|
64
- expect(args[:label]).to eq('udp-000')
65
- expect(args[:parent].label).to eq('1.1.1.1')
66
- OpenStruct.new(args)
67
- end.once
68
-
69
- expect(@content_service).to receive(:create_note) do |args|
70
- expect(args[:text]).to include("#[Id]#\nntpd-crypto")
71
- expect(args[:text]).to include("#[host]#\n1.1.1.1")
72
- expect(args[:node].label).to eq('udp-000')
73
- end.once
74
-
75
- expect(@content_service).to receive(:create_note) do |args|
76
- expect(args[:text]).to include("#[Id]#\nntp-clock-radio")
77
- expect(args[:text]).to include("#[host]#\n1.1.1.1")
78
- expect(args[:node].label).to eq('udp-000')
79
- end.once
80
-
81
- @importer.import(file: @fixtures_dir + '/simple.xml')
82
- end
83
- end
84
-
85
- describe 'Importer: Full' do
86
- it 'creates nodes, issues, notes and an evidences as needed' do
87
- expect(@content_service).to receive(:create_node).with(hash_including label: 'Nexpose Scan Summary').once
88
- expect(@content_service).to receive(:create_note) do |args|
89
- expect(args[:text]).to include("#[Title]#\nUSDA_Internal (4)")
90
- expect(args[:node].label).to eq('Nexpose Scan Summary')
91
- end.once
92
-
93
- expect(@content_service).to receive(:create_node) do |args|
94
- expect(args[:label]).to eq('1.1.1.1')
95
- expect(args[:type]).to eq(:host)
96
- create(:node, args.except(:type))
97
- end
98
-
99
- expect(@content_service).to receive(:create_note) do |args|
100
- expect(args[:text]).to include("#[Title]#\n1.1.1.1")
101
- expect(args[:node].label).to eq('1.1.1.1')
102
- end.once
103
-
104
- expect(@content_service).to receive(:create_note) do |args|
105
- expect(args[:text]).to include("#[Title]#\nService name: NTP")
106
- expect(args[:node].label).to eq('1.1.1.1')
107
- end.once
108
-
109
- expect(@content_service).to receive(:create_note) do |args|
110
- expect(args[:text]).to include("#[Title]#\nService name: SNMP")
111
- expect(args[:node].label).to eq('1.1.1.1')
112
- end.once
113
-
114
- expect(@content_service).to receive(:create_issue) do |args|
115
- expect(args[:text]).to include("#[Title]#\nApache HTTPD: error responses can expose cookies (CVE-2012-0053)")
116
- expect(args[:id]).to eq('ntp-clock-variables-disclosure')
117
- OpenStruct.new(args)
118
- end.once
119
-
120
- expect(@content_service).to receive(:create_issue) do |args|
121
- expect(args[:text]).to include("#[Title]#\nApache HTTPD: ETag Inode Information Leakage (CVE-2003-1418)")
122
- expect(args[:id]).to eq('test-02')
123
- OpenStruct.new(args)
124
- end.once
125
-
126
- expect(@content_service).to receive(:create_evidence) do |args|
127
- expect(args[:content]).to include("#[ID]#\nntp-clock-variables-disclosure\n\n")
128
- expect(args[:issue].id).to eq('ntp-clock-variables-disclosure')
129
- expect(args[:node].label).to eq('1.1.1.1')
130
- end.once
131
-
132
- expect(@content_service).to receive(:create_evidence) do |args|
133
- expect(args[:content]).to include("#[ID]#\ntest-02\n\n")
134
- expect(args[:issue].id).to eq('test-02')
135
- expect(args[:node].label).to eq('1.1.1.1')
136
- end.once
137
-
138
- @importer.import(file: @fixtures_dir + '/full.xml')
139
-
140
- expect(Node.find_by(label: '1.1.1.1').properties[:os]).to eq('IOS')
141
- end
142
-
143
- it 'wraps ciphers inside ssl issues in code blocks' do
144
- expect(@content_service).to receive(:create_issue) do |args|
145
- expect(args[:text]).to include('bc. ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256')
146
- OpenStruct.new(args)
147
- end.once
148
-
149
- @importer.import(file: @fixtures_dir + '/ssl.xml')
150
- end
151
-
152
- # Regression test for github.com/dradis/dradis-nexpose/issues/1
153
- it 'populates solutions regardless of if they are wrapped in paragraphs or lists' do
154
- expect(@content_service).to receive(:create_issue) do |args|
155
- expect(args[:text]).to include("#[Solution]#\n\nApache HTTPD >= 2.0 and < 2.0.65")
156
- OpenStruct.new(args)
157
- end.once
158
-
159
- expect(@content_service).to receive(:create_issue) do |args|
160
- expect(args[:text]).to include("#[Solution]#\n")
161
- expect(args[:text]).to include('You can remove inode information from the ETag header')
162
- OpenStruct.new(args)
163
- end.once
164
-
165
- @importer.import(file: @fixtures_dir + '/full.xml')
166
- end
167
-
168
- it 'populates tests regardless of if they contain paragraphs or containerblockelements' do
169
- expect(@content_service).to receive(:create_evidence) do |args|
170
- expect(args[:content]).to include("#[Content]#\nThe following NTP variables")
171
- OpenStruct.new(args)
172
- end.once
173
-
174
- expect(@content_service).to receive(:create_evidence) do |args|
175
- expect(args[:content]).to include("#[Content]#\nVulnerable URL:")
176
- OpenStruct.new(args)
177
- end.once
178
-
179
- @importer.import(file: @fixtures_dir + '/full.xml')
180
- end
181
-
182
- it 'transforms html entities (&lt; and &gt;)' do
183
- expect(@content_service).to receive(:create_issue) do |args|
184
- expect(args[:text]).to include("#[Solution]#\n\nApache HTTPD >= 2.0 and < 2.0.65")
185
- OpenStruct.new(args)
186
- end
187
-
188
- @importer.import(file: @fixtures_dir + '/full.xml')
189
- end
190
- end
191
-
192
- describe 'Importer: Full with duplicate nodes' do
193
- it 'creates evidence for each instance of the node' do
194
- expect(@content_service).to receive(:create_node).with(hash_including label: 'Nexpose Scan Summary').once
195
- expect(@content_service).to receive(:create_node) do |args|
196
- expect(args[:label]).to eq('1.1.1.1')
197
- expect(args[:type]).to eq(:host)
198
- create(:node, args.except(:type))
199
- end
200
-
201
- expect(@content_service).to receive(:create_evidence) do |args|
202
- expect(args[:content]).to include("#[ID]#\nntp-clock-variables-disclosure\n\n")
203
- expect(args[:issue].id).to eq('ntp-clock-variables-disclosure')
204
- expect(args[:node].label).to eq('1.1.1.1')
205
- end.twice
206
-
207
- @importer.import(file: @fixtures_dir + '/full_with_duplicate_node.xml')
208
- end
209
- end
210
- end
211
-
212
- it 'parses the fingerprints field' do
213
- doc = Nokogiri::XML(File.read(@fixtures_dir + '/full.xml'))
214
-
215
- ts = Dradis::Plugins::TemplateService.new(plugin: Dradis::Plugins::Nexpose)
216
- ts.set_template(template: 'full_node', content: "#[Fingerprints]#\n%node.fingerprints%\n")
217
- result = ts.process_template(data: doc.at_xpath('//nodes/node'), template: 'full_node')
218
-
219
- expect(result).to include('IOS')
220
- end
221
- end