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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b11c2f3efeb75d866e704afc1654be7901c80233d4c25ecf3796170c6217dd12
4
- data.tar.gz: 18e1f93496c8badc29df962c48a7285d56ad5a5b5d3e3faa366f63e001081178
3
+ metadata.gz: b5b342a73c3a4576df7bd68d24142393480c9abed09cd8f7a8c2f9aefe67339e
4
+ data.tar.gz: 4d24a2ed95a57a001e9f909740fd95067332065804d282598c2b86d31db6b644
5
5
  SHA512:
6
- metadata.gz: bafe0d744722c9dfb00170857fea3bad6c5f8cd2fcc571867ac0bfb86639f0dc700ea58134ab6f05556aa750dd51072b506a411452f4c7b796955b331916b57f
7
- data.tar.gz: 3dc33c2ba56abe1ba7ce2d715cb63826b51d140fa702be9543078a803b6a5a0665b582a695a7e24c933957e3e413b27e1cc6b8a2170b392ba8e44bc89231eeaa
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://dradisframework.org/) > 3.0, or [Dradis Pro](https://dradisframework.com/pro/).
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/dradisframework/blob/master/README.md)
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/dradisframework/blob/master/CONTRIBUTING.md)
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 = template_service.process_template(template: 'full_scan', data: xml_scan)
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 = template_service.process_template(template: 'full_node', data: nexpose_node)
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.software)
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("<hosts/>") unless xml_vuln.last_element_child.name == "hosts"
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] = node_test
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 = template_service.process_template(template: 'full_service', data: service)
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("<hosts/>") unless xml_vuln.last_element_child.name == "hosts"
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] = service_test
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
- issue_text = template_service.process_template(
127
- template: '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
-
145
- # 3.2 associate with the nodes via Evidence.
146
- # TODO: there is room for improvement here by providing proper Evidence content
147
- xml_vulnerability.xpath('./hosts/host').collect(&:text).each do |host_name|
148
- # if the node exists, this just returns it
149
- host_node = content_service.create_node(label: host_name, type: :host)
150
-
151
- evidence_content = template_service.process_template(
152
- template: 'full_evidence',
153
- data: evidence[id][host_name]
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 = template_service.process_template(template: 'simple_port', data: {id: id, finding: finding})
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
@@ -8,11 +8,11 @@ module Dradis
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 4
11
- MINOR = 10
11
+ MINOR = 12
12
12
  TINY = 0
13
13
  PRE = nil
14
14
 
15
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
16
16
  end
17
17
  end
18
18
  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
@@ -8,4 +8,5 @@ end
8
8
  require 'dradis/plugins/nexpose/engine'
9
9
  require 'dradis/plugins/nexpose/field_processor'
10
10
  require 'dradis/plugins/nexpose/importer'
11
+ require 'dradis/plugins/nexpose/mapping'
11
12
  require 'dradis/plugins/nexpose/version'
@@ -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').collect { |xml_service| Service.new(xml_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("./names/name").collect(&:text)
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.xpath(xpath_selector).collect do |xml_os|
97
- Hash[
98
- xml_os.attributes.collect do |name, xml_attribute|
99
- next if name == 'arch'
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
@@ -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
- def initialize(xml_node)
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
- @xml.xpath('./tests/test').map do |xml_test|
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 @xml.attributes[method_name].value if @xml.attributes.key?(method_name)
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
- @xml.xpath(xpath_selector).collect do |xml_item|
78
- {:text => xml_item.text}.merge(
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
- if xml_node.at_xpath('./Paragraph')
6
- xml_node.
7
- at_xpath('./Paragraph').
8
- text.
9
- split("\n").
10
- collect(&:strip).
11
- reject { |line| line.empty? }.join("\n")
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
- :nexpose_id, :title, :severity, :pci_severity, :cvss_score, :cvss_vector,
24
- :published, :added, :modified,
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
  }