dradis-nexpose 4.10.0 → 4.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/pull_request_template.md +12 -3
- data/CHANGELOG.md +11 -0
- data/README.md +3 -4
- data/lib/dradis/plugins/nexpose/formats/full.rb +45 -44
- data/lib/dradis/plugins/nexpose/formats/simple.rb +1 -1
- data/lib/dradis/plugins/nexpose/gem_version.rb +2 -2
- data/lib/dradis/plugins/nexpose/mapping.rb +101 -0
- data/lib/dradis/plugins/nexpose.rb +1 -0
- data/lib/nexpose/endpoint.rb +5 -7
- data/lib/nexpose/node.rb +6 -12
- data/lib/nexpose/service.rb +18 -11
- data/lib/nexpose/test.rb +17 -8
- data/lib/nexpose/vulnerability.rb +3 -2
- data/spec/fixtures/files/full.xml +25 -30
- data/spec/fixtures/files/full_with_duplicate_node.xml +136 -0
- data/spec/nexpose_upload_spec.rb +197 -146
- data/templates/full_evidence.sample +1 -1
- data/templates/full_service.sample +1 -1
- data/templates/full_vulnerability.sample +1 -0
- metadata +9 -18
- data/templates/full_evidence.fields +0 -3
- data/templates/full_evidence.template +0 -8
- data/templates/full_node.fields +0 -10
- data/templates/full_node.template +0 -16
- data/templates/full_scan.fields +0 -5
- data/templates/full_scan.template +0 -9
- data/templates/full_service.fields +0 -4
- data/templates/full_service.template +0 -11
- data/templates/full_vulnerability.fields +0 -13
- data/templates/full_vulnerability.template +0 -34
- data/templates/simple_port.fields +0 -2
- data/templates/simple_port.template +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5b342a73c3a4576df7bd68d24142393480c9abed09cd8f7a8c2f9aefe67339e
|
4
|
+
data.tar.gz: 4d24a2ed95a57a001e9f909740fd95067332065804d282598c2b86d31db6b644
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0080d88c9b79f02f1a9610d85aa350f26e3c059fae144c15a1ccb66c6acf69e69e076063c2d7451915e9a625892f1fc9a519f099aed3e9577edfa4278cb24b5d'
|
7
|
+
data.tar.gz: 9231e9e9a60fe0b06316f024b0f089c76bda3c4bcb4000f0eb4079da893ee295daf59cfabe5f47b85b6f989f2e72b1f87ed988c499ccc64a880a29c9a52e8bbd
|
@@ -1,3 +1,5 @@
|
|
1
|
+
Please review [CONTRIBUTING.md](https://github.com/dradis/dradis-ce/blob/develop/CONTRIBUTING.md) and remove this line.
|
2
|
+
|
1
3
|
### Summary
|
2
4
|
|
3
5
|
Provide a general description of the code changes in your pull
|
@@ -6,6 +8,11 @@ these bugs have open GitHub issues, be sure to tag them here as well,
|
|
6
8
|
to keep the conversation linked together.
|
7
9
|
|
8
10
|
|
11
|
+
### Testing Steps
|
12
|
+
|
13
|
+
Provide steps to test functionality, described in detail for someone not familiar with this part of the application / code base
|
14
|
+
|
15
|
+
|
9
16
|
### Other Information
|
10
17
|
|
11
18
|
If there's anything else that's important and relevant to your pull
|
@@ -26,11 +33,13 @@ products, we must have the copyright associated with the entire
|
|
26
33
|
codebase. Any code you create which is merged must be owned by us.
|
27
34
|
That's not us trying to be a jerks, that's just the way it works.
|
28
35
|
|
29
|
-
Please review the [CONTRIBUTING.md](https://github.com/dradis/dradis-ce/blob/master/CONTRIBUTING.md)
|
30
|
-
file for the details.
|
31
|
-
|
32
36
|
You can delete this section, but the following sentence needs to
|
33
37
|
remain in the PR's description:
|
34
38
|
|
35
39
|
> I assign all rights, including copyright, to any future Dradis
|
36
40
|
> work by myself to Security Roots.
|
41
|
+
|
42
|
+
### Check List
|
43
|
+
|
44
|
+
- [ ] Added a CHANGELOG entry
|
45
|
+
- [ ] Added specs
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
v4.12.0 (May 2024)
|
2
|
+
- Migrate integration to use Mappings Manager
|
3
|
+
- Update Dradis links in README
|
4
|
+
|
5
|
+
v4.11.0 (January 2024)
|
6
|
+
- Add port/protocol to evidences
|
7
|
+
- Use the details in <os> as the OS node property
|
8
|
+
- Import `vulnerability.risk_score` as a new Issue field
|
9
|
+
- Allow multiple evidence with the same test id & node address
|
10
|
+
- Add support for tests that start with `ContainerBlockElement`
|
11
|
+
|
1
12
|
v4.10.0 (September 2023)
|
2
13
|
- Update gemspec links
|
3
14
|
|
data/README.md
CHANGED
@@ -6,17 +6,16 @@ The Nexpose add-on enables users to upload Nexpose XML files to create a structu
|
|
6
6
|
|
7
7
|
This plugin supports both NeXpose-Simple and NeXpose-Full formats.
|
8
8
|
|
9
|
-
The add-on requires [Dradis CE](https://
|
10
|
-
|
9
|
+
The add-on requires [Dradis CE](https://dradis.com/ce/) > 3.0, or [Dradis Pro](https://dradis.com/).
|
11
10
|
|
12
11
|
## More information
|
13
12
|
|
14
|
-
See the Dradis Framework's [README.md](https://github.com/dradis/
|
13
|
+
See the Dradis Framework's [README.md](https://github.com/dradis/dradis-ce/blob/develop/README.md)
|
15
14
|
|
16
15
|
|
17
16
|
## Contributing
|
18
17
|
|
19
|
-
See the Dradis Framework's [CONTRIBUTING.md](https://github.com/dradis/
|
18
|
+
See the Dradis Framework's [CONTRIBUTING.md](https://github.com/dradis/dradis-ce/blob/develop/CONTRIBUTING.md)
|
20
19
|
|
21
20
|
|
22
21
|
## License
|
@@ -1,5 +1,4 @@
|
|
1
1
|
module Dradis::Plugins::Nexpose::Formats
|
2
|
-
|
3
2
|
# This module knows how to parse Nexpose Ful XML format.
|
4
3
|
module Full
|
5
4
|
private
|
@@ -12,30 +11,29 @@ module Dradis::Plugins::Nexpose::Formats
|
|
12
11
|
|
13
12
|
# First, extract scans
|
14
13
|
scan_node = content_service.create_node(label: 'Nexpose Scan Summary')
|
15
|
-
logger.info{ "\tProcessing scan summary" }
|
14
|
+
logger.info { "\tProcessing scan summary" }
|
16
15
|
|
17
16
|
doc.xpath('//scans/scan').each do |xml_scan|
|
18
|
-
note_text =
|
17
|
+
note_text = mapping_service.apply_mapping(source: 'full_scan', data: xml_scan)
|
19
18
|
content_service.create_note(node: scan_node, text: note_text)
|
20
19
|
end
|
21
20
|
|
22
|
-
|
23
21
|
# Second, we parse the nodes
|
24
22
|
doc.xpath('//nodes/node').each do |xml_node|
|
25
23
|
nexpose_node = Nexpose::Node.new(xml_node)
|
26
24
|
|
27
25
|
host_node = content_service.create_node(label: nexpose_node.address, type: :host)
|
28
|
-
logger.info{ "\tProcessing host: #{nexpose_node.address}" }
|
26
|
+
logger.info { "\tProcessing host: #{nexpose_node.address}" }
|
29
27
|
|
30
28
|
# add the summary note for this host
|
31
|
-
note_text =
|
29
|
+
note_text = mapping_service.apply_mapping(source: 'full_node', data: nexpose_node)
|
32
30
|
content_service.create_note(node: host_node, text: note_text)
|
33
31
|
|
34
32
|
if host_node.respond_to?(:properties)
|
35
|
-
logger.info{ "\tAdding host properties to #{nexpose_node.address}"}
|
33
|
+
logger.info { "\tAdding host properties to #{nexpose_node.address}" }
|
36
34
|
host_node.set_property(:ip, nexpose_node.address)
|
37
35
|
host_node.set_property(:hostname, nexpose_node.names)
|
38
|
-
host_node.set_property(:os, nexpose_node.
|
36
|
+
host_node.set_property(:os, nexpose_node.fingerprints)
|
39
37
|
host_node.set_property(:risk_score, nexpose_node.risk_score)
|
40
38
|
host_node.save
|
41
39
|
end
|
@@ -54,21 +52,22 @@ module Dradis::Plugins::Nexpose::Formats
|
|
54
52
|
# See:
|
55
53
|
# http://stackoverflow.com/questions/1625446/problem-with-upper-case-and-lower-case-xpath-functions-in-selenium-ide/1625859#1625859
|
56
54
|
xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='#{test_id}']").first
|
57
|
-
xml_vuln.add_child(
|
55
|
+
xml_vuln.add_child('<hosts/>') unless xml_vuln.last_element_child.name == 'hosts'
|
58
56
|
|
59
57
|
if xml_vuln.xpath("./hosts/host[text()='#{nexpose_node.address}']").empty?
|
60
58
|
xml_vuln.last_element_child.add_child("<host>#{nexpose_node.address}</host>")
|
61
59
|
end
|
62
60
|
|
63
|
-
evidence[test_id][nexpose_node.address]
|
61
|
+
evidence[test_id][nexpose_node.address] ||= []
|
62
|
+
evidence[test_id][nexpose_node.address] << node_test
|
64
63
|
end
|
65
64
|
|
66
65
|
nexpose_node.endpoints.each do |endpoint|
|
67
66
|
# endpoint_node = content_service.create_node(label: endpoint.label, parent: host_node)
|
68
|
-
logger.info{ "\t\tEndpoint: #{endpoint.label}" }
|
67
|
+
logger.info { "\t\tEndpoint: #{endpoint.label}" }
|
69
68
|
|
70
69
|
if host_node.respond_to?(:properties)
|
71
|
-
logger.info{ "\t\tAdding to Services table" }
|
70
|
+
logger.info { "\t\tAdding to Services table" }
|
72
71
|
host_node.set_service(
|
73
72
|
port: endpoint.port.to_i,
|
74
73
|
protocol: endpoint.protocol,
|
@@ -84,7 +83,7 @@ module Dradis::Plugins::Nexpose::Formats
|
|
84
83
|
endpoint.services.each do |service|
|
85
84
|
|
86
85
|
# add the summary note for this service
|
87
|
-
note_text =
|
86
|
+
note_text = mapping_service.apply_mapping(source: 'full_service', data: service)
|
88
87
|
# content_service.create_note(node: endpoint_node, text: note_text)
|
89
88
|
content_service.create_note(node: host_node, text: note_text)
|
90
89
|
|
@@ -102,13 +101,14 @@ module Dradis::Plugins::Nexpose::Formats
|
|
102
101
|
# http://stackoverflow.com/questions/1625446/problem-with-upper-case-and-lower-case-xpath-functions-in-selenium-ide/1625859#1625859
|
103
102
|
#
|
104
103
|
xml_vuln = doc.xpath("//VulnerabilityDefinitions/vulnerability[translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='#{test_id}']").first
|
105
|
-
xml_vuln.add_child(
|
104
|
+
xml_vuln.add_child('<hosts/>') unless xml_vuln.last_element_child.name == 'hosts'
|
106
105
|
|
107
106
|
if xml_vuln.xpath("./hosts/host[text()='#{nexpose_node.address}']").empty?
|
108
107
|
xml_vuln.last_element_child.add_child("<host>#{nexpose_node.address}</host>")
|
109
108
|
end
|
110
109
|
|
111
|
-
evidence[test_id][nexpose_node.address]
|
110
|
+
evidence[test_id][nexpose_node.address] ||= []
|
111
|
+
evidence[test_id][nexpose_node.address] << service_test
|
112
112
|
end
|
113
113
|
end
|
114
114
|
end
|
@@ -118,42 +118,43 @@ module Dradis::Plugins::Nexpose::Formats
|
|
118
118
|
end
|
119
119
|
|
120
120
|
# Third, parse vulnerability definitions
|
121
|
-
logger.info{ "\tProcessing issue definitions:" }
|
121
|
+
logger.info { "\tProcessing issue definitions:" }
|
122
122
|
|
123
123
|
doc.xpath('//VulnerabilityDefinitions/vulnerability').each do |xml_vulnerability|
|
124
124
|
id = xml_vulnerability['id'].downcase
|
125
125
|
# if @vuln_list.include?(id)
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
evidence_content =
|
152
|
-
|
153
|
-
data: evidence
|
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
154
|
)
|
155
155
|
content_service.create_evidence(content: evidence_content, issue: issue, node: host_node)
|
156
156
|
end
|
157
|
+
end
|
157
158
|
|
158
159
|
# end
|
159
160
|
end
|
@@ -26,7 +26,7 @@ module Dradis::Plugins::Nexpose::Formats
|
|
26
26
|
port_node = content_service.create_node(label: port_label, parent: host_node)
|
27
27
|
|
28
28
|
findings.each do |id, finding|
|
29
|
-
port_text =
|
29
|
+
port_text = mapping_service.apply_mapping(source: 'simple_port', data: {id: id, finding: finding})
|
30
30
|
port_text << "\n#[host]#\n#{host['address']}\n\n"
|
31
31
|
content_service.create_note node: port_node, text: port_text
|
32
32
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Dradis::Plugins::Nexpose
|
2
|
+
module Mapping
|
3
|
+
DEFAULT_MAPPING = {
|
4
|
+
full_evidence: {
|
5
|
+
'ID' => '{{ nexpose[evidence.id] }}',
|
6
|
+
'Status' => '{{ nexpose[evidence.status] }}',
|
7
|
+
'Content' => '{{ nexpose[evidence.content] }}'
|
8
|
+
},
|
9
|
+
full_node: {
|
10
|
+
'Title' => '{{ nexpose[node.address] }}',
|
11
|
+
'Hostname' => '{{ nexpose[node.site_name] }}',
|
12
|
+
'Details' => "Status: {{ nexpose[node.status] }}\nDevice id: {{ nexpose[node.device_id] }}\nHW address: {{ nexpose[node.hardware_address] }}",
|
13
|
+
'Names' => '{{ nexpose[node.names] }}',
|
14
|
+
'Software' => '{{ nexpose[node.software] }}'
|
15
|
+
},
|
16
|
+
full_scan: {
|
17
|
+
'Title' => '{{ nexpose[scan.name] }} ({{ nexpose[scan.scan_id] }})',
|
18
|
+
'Timing' => "Start time: {{ nexpose[scan.start_time] }}\nEnd time: {{ nexpose[scan.end_time] }}",
|
19
|
+
'Status' => '{{ nexpose[scan.status] }}'
|
20
|
+
},
|
21
|
+
full_service: {
|
22
|
+
'Title' => 'Service name: {{ nexpose[service.name] }}',
|
23
|
+
'Fingerprinting' => '{{ nexpose[service.fingerprints] }}',
|
24
|
+
'Configuration' => '{{ nexpose[service.configurations] }}',
|
25
|
+
'Tests' => '{{ nexpose[service.tests] }}'
|
26
|
+
},
|
27
|
+
full_vulnerability: {
|
28
|
+
'Title' => '{{ nexpose[vulnerability.title] }}',
|
29
|
+
'Nexpose Id' => '{{ nexpose[vulnerability.nexpose_id] }}',
|
30
|
+
'Severity' => '{{ nexpose[vulnerability.severity] }}',
|
31
|
+
'PCI Severity' => '{{ nexpose[vulnerability.pci_severity] }}',
|
32
|
+
'CVSS Score' => '{{ nexpose[vulnerability.cvss_score] }}',
|
33
|
+
'CVSS Vector' => '{{ nexpose[vulnerability.cvss_vector] }}',
|
34
|
+
'Published' => '{{ nexpose[vulnerability.published] }}',
|
35
|
+
'Description' => '{{ nexpose[vulnerability.description] }}',
|
36
|
+
'Solution' => '{{ nexpose[vulnerability.solution] }}',
|
37
|
+
'References' => '{{ nexpose[vulnerability.references] }}',
|
38
|
+
'Tags' => '{{ nexpose[vulnerability.tags] }}'
|
39
|
+
},
|
40
|
+
simple_port: {
|
41
|
+
'Id' => '{{ nexpose[port.id] }}',
|
42
|
+
'References' => '{{ nexpose[port.finding] }}'
|
43
|
+
}
|
44
|
+
}.freeze
|
45
|
+
|
46
|
+
SOURCE_FIELDS = {
|
47
|
+
full_evidence: [
|
48
|
+
'evidence.id',
|
49
|
+
'evidence.status',
|
50
|
+
'evidence.content',
|
51
|
+
'evidence.port',
|
52
|
+
'evidence.protocol'
|
53
|
+
],
|
54
|
+
full_node: [
|
55
|
+
'node.address',
|
56
|
+
'node.device_id',
|
57
|
+
'node.fingerprints',
|
58
|
+
'node.hardware_address',
|
59
|
+
'node.names',
|
60
|
+
'node.tests',
|
61
|
+
'node.risk_score',
|
62
|
+
'node.site_name',
|
63
|
+
'node.status',
|
64
|
+
'node.software'
|
65
|
+
],
|
66
|
+
full_scan: [
|
67
|
+
'scan.end_time',
|
68
|
+
'scan.name',
|
69
|
+
'scan.scan_id',
|
70
|
+
'scan.start_time',
|
71
|
+
'scan.status'
|
72
|
+
],
|
73
|
+
full_service: [
|
74
|
+
'service.configurations',
|
75
|
+
'service.fingerprints',
|
76
|
+
'service.name',
|
77
|
+
'service.tests'
|
78
|
+
],
|
79
|
+
full_vulnerability: [
|
80
|
+
'vulnerability.added',
|
81
|
+
'vulnerability.cvss_score',
|
82
|
+
'vulnerability.cvss_vector',
|
83
|
+
'vulnerability.description',
|
84
|
+
'vulnerability.modified',
|
85
|
+
'vulnerability.nexpose_id',
|
86
|
+
'vulnerability.pci_severity',
|
87
|
+
'vulnerability.published',
|
88
|
+
'vulnerability.risk_score',
|
89
|
+
'vulnerability.references',
|
90
|
+
'vulnerability.severity',
|
91
|
+
'vulnerability.solution',
|
92
|
+
'vulnerability.tags',
|
93
|
+
'vulnerability.title'
|
94
|
+
],
|
95
|
+
simple_port: [
|
96
|
+
'port.finding',
|
97
|
+
'port.id'
|
98
|
+
]
|
99
|
+
}.freeze
|
100
|
+
end
|
101
|
+
end
|
data/lib/nexpose/endpoint.rb
CHANGED
@@ -8,7 +8,6 @@ module Nexpose
|
|
8
8
|
# Instead of providing separate methods for each supported property we rely
|
9
9
|
# on Ruby's #method_missing to do most of the work.
|
10
10
|
class Endpoint
|
11
|
-
|
12
11
|
# Accepts an XML node from Nokogiri::XML.
|
13
12
|
def initialize(xml_node)
|
14
13
|
@xml = xml_node
|
@@ -39,13 +38,14 @@ module Nexpose
|
|
39
38
|
# Each of the services associated with this endpoint. Returns an array of
|
40
39
|
# Nexpose::Service objects
|
41
40
|
def services
|
42
|
-
@xml.xpath('./services/service').
|
41
|
+
@xml.xpath('./services/service').map do |xml_service|
|
42
|
+
Service.new(xml_service, endpoint: { port: port, protocol: protocol })
|
43
|
+
end
|
43
44
|
end
|
44
45
|
|
45
|
-
|
46
46
|
# This allows external callers (and specs) to check for implemented
|
47
47
|
# properties
|
48
|
-
def respond_to?(method, include_private=false)
|
48
|
+
def respond_to?(method, include_private = false)
|
49
49
|
return true if supported_tags.include?(method.to_sym)
|
50
50
|
super
|
51
51
|
end
|
@@ -57,7 +57,6 @@ module Nexpose
|
|
57
57
|
# attribute, simple descendent or collection that it maps to in the XML
|
58
58
|
# tree.
|
59
59
|
def method_missing(method, *args)
|
60
|
-
|
61
60
|
# We could remove this check and return nil for any non-recognized tag.
|
62
61
|
# The problem would be that it would make tricky to debug problems with
|
63
62
|
# typos. For instance: <>.potr would return nil instead of raising an
|
@@ -69,8 +68,7 @@ module Nexpose
|
|
69
68
|
|
70
69
|
# First we try the attributes. In Ruby we use snake_case, but in XML
|
71
70
|
# CamelCase is used for some attributes
|
72
|
-
translations_table = {
|
73
|
-
}
|
71
|
+
translations_table = {}
|
74
72
|
|
75
73
|
method_name = translations_table.fetch(method, method.to_s)
|
76
74
|
return @xml.attributes[method_name].value if @xml.attributes.key?(method_name)
|
data/lib/nexpose/node.rb
CHANGED
@@ -43,10 +43,9 @@ module Nexpose
|
|
43
43
|
@xml.xpath('./endpoints/endpoint').collect { |xml_endpoint| Endpoint.new(xml_endpoint) }
|
44
44
|
end
|
45
45
|
|
46
|
-
|
47
46
|
# This allows external callers (and specs) to check for implemented
|
48
47
|
# properties
|
49
|
-
def respond_to?(method, include_private=false)
|
48
|
+
def respond_to?(method, include_private = false)
|
50
49
|
return true if supported_tags.include?(method.to_sym)
|
51
50
|
super
|
52
51
|
end
|
@@ -58,7 +57,6 @@ module Nexpose
|
|
58
57
|
# attribute, simple descendent or collection that it maps to in the XML
|
59
58
|
# tree.
|
60
59
|
def method_missing(method, *args)
|
61
|
-
|
62
60
|
# We could remove this check and return nil for any non-recognized tag.
|
63
61
|
# The problem would be that it would make tricky to debug problems with
|
64
62
|
# typos. For instance: <>.potr would return nil instead of raising an
|
@@ -84,7 +82,7 @@ module Nexpose
|
|
84
82
|
|
85
83
|
# Finally the enumerations: names
|
86
84
|
if method_name == 'names'
|
87
|
-
@xml.xpath(
|
85
|
+
@xml.xpath('./names/name').collect(&:text)
|
88
86
|
|
89
87
|
elsif ['fingerprints', 'software'].include?(method_name)
|
90
88
|
|
@@ -93,14 +91,10 @@ module Nexpose
|
|
93
91
|
'software' => './software/fingerprint'
|
94
92
|
}[method_name]
|
95
93
|
|
96
|
-
@xml.
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
[name.sub(/-/,'_').to_sym, xml_attribute.value]
|
101
|
-
end
|
102
|
-
]
|
103
|
-
end
|
94
|
+
xml_os = @xml.at_xpath(xpath_selector)
|
95
|
+
return '' if xml_os.nil?
|
96
|
+
|
97
|
+
xml_os.attributes['product'].value
|
104
98
|
else
|
105
99
|
# nothing found, the tag is valid but not present in this ReportItem
|
106
100
|
return nil
|
data/lib/nexpose/service.rb
CHANGED
@@ -8,9 +8,15 @@ module Nexpose
|
|
8
8
|
# Instead of providing separate methods for each supported property we rely
|
9
9
|
# on Ruby's #method_missing to do most of the work.
|
10
10
|
class Service
|
11
|
+
attr_accessor :endpoint, :xml
|
12
|
+
|
11
13
|
# Accepts an XML node from Nokogiri::XML.
|
12
|
-
|
14
|
+
#
|
15
|
+
# endpoint - If the Service is instantiated from the Endpoint class (e.g.
|
16
|
+
# from <endpoint><services>...) , it will have access to the parent data.
|
17
|
+
def initialize(xml_node, endpoint: nil)
|
13
18
|
@xml = xml_node
|
19
|
+
@endpoint = endpoint
|
14
20
|
end
|
15
21
|
|
16
22
|
# List of supported tags. They can be attributes, simple descendans or
|
@@ -29,15 +35,18 @@ module Nexpose
|
|
29
35
|
|
30
36
|
# Convert each ./test/test entry into a simple hash
|
31
37
|
def tests(*args)
|
32
|
-
|
38
|
+
xml.xpath('./tests/test').map do |xml_test|
|
39
|
+
# Inject evidence with data from the node
|
40
|
+
xml_test['port'] = endpoint[:port]
|
41
|
+
xml_test['protocol'] = endpoint[:protocol]
|
42
|
+
|
33
43
|
Nexpose::Test.new(xml_test)
|
34
44
|
end
|
35
45
|
end
|
36
46
|
|
37
|
-
|
38
47
|
# This allows external callers (and specs) to check for implemented
|
39
48
|
# properties
|
40
|
-
def respond_to?(method, include_private=false)
|
49
|
+
def respond_to?(method, include_private = false)
|
41
50
|
return true if supported_tags.include?(method.to_sym)
|
42
51
|
super
|
43
52
|
end
|
@@ -49,7 +58,6 @@ module Nexpose
|
|
49
58
|
# attribute, simple descendent or collection that it maps to in the XML
|
50
59
|
# tree.
|
51
60
|
def method_missing(method, *args)
|
52
|
-
|
53
61
|
# We could remove this check and return nil for any non-recognized tag.
|
54
62
|
# The problem would be that it would make tricky to debug problems with
|
55
63
|
# typos. For instance: <>.potr would return nil instead of raising an
|
@@ -61,11 +69,10 @@ module Nexpose
|
|
61
69
|
|
62
70
|
# First we try the attributes. In Ruby we use snake_case, but in XML
|
63
71
|
# CamelCase is used for some attributes
|
64
|
-
translations_table = {
|
65
|
-
}
|
72
|
+
translations_table = {}
|
66
73
|
|
67
74
|
method_name = translations_table.fetch(method, method.to_s)
|
68
|
-
return
|
75
|
+
return xml.attributes[method_name].value if xml.attributes.key?(method_name)
|
69
76
|
|
70
77
|
# Finally the enumerations: references, tags
|
71
78
|
if ['fingerprints', 'configurations'].include?(method_name)
|
@@ -74,11 +81,11 @@ module Nexpose
|
|
74
81
|
'configurations' => './configuration/config'
|
75
82
|
}[method_name]
|
76
83
|
|
77
|
-
|
78
|
-
{:
|
84
|
+
xml.xpath(xpath_selector).collect do |xml_item|
|
85
|
+
{ text: xml_item.text }.merge(
|
79
86
|
Hash[
|
80
87
|
xml_item.attributes.collect do |name, xml_attribute|
|
81
|
-
[name.sub(/-/,'_').to_sym, xml_attribute.value]
|
88
|
+
[name.sub(/-/, '_').to_sym, xml_attribute.value]
|
82
89
|
end
|
83
90
|
]
|
84
91
|
)
|
data/lib/nexpose/test.rb
CHANGED
@@ -2,13 +2,20 @@ module Nexpose
|
|
2
2
|
class Test
|
3
3
|
def self.new(xml_node)
|
4
4
|
content =
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
# get first Paragraph or ContainerBlockElement that's a direct child of <test>
|
6
|
+
if xml = xml_node.at_xpath('./Paragraph | ./ContainerBlockElement')
|
7
|
+
# get all nested paragraph elements
|
8
|
+
nested_paragraphs = xml.xpath('.//Paragraph')
|
9
|
+
|
10
|
+
content = nested_paragraphs.children.map do |node|
|
11
|
+
case node.name
|
12
|
+
when 'text'
|
13
|
+
node.text.strip
|
14
|
+
when 'URLLink'
|
15
|
+
node['LinkURL']
|
16
|
+
end
|
17
|
+
end.compact
|
18
|
+
content.map(&:strip).reject(&:empty?).join("\n")
|
12
19
|
else
|
13
20
|
'n/a'
|
14
21
|
end
|
@@ -16,7 +23,9 @@ module Nexpose
|
|
16
23
|
{
|
17
24
|
id: xml_node.attributes['id'],
|
18
25
|
status: xml_node.attributes['status'],
|
19
|
-
content: content
|
26
|
+
content: content,
|
27
|
+
port: xml_node.attributes['port'],
|
28
|
+
protocol: xml_node.attributes['protocol']
|
20
29
|
}
|
21
30
|
end
|
22
31
|
end
|
@@ -20,8 +20,8 @@ module Nexpose
|
|
20
20
|
def supported_tags
|
21
21
|
[
|
22
22
|
# attributes
|
23
|
-
:
|
24
|
-
:published, :
|
23
|
+
:added, :cvss_score, :cvss_vector, :modified, :nexpose_id, :pci_severity,
|
24
|
+
:published, :risk_score, :severity, :title,
|
25
25
|
|
26
26
|
# simple tags
|
27
27
|
:description, :solution,
|
@@ -64,6 +64,7 @@ module Nexpose
|
|
64
64
|
translations_table = {
|
65
65
|
:nexpose_id => 'id',
|
66
66
|
:pci_severity => 'pciSeverity',
|
67
|
+
:risk_score => 'riskScore',
|
67
68
|
:cvss_score => 'cvssScore',
|
68
69
|
:cvss_vector =>'cvssVector'
|
69
70
|
}
|