dradis-nexpose 4.14.0 → 4.16.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 +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/dradis/plugins/nexpose/gem_version.rb +1 -1
- data/lib/dradis/plugins/nexpose/xml_formatter.rb +81 -0
- data/lib/dradis/plugins/nexpose.rb +1 -0
- data/lib/nexpose/node.rb +13 -11
- data/lib/nexpose/vulnerability.rb +12 -53
- data/spec/fixtures/files/full.xml +27 -2
- data/spec/fixtures/files/full_with_duplicate_node.xml +4 -1
- data/spec/fixtures/files/lists.xml +26 -0
- data/spec/nexpose_upload_spec.rb +210 -0
- data/spec/xml_formatter_spec.rb +23 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0dd759ff5965a397c6dc53aa7fe958a472377df1e4b88598c084ef710bbf7325
|
4
|
+
data.tar.gz: cc1caab7f91bf7a934b940bda4773ea0af137218320c26fe4b33e89bfe212499
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11ab68a5ada00fd69fc77b7f41b3e08441dc7e0ac9fdbad90ff006b62a307fd6a4a44d05d534e954c88391ce63ec5fc28b4682e4e6e4f4c488f7a489192c0e6f
|
7
|
+
data.tar.gz: f610891a766cdb60ac5f1eccb90bfc497b2ee087f3c5ef3bd52037a11e342118c4ea62938ce3e7170409682c7a78302ecbbf06fe6d2450dde8a12584a6544ceb
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
v4.16.0 (May 2025)
|
2
|
+
- Format UnorderedList/OrderedList to textile
|
3
|
+
|
4
|
+
v4.15.0 (December 2024)
|
5
|
+
- No changes
|
6
|
+
|
1
7
|
v4.14.0 (October 2024)
|
2
8
|
- Separate general importer into Full & Simple importers
|
3
9
|
|
@@ -7,6 +13,7 @@ v4.13.0 (July 2024)
|
|
7
13
|
v4.12.0 (May 2024)
|
8
14
|
- Migrate integration to use Mappings Manager
|
9
15
|
- Update Dradis links in README
|
16
|
+
- Use 'n/a' as OS node property if there is no product value
|
10
17
|
|
11
18
|
v4.11.0 (January 2024)
|
12
19
|
- Add port/protocol to evidences
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Dradis::Plugins::Nexpose
|
2
|
+
class XmlFormatter
|
3
|
+
def format_html_content(source)
|
4
|
+
result = format_list(source)
|
5
|
+
|
6
|
+
cleanup_html(result)
|
7
|
+
end
|
8
|
+
|
9
|
+
def cleanup_html(source)
|
10
|
+
result = source.to_s
|
11
|
+
result.gsub!(/<ContainerBlockElement>(.*?)<\/ContainerBlockElement>/m) { |m| "#{ $1 }" }
|
12
|
+
result.gsub!(/<Paragraph preformat=\"true\">(\s*)<Paragraph preformat=\"true\">(.*?)<\/Paragraph>(\s*)<\/Paragraph>/mi) do
|
13
|
+
text = $2
|
14
|
+
text[/\n/] ? "\nbc.. #{ text }\n\np. " : "@#{text}@"
|
15
|
+
end
|
16
|
+
result.gsub!(/<Paragraph preformat=\"true\">(.*?)<\/Paragraph>/mi) do
|
17
|
+
text = $1
|
18
|
+
text[/\n/] ? "\nbc.. #{ text }\n\np. " : "@#{text}@"
|
19
|
+
end
|
20
|
+
result.gsub!(/<Paragraph>(.*?)<\/Paragraph>/m) { |m| "#{ $1 }\n" }
|
21
|
+
result.gsub!(/<Paragraph>|<\/Paragraph>/, '')
|
22
|
+
result.gsub!(/ /, '')
|
23
|
+
result.gsub!(/ /, '')
|
24
|
+
result.gsub!(/\t\t/, '')
|
25
|
+
result.gsub!(/<URLLink(.*)LinkURL=\"(.*?)\"(.*?)>(.*?)<\/URLLink>/im) { "\"#{$4.strip}\":#{$2.strip} " }
|
26
|
+
result.gsub!(/<URLLink LinkTitle=\"(.*?)\"(.*?)LinkURL=\"(.*?)\"\/>/i) { "\"#{$1.strip}\":#{$3.strip} " }
|
27
|
+
result.gsub!(/<URLLink LinkURL=\"(.*?)\"(.*?)LinkTitle=\"(.*?)\"\/>/i) { "\"#{$3.strip}\":#{$1.strip} " }
|
28
|
+
result.gsub!(/>/, '>')
|
29
|
+
result.gsub!(/</, '<')
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def cleanup_nested(source)
|
34
|
+
result = source.to_s
|
35
|
+
result.gsub!(/<references>/, '')
|
36
|
+
result.gsub!(/<\/references>/, '')
|
37
|
+
result.gsub!(/<reference source=\"(.*?)\">(.*?)<\/reference>/i) { "#{$1.strip}: #{$2.strip}\n" }
|
38
|
+
result.gsub!(/<tags>/, '')
|
39
|
+
result.gsub!(/<\/tags>/, '')
|
40
|
+
result.gsub!(/<tag>(.*?)<\/tag>/) { "#{$1}\n" }
|
41
|
+
result.gsub!(/ /, '')
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def format_list(source)
|
48
|
+
if source.xpath('./UnorderedList | ./OrderedList').any?
|
49
|
+
format_nexpose_list(source)
|
50
|
+
else
|
51
|
+
source
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_nexpose_list(xml, depth = 1)
|
56
|
+
xml.xpath('./UnorderedList | ./OrderedList').map do |list|
|
57
|
+
list_item_element = list.name == 'UnorderedList' ? '*' : '#'
|
58
|
+
|
59
|
+
list.xpath('./ListItem').map do |list_item|
|
60
|
+
paragraphs = list_item.xpath('./Paragraph')
|
61
|
+
|
62
|
+
list_item_text =
|
63
|
+
if paragraphs.any?
|
64
|
+
paragraphs.map do |paragraph|
|
65
|
+
# <Paragraph> nodes can either have more lists or just text
|
66
|
+
if paragraph.xpath('./UnorderedList | ./OrderedList').any?
|
67
|
+
format_nexpose_list(paragraph, depth + 1)
|
68
|
+
else
|
69
|
+
cleanup_html(paragraph.to_s).chomp
|
70
|
+
end
|
71
|
+
end.join("\n")
|
72
|
+
else
|
73
|
+
list_item.text
|
74
|
+
end
|
75
|
+
|
76
|
+
''.ljust(depth, list_item_element) + ' ' + list_item_text
|
77
|
+
end.join("\n")
|
78
|
+
end.join("\n")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/nexpose/node.rb
CHANGED
@@ -83,22 +83,24 @@ module Nexpose
|
|
83
83
|
# Finally the enumerations: names
|
84
84
|
if method_name == 'names'
|
85
85
|
@xml.xpath('./names/name').collect(&:text)
|
86
|
-
|
87
86
|
elsif ['fingerprints', 'software'].include?(method_name)
|
88
|
-
|
89
|
-
xpath_selector = {
|
90
|
-
'fingerprints' => './fingerprints/os',
|
91
|
-
'software' => './software/fingerprint'
|
92
|
-
}[method_name]
|
93
|
-
|
94
|
-
xml_os = @xml.at_xpath(xpath_selector)
|
95
|
-
return '' if xml_os.nil?
|
96
|
-
|
97
|
-
xml_os.attributes['product'].value
|
87
|
+
get_fingerprint_product(method_name)
|
98
88
|
else
|
99
89
|
# nothing found, the tag is valid but not present in this ReportItem
|
100
90
|
return nil
|
101
91
|
end
|
102
92
|
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# returns the first 'product' value from the <fingerprints> or <software> element
|
97
|
+
def get_fingerprint_product(method_name)
|
98
|
+
xpath_selector = {
|
99
|
+
'fingerprints' => './fingerprints/os',
|
100
|
+
'software' => './software/fingerprint'
|
101
|
+
}[method_name]
|
102
|
+
|
103
|
+
@xml.at_xpath(xpath_selector + '/@product')&.value || 'n/a'
|
104
|
+
end
|
103
105
|
end
|
104
106
|
end
|
@@ -20,7 +20,7 @@ module Nexpose
|
|
20
20
|
def supported_tags
|
21
21
|
[
|
22
22
|
# attributes
|
23
|
-
:added, :cvss_score, :cvss_vector, :modified, :nexpose_id, :pci_severity,
|
23
|
+
:added, :cvss_score, :cvss_vector, :modified, :nexpose_id, :pci_severity,
|
24
24
|
:published, :risk_score, :severity, :title,
|
25
25
|
|
26
26
|
# simple tags
|
@@ -34,10 +34,9 @@ module Nexpose
|
|
34
34
|
]
|
35
35
|
end
|
36
36
|
|
37
|
-
|
38
37
|
# This allows external callers (and specs) to check for implemented
|
39
38
|
# properties
|
40
|
-
def respond_to?(method, include_private=false)
|
39
|
+
def respond_to?(method, include_private = false)
|
41
40
|
return true if supported_tags.include?(method.to_sym)
|
42
41
|
super
|
43
42
|
end
|
@@ -49,7 +48,6 @@ module Nexpose
|
|
49
48
|
# attribute, simple descendent or collection that it maps to in the XML
|
50
49
|
# tree.
|
51
50
|
def method_missing(method, *args)
|
52
|
-
|
53
51
|
# We could remove this check and return nil for any non-recognized tag.
|
54
52
|
# The problem would be that it would make tricky to debug problems with
|
55
53
|
# typos. For instance: <>.potr would return nil instead of raising an
|
@@ -62,11 +60,11 @@ module Nexpose
|
|
62
60
|
# First we try the attributes. In Ruby we use snake_case, but in XML
|
63
61
|
# CamelCase is used for some attributes
|
64
62
|
translations_table = {
|
65
|
-
:
|
66
|
-
:
|
67
|
-
:
|
68
|
-
:
|
69
|
-
:
|
63
|
+
nexpose_id: 'id',
|
64
|
+
pci_severity: 'pciSeverity',
|
65
|
+
risk_score: 'riskScore',
|
66
|
+
cvss_score: 'cvssScore',
|
67
|
+
cvss_vector: 'cvssVector'
|
70
68
|
}
|
71
69
|
|
72
70
|
method_name = translations_table.fetch(method, method.to_s)
|
@@ -78,13 +76,14 @@ module Nexpose
|
|
78
76
|
nest = @xml.xpath("./#{method_name}").first
|
79
77
|
|
80
78
|
# We need to clean up tags that have HTML content in them
|
79
|
+
formatter = Dradis::Plugins::Nexpose::XmlFormatter.new
|
81
80
|
if tags_with_html_content.include?(method)
|
82
|
-
result =
|
81
|
+
result = formatter.format_html_content(tag)
|
83
82
|
result = add_bc_to_ssl_cipher_list(result) if SSL_CIPHER_VULN_IDS.include?(@xml.attributes['id'].value)
|
84
83
|
return result
|
85
84
|
# And we need to clean up the tags with nested content in them
|
86
85
|
elsif tags_with_nested_content.include?(method)
|
87
|
-
return cleanup_nested(nest)
|
86
|
+
return formatter.cleanup_nested(nest)
|
88
87
|
else
|
89
88
|
return tag
|
90
89
|
end
|
@@ -96,7 +95,7 @@ module Nexpose
|
|
96
95
|
return @xml.xpath("//test[@id='#{vuln_id}']/Paragraph").
|
97
96
|
text.split("\n").
|
98
97
|
collect(&:strip).
|
99
|
-
reject{|line| line.empty?}.join("\n")
|
98
|
+
reject { |line| line.empty? }.join("\n")
|
100
99
|
end
|
101
100
|
|
102
101
|
nil
|
@@ -106,46 +105,7 @@ module Nexpose
|
|
106
105
|
|
107
106
|
def add_bc_to_ssl_cipher_list(source)
|
108
107
|
result = source.to_s
|
109
|
-
result.gsub!(/\n(.*?)!(.*?)/){"\nbc. #{ $1 }!#{ $2 }\n"}
|
110
|
-
result
|
111
|
-
end
|
112
|
-
|
113
|
-
def cleanup_html(source)
|
114
|
-
result = source.to_s
|
115
|
-
result.gsub!(/<ContainerBlockElement>(.*?)<\/ContainerBlockElement>/m){|m| "#{ $1 }"}
|
116
|
-
result.gsub!(/<Paragraph preformat=\"true\">(\s*)<Paragraph preformat=\"true\">(.*?)<\/Paragraph>(\s*)<\/Paragraph>/mi) do
|
117
|
-
text = $2
|
118
|
-
text[/\n/] ? "\nbc.. #{ text }\n\np. " : "@#{text}@"
|
119
|
-
end
|
120
|
-
result.gsub!(/<Paragraph preformat=\"true\">(.*?)<\/Paragraph>/mi) do
|
121
|
-
text = $1
|
122
|
-
text[/\n/] ? "\nbc.. #{ text }\n\np. " : "@#{text}@"
|
123
|
-
end
|
124
|
-
result.gsub!(/<Paragraph>(.*?)<\/Paragraph>/m){|m| "#{ $1 }\n"}
|
125
|
-
result.gsub!(/<Paragraph>|<\/Paragraph>/, '')
|
126
|
-
result.gsub!(/<UnorderedList(.*?)>(.*?)<\/UnorderedList>/m){|m| "#{ $2 }"}
|
127
|
-
result.gsub!(/<OrderedList(.*?)>(.*?)<\/OrderedList>/m){|m| "#{ $2 }"}
|
128
|
-
result.gsub!(/<ListItem>|<\/ListItem>/, '')
|
129
|
-
result.gsub!(/ /, '')
|
130
|
-
result.gsub!(/ /, '')
|
131
|
-
result.gsub!(/\t\t/, '')
|
132
|
-
result.gsub!(/<URLLink(.*)LinkURL=\"(.*?)\"(.*?)>(.*?)<\/URLLink>/im) { "\"#{$4.strip}\":#{$2.strip} " }
|
133
|
-
result.gsub!(/<URLLink LinkTitle=\"(.*?)\"(.*?)LinkURL=\"(.*?)\"\/>/i) { "\"#{$1.strip}\":#{$3.strip} " }
|
134
|
-
result.gsub!(/<URLLink LinkURL=\"(.*?)\"(.*?)LinkTitle=\"(.*?)\"\/>/i) { "\"#{$3.strip}\":#{$1.strip} " }
|
135
|
-
result.gsub!(/>/, '>')
|
136
|
-
result.gsub!(/</, '<')
|
137
|
-
result
|
138
|
-
end
|
139
|
-
|
140
|
-
def cleanup_nested(source)
|
141
|
-
result = source.to_s
|
142
|
-
result.gsub!(/<references>/, '')
|
143
|
-
result.gsub!(/<\/references>/, '')
|
144
|
-
result.gsub!(/<reference source=\"(.*?)\">(.*?)<\/reference>/i) {"#{$1.strip}: #{$2.strip}\n"}
|
145
|
-
result.gsub!(/<tags>/, '')
|
146
|
-
result.gsub!(/<\/tags>/, '')
|
147
|
-
result.gsub!(/<tag>(.*?)<\/tag>/) {"#{$1}\n"}
|
148
|
-
result.gsub!(/ /, '')
|
108
|
+
result.gsub!(/\n(.*?)!(.*?)/) { "\nbc. #{ $1 }!#{ $2 }\n" }
|
149
109
|
result
|
150
110
|
end
|
151
111
|
|
@@ -156,6 +116,5 @@ module Nexpose
|
|
156
116
|
def tags_with_nested_content
|
157
117
|
[:references, :tags]
|
158
118
|
end
|
159
|
-
|
160
119
|
end
|
161
120
|
end
|
@@ -11,6 +11,15 @@
|
|
11
11
|
<fingerprints>
|
12
12
|
<os certainty="0.80" family="IOS" product="IOS" vendor="Cisco" arch="x86_64"/>
|
13
13
|
</fingerprints>
|
14
|
+
<software>
|
15
|
+
<fingerprint
|
16
|
+
certainty="1.00"
|
17
|
+
software-class="General"
|
18
|
+
vendor="Sun"
|
19
|
+
family="Java"
|
20
|
+
product="JRE"
|
21
|
+
version="1.6.0.22"/>
|
22
|
+
</software>
|
14
23
|
<tests/>
|
15
24
|
<endpoints>
|
16
25
|
<endpoint port="123" protocol="udp" status="open">
|
@@ -118,11 +127,27 @@
|
|
118
127
|
<ContainerBlockElement>
|
119
128
|
<UnorderedList>
|
120
129
|
<ListItem>
|
130
|
+
<Paragraph>Microsoft Windows</Paragraph>
|
121
131
|
<Paragraph>
|
122
|
-
<
|
123
|
-
|
132
|
+
<OrderedList>
|
133
|
+
<ListItem>Open the Windows Control Panel.</ListItem>
|
134
|
+
<ListItem>Select "Administrative Tools".</ListItem>
|
135
|
+
</OrderedList>
|
124
136
|
</Paragraph>
|
125
137
|
</ListItem>
|
138
|
+
<ListItem>
|
139
|
+
<Paragraph>Microsoft Windows</Paragraph>
|
140
|
+
<Paragraph>
|
141
|
+
<OrderedList>
|
142
|
+
<ListItem>Open the "Performance and Maintenance" control panel.</ListItem>
|
143
|
+
<ListItem>Select "Administrative Tools".</ListItem>
|
144
|
+
<ListItem>Restart the system for the changes to take effect.</ListItem></OrderedList></Paragraph></ListItem>
|
145
|
+
<ListItem>
|
146
|
+
<Paragraph>Microsoft Windows</Paragraph>
|
147
|
+
<Paragraph>
|
148
|
+
<OrderedList>
|
149
|
+
<ListItem>Open the "Administrative Tools" control panel.</ListItem>
|
150
|
+
<ListItem>Restart the system for the changes to take effect.</ListItem></OrderedList></Paragraph></ListItem>
|
126
151
|
<ListItem>
|
127
152
|
<Paragraph>OpenBSD</Paragraph>
|
128
153
|
<Paragraph>Download and apply the patch from:
|
@@ -9,8 +9,11 @@
|
|
9
9
|
<name>localhost:5000</name>
|
10
10
|
</names>
|
11
11
|
<fingerprints>
|
12
|
-
<os certainty="0.80" family="IOS"
|
12
|
+
<os certainty="0.80" family="IOS" vendor="Cisco" arch="x86_64"/>
|
13
13
|
</fingerprints>
|
14
|
+
<software>
|
15
|
+
<fingerprint certainty="1.00" vendor="Sun" family="Java"/>
|
16
|
+
</software>
|
14
17
|
<tests/>
|
15
18
|
<endpoints>
|
16
19
|
<endpoint port="123" protocol="udp" status="open">
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<ContainerBlockElement>
|
2
|
+
<UnorderedList>
|
3
|
+
<ListItem>
|
4
|
+
<Paragraph>Microsoft Windows</Paragraph>
|
5
|
+
<Paragraph>
|
6
|
+
<OrderedList>
|
7
|
+
<ListItem>Open the Windows Control Panel.</ListItem>
|
8
|
+
<ListItem>Select "Administrative Tools".</ListItem>
|
9
|
+
</OrderedList>
|
10
|
+
</Paragraph>
|
11
|
+
</ListItem>
|
12
|
+
<ListItem>
|
13
|
+
<Paragraph>Microsoft Windows</Paragraph>
|
14
|
+
<Paragraph>
|
15
|
+
<OrderedList>
|
16
|
+
<ListItem>Open the "Performance and Maintenance" control panel.</ListItem>
|
17
|
+
<ListItem>Select "Administrative Tools".</ListItem>
|
18
|
+
<ListItem>Restart the system for the changes to take effect.</ListItem></OrderedList></Paragraph></ListItem>
|
19
|
+
<ListItem>
|
20
|
+
<Paragraph>Microsoft Windows</Paragraph>
|
21
|
+
<Paragraph>
|
22
|
+
<OrderedList>
|
23
|
+
<ListItem>Open the "Administrative Tools" control panel.</ListItem>
|
24
|
+
<ListItem>Restart the system for the changes to take effect.</ListItem></OrderedList></Paragraph></ListItem>
|
25
|
+
</UnorderedList>
|
26
|
+
</ContainerBlockElement>
|
@@ -0,0 +1,210 @@
|
|
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: Full with fingerprints elements' do
|
10
|
+
def apply_mapping(doc, field)
|
11
|
+
ms = Dradis::Plugins::MappingService.new(
|
12
|
+
integration: Dradis::Plugins::Nexpose
|
13
|
+
)
|
14
|
+
mapping_fields = [OpenStruct.new(
|
15
|
+
destination_field: 'Fingerprints',
|
16
|
+
content: "{{ nexpose[#{field}] }}"
|
17
|
+
)]
|
18
|
+
|
19
|
+
@result = ms.apply_mapping(
|
20
|
+
data: doc.at_xpath('//nodes/node'),
|
21
|
+
mapping_fields: mapping_fields,
|
22
|
+
source: 'full_node'
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'with fingerprints > OS elements' do
|
27
|
+
it 'uses the os product value' do
|
28
|
+
doc = Nokogiri::XML(File.read(@fixtures_dir + '/full.xml'))
|
29
|
+
apply_mapping(doc, 'node.fingerprints')
|
30
|
+
|
31
|
+
expect(@result).to include('IOS')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'defaults to n/a if there is no os product value' do
|
35
|
+
doc = Nokogiri::XML(File.read(@fixtures_dir + '/full_with_duplicate_node.xml'))
|
36
|
+
apply_mapping(doc, 'node.fingerprints')
|
37
|
+
|
38
|
+
expect(@result).to include('n/a')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'with software > fingerprint elements' do
|
43
|
+
it 'uses the product value given software/fingerprints' do
|
44
|
+
doc = Nokogiri::XML(File.read(@fixtures_dir + '/full.xml'))
|
45
|
+
apply_mapping(doc, 'node.software')
|
46
|
+
|
47
|
+
expect(@result).to include('JRE')
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'defaults to n/a if there is no os product value' do
|
51
|
+
doc = Nokogiri::XML(File.read(@fixtures_dir + '/full_with_duplicate_node.xml'))
|
52
|
+
apply_mapping(doc, 'node.software')
|
53
|
+
|
54
|
+
expect(@result).to include('n/a')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'Importer: Full' do
|
59
|
+
it 'creates nodes, issues, notes and an evidences as needed' do
|
60
|
+
expect(@content_service).to receive(:create_node).with(hash_including label: 'Nexpose Scan Summary').once
|
61
|
+
expect(@content_service).to receive(:create_note) do |args|
|
62
|
+
expect(args[:text]).to include("#[Title]#\nUSDA_Internal (4)")
|
63
|
+
expect(args[:node].label).to eq('Nexpose Scan Summary')
|
64
|
+
end.once
|
65
|
+
|
66
|
+
expect(@content_service).to receive(:create_node) do |args|
|
67
|
+
expect(args[:label]).to eq('1.1.1.1')
|
68
|
+
expect(args[:type]).to eq(:host)
|
69
|
+
create(:node, args.except(:type))
|
70
|
+
end
|
71
|
+
|
72
|
+
expect(@content_service).to receive(:create_note) do |args|
|
73
|
+
expect(args[:text]).to include("#[Title]#\n1.1.1.1")
|
74
|
+
expect(args[:node].label).to eq('1.1.1.1')
|
75
|
+
end.once
|
76
|
+
|
77
|
+
expect(@content_service).to receive(:create_note) do |args|
|
78
|
+
expect(args[:text]).to include("#[Title]#\nService name: NTP")
|
79
|
+
expect(args[:node].label).to eq('1.1.1.1')
|
80
|
+
end.once
|
81
|
+
|
82
|
+
expect(@content_service).to receive(:create_note) do |args|
|
83
|
+
expect(args[:text]).to include("#[Title]#\nService name: SNMP")
|
84
|
+
expect(args[:node].label).to eq('1.1.1.1')
|
85
|
+
end.once
|
86
|
+
|
87
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
88
|
+
expect(args[:text]).to include("#[Title]#\nApache HTTPD: error responses can expose cookies (CVE-2012-0053)")
|
89
|
+
expect(args[:id]).to eq('ntp-clock-variables-disclosure')
|
90
|
+
OpenStruct.new(args)
|
91
|
+
end.once
|
92
|
+
|
93
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
94
|
+
expect(args[:text]).to include("#[Title]#\nApache HTTPD: ETag Inode Information Leakage (CVE-2003-1418)")
|
95
|
+
expect(args[:id]).to eq('test-02')
|
96
|
+
OpenStruct.new(args)
|
97
|
+
end.once
|
98
|
+
|
99
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
100
|
+
expect(args[:content]).to include("#[ID]#\nntp-clock-variables-disclosure\n\n")
|
101
|
+
expect(args[:issue].id).to eq('ntp-clock-variables-disclosure')
|
102
|
+
expect(args[:node].label).to eq('1.1.1.1')
|
103
|
+
end.once
|
104
|
+
|
105
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
106
|
+
expect(args[:content]).to include("#[ID]#\ntest-02\n\n")
|
107
|
+
expect(args[:issue].id).to eq('test-02')
|
108
|
+
expect(args[:node].label).to eq('1.1.1.1')
|
109
|
+
end.once
|
110
|
+
|
111
|
+
@importer.import(file: @fixtures_dir + '/full.xml')
|
112
|
+
|
113
|
+
expect(Node.find_by(label: '1.1.1.1').properties[:os]).to eq('IOS')
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'wraps ciphers inside ssl issues in code blocks' do
|
117
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
118
|
+
expect(args[:text]).to include('bc. ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256')
|
119
|
+
OpenStruct.new(args)
|
120
|
+
end.once
|
121
|
+
|
122
|
+
@importer.import(file: @fixtures_dir + '/ssl.xml')
|
123
|
+
end
|
124
|
+
|
125
|
+
# Regression test for github.com/dradis/dradis-nexpose/issues/1
|
126
|
+
it 'populates solutions regardless of if they are wrapped in paragraphs or lists' do
|
127
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
128
|
+
expect(args[:text]).to include("#[Solution]#\n\nApache HTTPD >= 2.0 and < 2.0.65")
|
129
|
+
OpenStruct.new(args)
|
130
|
+
end.once
|
131
|
+
|
132
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
133
|
+
expect(args[:text]).to include("#[Solution]#\n")
|
134
|
+
expect(args[:text]).to include('You can remove inode information from the ETag header')
|
135
|
+
OpenStruct.new(args)
|
136
|
+
end.once
|
137
|
+
|
138
|
+
@importer.import(file: @fixtures_dir + '/full.xml')
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'populates tests regardless of if they contain paragraphs or containerblockelements' do
|
142
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
143
|
+
expect(args[:content]).to include("#[Content]#\nThe following NTP variables")
|
144
|
+
OpenStruct.new(args)
|
145
|
+
end.once
|
146
|
+
|
147
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
148
|
+
expect(args[:content]).to include("#[Content]#\nVulnerable URL:")
|
149
|
+
OpenStruct.new(args)
|
150
|
+
end.once
|
151
|
+
|
152
|
+
@importer.import(file: @fixtures_dir + '/full.xml')
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'transforms html entities (< and >)' do
|
156
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
157
|
+
expect(args[:text]).to include("#[Solution]#\n\nApache HTTPD >= 2.0 and < 2.0.65")
|
158
|
+
OpenStruct.new(args)
|
159
|
+
end
|
160
|
+
|
161
|
+
@importer.import(file: @fixtures_dir + '/full.xml')
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'formats the list to textile lists' do
|
165
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
166
|
+
expect(args[:text]).to include("#[Title]#\nApache HTTPD: error responses can expose cookies (CVE-2012-0053)")
|
167
|
+
OpenStruct.new(args)
|
168
|
+
end.once
|
169
|
+
|
170
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
171
|
+
expect(args[:text]).to include('* Microsoft Windows')
|
172
|
+
expect(args[:text]).to include('## Open the Windows Control Panel.')
|
173
|
+
expect(args[:text]).to include('## Select "Administrative Tools".')
|
174
|
+
OpenStruct.new(args)
|
175
|
+
end.once
|
176
|
+
|
177
|
+
@importer.import(file: @fixtures_dir + '/full.xml')
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe 'Importer: Full with duplicate nodes' do
|
182
|
+
it 'creates evidence for each instance of the node' do
|
183
|
+
expect(@content_service).to receive(:create_node).with(hash_including label: 'Nexpose Scan Summary').once
|
184
|
+
expect(@content_service).to receive(:create_node) do |args|
|
185
|
+
expect(args[:label]).to eq('1.1.1.1')
|
186
|
+
expect(args[:type]).to eq(:host)
|
187
|
+
create(:node, args.except(:type))
|
188
|
+
end
|
189
|
+
|
190
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
191
|
+
expect(args[:content]).to include("#[ID]#\nntp-clock-variables-disclosure\n\n")
|
192
|
+
expect(args[:issue].id).to eq('ntp-clock-variables-disclosure')
|
193
|
+
expect(args[:node].label).to eq('1.1.1.1')
|
194
|
+
end.twice
|
195
|
+
|
196
|
+
@importer.import(file: @fixtures_dir + '/full_with_duplicate_node.xml')
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'parses the fingerprints field' do
|
202
|
+
doc = Nokogiri::XML(File.read(@fixtures_dir + '/full.xml'))
|
203
|
+
|
204
|
+
ts = Dradis::Plugins::TemplateService.new(plugin: Dradis::Plugins::Nexpose)
|
205
|
+
ts.set_template(template: 'full_node', content: "#[Fingerprints]#\n%node.fingerprints%\n")
|
206
|
+
result = ts.process_template(data: doc.at_xpath('//nodes/node'), template: 'full_node')
|
207
|
+
|
208
|
+
expect(result).to include('IOS')
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Run the spec by running the command: rspec spec/xml_formatter_spec.rb"
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Dradis::Plugins::Nexpose::XmlFormatter do
|
6
|
+
let(:source) { File.read(File.expand_path('../fixtures/files/lists.xml', __FILE__)) }
|
7
|
+
|
8
|
+
it 'parses the <UnorderedList> and <OrderedList> elements' do
|
9
|
+
xml = Nokogiri::XML(source).at_xpath('./ContainerBlockElement')
|
10
|
+
expect(Dradis::Plugins::Nexpose::XmlFormatter.new.format_html_content(xml)).to eq(
|
11
|
+
"* Microsoft Windows\n"\
|
12
|
+
"## Open the Windows Control Panel.\n"\
|
13
|
+
"## Select \"Administrative Tools\".\n"\
|
14
|
+
"* Microsoft Windows\n"\
|
15
|
+
"## Open the \"Performance and Maintenance\" control panel.\n"\
|
16
|
+
"## Select \"Administrative Tools\".\n"\
|
17
|
+
"## Restart the system for the changes to take effect.\n"\
|
18
|
+
"* Microsoft Windows\n"\
|
19
|
+
"## Open the \"Administrative Tools\" control panel.\n"\
|
20
|
+
'## Restart the system for the changes to take effect.'
|
21
|
+
)
|
22
|
+
end
|
23
|
+
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.
|
4
|
+
version: 4.16.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:
|
11
|
+
date: 2025-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dradis-plugins
|
@@ -122,6 +122,7 @@ files:
|
|
122
122
|
- lib/dradis/plugins/nexpose/mapping.rb
|
123
123
|
- lib/dradis/plugins/nexpose/simple/importer.rb
|
124
124
|
- lib/dradis/plugins/nexpose/version.rb
|
125
|
+
- lib/dradis/plugins/nexpose/xml_formatter.rb
|
125
126
|
- lib/nexpose/endpoint.rb
|
126
127
|
- lib/nexpose/node.rb
|
127
128
|
- lib/nexpose/scan.rb
|
@@ -131,11 +132,14 @@ files:
|
|
131
132
|
- lib/tasks/thorfile.rb
|
132
133
|
- spec/fixtures/files/full.xml
|
133
134
|
- spec/fixtures/files/full_with_duplicate_node.xml
|
135
|
+
- spec/fixtures/files/lists.xml
|
134
136
|
- spec/fixtures/files/simple.xml
|
135
137
|
- spec/fixtures/files/ssl.xml
|
136
138
|
- spec/nexpose/full/importer_spec.rb
|
137
139
|
- spec/nexpose/simple/importer_spec.rb
|
140
|
+
- spec/nexpose_upload_spec.rb
|
138
141
|
- spec/spec_helper.rb
|
142
|
+
- spec/xml_formatter_spec.rb
|
139
143
|
- templates/full_evidence.sample
|
140
144
|
- templates/full_node.sample
|
141
145
|
- templates/full_scan.sample
|
@@ -168,8 +172,11 @@ summary: Nexpose add-on for the Dradis Framework.
|
|
168
172
|
test_files:
|
169
173
|
- spec/fixtures/files/full.xml
|
170
174
|
- spec/fixtures/files/full_with_duplicate_node.xml
|
175
|
+
- spec/fixtures/files/lists.xml
|
171
176
|
- spec/fixtures/files/simple.xml
|
172
177
|
- spec/fixtures/files/ssl.xml
|
173
178
|
- spec/nexpose/full/importer_spec.rb
|
174
179
|
- spec/nexpose/simple/importer_spec.rb
|
180
|
+
- spec/nexpose_upload_spec.rb
|
175
181
|
- spec/spec_helper.rb
|
182
|
+
- spec/xml_formatter_spec.rb
|