dradis-nexpose 4.10.0 → 4.11.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: fefa5d899cb4a34cead8f685c4bd13efdea29d034ddb7a505ce0e00f23b9b17d
4
+ data.tar.gz: 713ae33f6884e7c75393055f4cf68932a4ec9ef310e722d333b34c83df8b65ba
5
5
  SHA512:
6
- metadata.gz: bafe0d744722c9dfb00170857fea3bad6c5f8cd2fcc571867ac0bfb86639f0dc700ea58134ab6f05556aa750dd51072b506a411452f4c7b796955b331916b57f
7
- data.tar.gz: 3dc33c2ba56abe1ba7ce2d715cb63826b51d140fa702be9543078a803b6a5a0665b582a695a7e24c933957e3e413b27e1cc6b8a2170b392ba8e44bc89231eeaa
6
+ metadata.gz: 7a07af401d0dbe92e6d488244502eae76d2404482429190b8e7a68117c97e79e850905b0ab71954024137e9144125de3169866ef2749343c9b0d909c7e3027e1
7
+ data.tar.gz: 69b31e47e60b9ee6bbd6771222ea1a8f5940bf9d40c6700e789bee6a546a2b296d087b9a75fc850c32f827c9ac917bd22cbc16ebffed2ed6449f5ac8ca77023b
@@ -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,9 @@
1
+ v4.11.0 (January 2024)
2
+ - Add port/protocol to evidences
3
+ - Use the details in <os> as the OS node property
4
+ - Import `vulnerability.risk_score` as a new Issue field
5
+ - Allow multiple evidence with the same test id & node address
6
+
1
7
  v4.10.0 (September 2023)
2
8
  - Update gemspec links
3
9
 
data/README.md CHANGED
@@ -11,12 +11,12 @@ The add-on requires [Dradis CE](https://dradisframework.org/) > 3.0, or [Dradis
11
11
 
12
12
  ## More information
13
13
 
14
- See the Dradis Framework's [README.md](https://github.com/dradis/dradisframework/blob/master/README.md)
14
+ See the Dradis Framework's [README.md](https://github.com/dradis/dradis-ce/blob/develop/README.md)
15
15
 
16
16
 
17
17
  ## Contributing
18
18
 
19
- See the Dradis Framework's [CONTRIBUTING.md](https://github.com/dradis/dradisframework/blob/master/CONTRIBUTING.md)
19
+ See the Dradis Framework's [CONTRIBUTING.md](https://github.com/dradis/dradis-ce/blob/develop/CONTRIBUTING.md)
20
20
 
21
21
 
22
22
  ## 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
17
  note_text = template_service.process_template(template: '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
29
  note_text = template_service.process_template(template: '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,
@@ -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)
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}" }
150
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
151
  evidence_content = template_service.process_template(
152
152
  template: 'full_evidence',
153
- data: evidence[id][host_name]
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
@@ -8,11 +8,11 @@ module Dradis
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 4
11
- MINOR = 10
11
+ MINOR = 11
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
@@ -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
@@ -16,7 +16,9 @@ module Nexpose
16
16
  {
17
17
  id: xml_node.attributes['id'],
18
18
  status: xml_node.attributes['status'],
19
- content: content
19
+ content: content,
20
+ port: xml_node.attributes['port'],
21
+ protocol: xml_node.attributes['protocol']
20
22
  }
21
23
  end
22
24
  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
  }
@@ -9,7 +9,7 @@
9
9
  <name>localhost:5000</name>
10
10
  </names>
11
11
  <fingerprints>
12
- <os certainty="0.80" family="IOS" product="IOS" vendor="Cisco"/>
12
+ <os certainty="0.80" family="IOS" product="IOS" vendor="Cisco" arch="x86_64"/>
13
13
  </fingerprints>
14
14
  <tests/>
15
15
  <endpoints>
@@ -0,0 +1,136 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <NexposeReport version="2.0">
3
+ <scans>
4
+ <scan endTime="20141110T175832478" id="4" name="USDA_Internal" startTime="20141110T094538362" status="finished"/>
5
+ </scans>
6
+ <nodes>
7
+ <node address="1.1.1.1" device-id="75" risk-score="0.0" scan-template="Edge Standard" site-importance="Normal" site-name="USDA_Internal" status="alive">
8
+ <names>
9
+ <name>localhost:5000</name>
10
+ </names>
11
+ <fingerprints>
12
+ <os certainty="0.80" family="IOS" product="IOS" vendor="Cisco" arch="x86_64"/>
13
+ </fingerprints>
14
+ <tests/>
15
+ <endpoints>
16
+ <endpoint port="123" protocol="udp" status="open">
17
+ <services>
18
+ <service name="NTP">
19
+ <fingerprints>
20
+ <fingerprint certainty="0.90" family="NTP" product="NTP" vendor="Cisco"/>
21
+ </fingerprints>
22
+ <configuration>
23
+ <config name="ntp.variables">system=&quot;cisco&quot;, leap=0, stratum=5, rootdelay=88.21,
24
+
25
+ rootdispersion=108.54, peer=24960, refid=135.89.100.96,
26
+
27
+ reftime=0xD80BB6B5.715ACDD8, poll=10, clock=0xD80BB78F.8931F3F6,
28
+
29
+ phase=8.259, freq=-141.24, error=11.32</config>
30
+ </configuration>
31
+ <tests>
32
+ <test id="ntp-clock-variables-disclosure" pci-compliance-status="pass" scan-id="4" status="vulnerable-exploited" vulnerable-since="20141110T161846666">
33
+ <Paragraph>
34
+ <Paragraph>The following NTP variables were found from a readvar request: system=&quot;cisco&quot;, leap=0, stratum=5, rootdelay=88.21,
35
+ rootdispersion=108.54, peer=24960, refid=135.89.100.96,
36
+ reftime=0xD80BB6B5.715ACDD8, poll=10, clock=0xD80BB78F.8931F3F6,
37
+ phase=8.259, freq=-141.24, error=11.32</Paragraph>
38
+ </Paragraph>
39
+ </test>
40
+ </tests>
41
+ </service>
42
+ </services>
43
+ </endpoint>
44
+ <endpoint port="161" protocol="udp" status="open">
45
+ <services>
46
+ <service name="SNMP">
47
+ <tests/>
48
+ </service>
49
+ </services>
50
+ </endpoint>
51
+ </endpoints>
52
+ </node>
53
+ <node address="1.1.1.1" device-id="75" risk-score="0.0" scan-template="Edge Standard" site-importance="Normal" site-name="USDA_Internal" status="alive">
54
+ <names>
55
+ <name>localhost:6000</name>
56
+ </names>
57
+ <fingerprints>
58
+ <os certainty="0.80" family="IOS" product="IOS" vendor="Cisco" arch="x86_64"/>
59
+ </fingerprints>
60
+ <tests/>
61
+ <endpoints>
62
+ <endpoint port="123" protocol="udp" status="open">
63
+ <services>
64
+ <service name="NTP">
65
+ <fingerprints>
66
+ <fingerprint certainty="0.90" family="NTP" product="NTP" vendor="Cisco"/>
67
+ </fingerprints>
68
+ <configuration>
69
+ <config name="ntp.variables">system=&quot;cisco&quot;, leap=0, stratum=5, rootdelay=88.21,
70
+
71
+ rootdispersion=108.54, peer=24960, refid=135.89.100.96,
72
+
73
+ reftime=0xD80BB6B5.715ACDD8, poll=10, clock=0xD80BB78F.8931F3F6,
74
+
75
+ phase=8.259, freq=-141.24, error=11.32</config>
76
+ </configuration>
77
+ <tests>
78
+ <test id="ntp-clock-variables-disclosure" pci-compliance-status="pass" scan-id="4" status="vulnerable-exploited" vulnerable-since="20141110T161846666">
79
+ <Paragraph>
80
+ <Paragraph>The following NTP variables were found from a readvar request: system=&quot;cisco&quot;, leap=0, stratum=5, rootdelay=88.21,
81
+ rootdispersion=108.54, peer=24960, refid=135.89.100.96,
82
+ reftime=0xD80BB6B5.715ACDD8, poll=10, clock=0xD80BB78F.8931F3F6,
83
+ phase=8.259, freq=-141.24, error=11.32</Paragraph>
84
+ </Paragraph>
85
+ </test>
86
+ </tests>
87
+ </service>
88
+ </services>
89
+ </endpoint>
90
+ <endpoint port="161" protocol="udp" status="open">
91
+ <services>
92
+ <service name="SNMP">
93
+ <tests/>
94
+ </service>
95
+ </services>
96
+ </endpoint>
97
+ </endpoints>
98
+ </node>
99
+ </nodes>
100
+ <VulnerabilityDefinitions>
101
+ <vulnerability added="20120412T000000000" cvssScore="4.3" cvssVector="(AV:N/AC:M/Au:N/C:P/I:N/A:N)" id="ntp-clock-variables-disclosure" modified="20131205T000000000" pciSeverity="3" published="20120127T000000000" riskScore="378.27377" severity="4" title="Apache HTTPD: error responses can expose cookies (CVE-2012-0053)">
102
+ <malware/>
103
+ <exploits>
104
+ <exploit id="3479" link="http://www.exploit-db.com/exploits/18442" skillLevel="Expert" title="Apache httpOnly Cookie Disclosure" type="exploitdb"/>
105
+ </exploits>
106
+ <description>
107
+ <ContainerBlockElement>
108
+ <Paragraph>A flaw was found in the default error response for status code 400. This flaw could be used by an attacker to expose &quot;httpOnly&quot; cookies when no custom ErrorDocument is specified.</Paragraph>
109
+ </ContainerBlockElement>
110
+ </description>
111
+ <references>
112
+ <reference source="APPLE">APPLE-SA-2012-09-19-2</reference>
113
+ <reference source="BID">51706</reference>
114
+ <reference source="CVE">CVE-2012-0053</reference>
115
+ <reference source="REDHAT">RHSA-2012:0128</reference>
116
+ <reference source="SECUNIA">48551</reference>
117
+ <reference source="URL">http://httpd.apache.org/security/vulnerabilities_20.html</reference>
118
+ <reference source="URL">http://httpd.apache.org/security/vulnerabilities_22.html</reference>
119
+ </references>
120
+ <tags>
121
+ <tag>Apache</tag>
122
+ <tag>Apache HTTP Server</tag>
123
+ <tag>Web</tag>
124
+ </tags>
125
+ <solution>
126
+ <ContainerBlockElement>
127
+ <Paragraph>Apache HTTPD &gt;= 2.0 and &lt; 2.0.65</Paragraph>
128
+ <Paragraph>Download and apply the upgrade from:
129
+
130
+ <URLLink LinkTitle="http://archive.apache.org/dist/httpd/httpd-2.0.65.tar.gz" LinkURL="http://archive.apache.org/dist/httpd/httpd-2.0.65.tar.gz"/></Paragraph>
131
+ <Paragraph>Many platforms and distributions provide pre-built binary packages for Apache HTTP server. These pre-built packages are usually customized and optimized for a particular distribution, therefore we recommend that you use the packages if they are available for your operating system.</Paragraph>
132
+ </ContainerBlockElement>
133
+ </solution>
134
+ </vulnerability>
135
+ </VulnerabilityDefinitions>
136
+ </NexposeReport>
@@ -1,170 +1,201 @@
1
- require 'spec_helper'
1
+ require 'rails_helper'
2
2
  require 'ostruct'
3
3
 
4
4
  describe 'Nexpose upload plugin' do
5
- before(:each) do
6
- # Stub template service
7
- templates_dir = File.expand_path('../../templates', __FILE__)
8
- expect_any_instance_of(Dradis::Plugins::TemplateService)
9
- .to receive(:default_templates_dir).and_return(templates_dir)
10
-
11
- # Init services
12
- plugin = Dradis::Plugins::Nexpose
13
-
14
- @content_service = Dradis::Plugins::ContentService::Base.new(
15
- logger: Logger.new(STDOUT),
16
- plugin: plugin
17
- )
18
-
19
- @importer = plugin::Importer.new(
20
- content_service: @content_service,
21
- )
22
-
23
- # Stub dradis-plugins methods
24
- #
25
- # They return their argument hashes as objects mimicking
26
- # Nodes, Issues, etc
27
- allow(@content_service).to receive(:create_node) do |args|
28
- OpenStruct.new(args)
29
- end
30
- allow(@content_service).to receive(:create_note) do |args|
31
- OpenStruct.new(args)
32
- end
33
- allow(@content_service).to receive(:create_issue) do |args|
34
- OpenStruct.new(args)
35
- end
36
- allow(@content_service).to receive(:create_evidence) do |args|
37
- OpenStruct.new(args)
38
- end
5
+ before do
6
+ @fixtures_dir = File.expand_path('../fixtures/files/', __FILE__)
39
7
  end
40
8
 
41
- describe "Importer: Simple" do
42
- it "creates nodes, issues, notes and an evidences as needed" do
43
-
44
- expect(@content_service).to receive(:create_node).with(hash_including label: '1.1.1.1', type: :host).once
45
-
46
- expect(@content_service).to receive(:create_note) do |args|
47
- expect(args[:text]).to include("Host Description : Linux 2.6.9-89.ELsmp")
48
- expect(args[:text]).to include("Scanner Fingerprint certainty : 0.80")
49
- expect(args[:node].label).to eq("1.1.1.1")
50
- end.once
51
-
52
- expect(@content_service).to receive(:create_node) do |args|
53
- expect(args[:label]).to eq('Generic Findings')
54
- expect(args[:parent].label).to eq("1.1.1.1")
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|
55
33
  OpenStruct.new(args)
56
- end.once
57
-
58
- expect(@content_service).to receive(:create_node) do |args|
59
- expect(args[:label]).to eq('udp-000')
60
- expect(args[:parent].label).to eq("1.1.1.1")
34
+ end
35
+ allow(@content_service).to receive(:create_note) do |args|
61
36
  OpenStruct.new(args)
62
- end.once
63
-
64
- expect(@content_service).to receive(:create_note) do |args|
65
- expect(args[:text]).to include("#[Id]#\nntpd-crypto")
66
- expect(args[:text]).to include("#[host]#\n1.1.1.1")
67
- expect(args[:node].label).to eq("udp-000")
68
- end.once
69
-
70
- expect(@content_service).to receive(:create_note) do |args|
71
- expect(args[:text]).to include("#[Id]#\nntp-clock-radio")
72
- expect(args[:text]).to include("#[host]#\n1.1.1.1")
73
- expect(args[:node].label).to eq("udp-000")
74
- end.once
75
-
76
- @importer.import(file: 'spec/fixtures/files/simple.xml')
77
- end
78
- end
79
-
80
- describe "Importer: Full" do
81
- it "creates nodes, issues, notes and an evidences as needed" do
82
- expect(@content_service).to receive(:create_node).with(hash_including label: "Nexpose Scan Summary").once
83
- expect(@content_service).to receive(:create_note) do |args|
84
- expect(args[:text]).to include("#[Title]#\nUSDA_Internal (4)")
85
- expect(args[:node].label).to eq("Nexpose Scan Summary")
86
- end.once
87
-
88
- expect(@content_service).to receive(:create_node).with(
89
- hash_including label: "1.1.1.1", type: :host
90
- ).twice
91
-
92
- expect(@content_service).to receive(:create_note) do |args|
93
- expect(args[:text]).to include("#[Title]#\n1.1.1.1")
94
- expect(args[:node].label).to eq("1.1.1.1")
95
- end.once
96
-
97
- expect(@content_service).to receive(:create_note) do |args|
98
- expect(args[:text]).to include("#[Title]#\nService name: NTP")
99
- expect(args[:node].label).to eq("1.1.1.1")
100
- end.once
101
-
102
- expect(@content_service).to receive(:create_note) do |args|
103
- expect(args[:text]).to include("#[Title]#\nService name: SNMP")
104
- expect(args[:node].label).to eq("1.1.1.1")
105
- end.once
106
-
107
- expect(@content_service).to receive(:create_issue) do |args|
108
- expect(args[:text]).to include("#[Title]#\nApache HTTPD: error responses can expose cookies (CVE-2012-0053)")
109
- expect(args[:id]).to eq("ntp-clock-variables-disclosure")
37
+ end
38
+ allow(@content_service).to receive(:create_issue) do |args|
110
39
  OpenStruct.new(args)
111
- end.once
112
-
113
- expect(@content_service).to receive(:create_issue) do |args|
114
- expect(args[:text]).to include("#[Title]#\nApache HTTPD: ETag Inode Information Leakage (CVE-2003-1418)")
115
- expect(args[:id]).to eq("ntp-clock-variables-disclosure")
40
+ end
41
+ allow(@content_service).to receive(:create_evidence) do |args|
116
42
  OpenStruct.new(args)
117
- end.once
43
+ end
44
+ end
118
45
 
119
- expect(@content_service).to receive(:create_evidence) do |args|
120
- expect(args[:content]).to include("#[ID]#\nntp-clock-variables-disclosure\n\n")
121
- expect(args[:issue].id).to eq("ntp-clock-variables-disclosure")
122
- expect(args[:node].label).to eq("1.1.1.1")
123
- end.once
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
124
84
 
125
- allow_any_instance_of(OpenStruct).to receive(:respond_to?).with(:properties).and_return(true)
126
- allow_any_instance_of(OpenStruct).to receive(:set_service).and_return(true)
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('ntp-clock-variables-disclosure')
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
+ @importer.import(file: @fixtures_dir + '/full.xml')
133
+
134
+ expect(Node.find_by(label: '1.1.1.1').properties[:os]).to eq('IOS')
135
+ end
127
136
 
128
- expect_any_instance_of(OpenStruct).to receive(:set_property).with(:hostname, ['localhost:5000'])
129
- expect_any_instance_of(OpenStruct).to receive(:set_property).with(:ip, '1.1.1.1')
130
- expect_any_instance_of(OpenStruct).to receive(:set_property).with(:os, [])
131
- expect_any_instance_of(OpenStruct).to receive(:set_property).with(:risk_score, '0.0')
137
+ it 'wraps ciphers inside ssl issues in code blocks' do
138
+ expect(@content_service).to receive(:create_issue) do |args|
139
+ expect(args[:text]).to include('bc. ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256')
140
+ OpenStruct.new(args)
141
+ end.once
132
142
 
133
- @importer.import(file: 'spec/fixtures/files/full.xml')
134
- end
143
+ @importer.import(file: @fixtures_dir + '/ssl.xml')
144
+ end
135
145
 
136
- it "wraps ciphers inside ssl issues in code blocks" do
137
- expect(@content_service).to receive(:create_issue) do |args|
138
- expect(args[:text]).to include("bc. ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256")
139
- OpenStruct.new(args)
140
- end.once
146
+ # Regression test for github.com/dradis/dradis-nexpose/issues/1
147
+ it 'populates solutions regardless they are wrapped in paragraphs or lists' do
148
+ expect(@content_service).to receive(:create_issue) do |args|
149
+ expect(args[:text]).to include("#[Solution]#\n\nApache HTTPD >= 2.0 and < 2.0.65")
150
+ OpenStruct.new(args)
151
+ end.once
141
152
 
142
- @importer.import(file: 'spec/fixtures/files/ssl.xml')
143
- end
153
+ expect(@content_service).to receive(:create_issue) do |args|
154
+ expect(args[:text]).to include("#[Solution]#\n")
155
+ expect(args[:text]).to include('You can remove inode information from the ETag header')
156
+ OpenStruct.new(args)
157
+ end.once
144
158
 
145
- # Regression test for github.com/dradis/dradis-nexpose/issues/1
146
- it "populates solutions regardless they are wrapped in paragraphs or lists" do
147
- expect(@content_service).to receive(:create_issue) do |args|
148
- expect(args[:text]).to include("#[Solution]#\n\nApache HTTPD >= 2.0 and < 2.0.65")
149
- OpenStruct.new(args)
150
- end.once
159
+ @importer.import(file: @fixtures_dir + '/full.xml')
160
+ end
151
161
 
152
- expect(@content_service).to receive(:create_issue) do |args|
153
- expect(args[:text]).to include("#[Solution]#\n")
154
- expect(args[:text]).to include("You can remove inode information from the ETag header")
155
- OpenStruct.new(args)
156
- end.once
162
+ it 'transforms html entities (&lt; and &gt;)' do
163
+ expect(@content_service).to receive(:create_issue) do |args|
164
+ expect(args[:text]).to include("#[Solution]#\n\nApache HTTPD >= 2.0 and < 2.0.65")
165
+ OpenStruct.new(args)
166
+ end
157
167
 
158
- @importer.import(file: 'spec/fixtures/files/full.xml')
168
+ @importer.import(file: @fixtures_dir + '/full.xml')
169
+ end
159
170
  end
160
171
 
161
- it "transforms html entities (&lt; and &gt;)" do
162
- expect(@content_service).to receive(:create_issue) do |args|
163
- expect(args[:text]).to include("#[Solution]#\n\nApache HTTPD >= 2.0 and < 2.0.65")
164
- OpenStruct.new(args)
172
+ describe 'Importer: Full with duplicate nodes' do
173
+ it 'creates evidence for each instance of the node' do
174
+ expect(@content_service).to receive(:create_node).with(hash_including label: 'Nexpose Scan Summary').once
175
+ expect(@content_service).to receive(:create_node) do |args|
176
+ expect(args[:label]).to eq('1.1.1.1')
177
+ expect(args[:type]).to eq(:host)
178
+ create(:node, args.except(:type))
179
+ end
180
+
181
+ expect(@content_service).to receive(:create_evidence) do |args|
182
+ expect(args[:content]).to include("#[ID]#\nntp-clock-variables-disclosure\n\n")
183
+ expect(args[:issue].id).to eq('ntp-clock-variables-disclosure')
184
+ expect(args[:node].label).to eq('1.1.1.1')
185
+ end.twice
186
+
187
+ @importer.import(file: @fixtures_dir + '/full_with_duplicate_node.xml')
165
188
  end
166
-
167
- @importer.import(file: 'spec/fixtures/files/full.xml')
168
189
  end
169
190
  end
191
+
192
+ it 'parses the fingerprints field' do
193
+ doc = Nokogiri::XML(File.read(@fixtures_dir + '/full.xml'))
194
+
195
+ ts = Dradis::Plugins::TemplateService.new(plugin: Dradis::Plugins::Nexpose)
196
+ ts.set_template(template: 'full_node', content: "#[Fingerprints]#\n%node.fingerprints%\n")
197
+ result = ts.process_template(data: doc.at_xpath('//nodes/node'), template: 'full_node')
198
+
199
+ expect(result).to include('IOS')
200
+ end
170
201
  end
@@ -1,3 +1,5 @@
1
1
  evidence.id
2
2
  evidence.status
3
3
  evidence.content
4
+ evidence.port
5
+ evidence.protocol
@@ -1,4 +1,4 @@
1
- <test id="http-coldfusion-cfide-unprotected" key="/CFIDE/adminapi/base.cfc?wsdl" status="vulnerable-exploited" scan-id="4" vulnerable-since="20141110T165124356" pci-compliance-status="fail">
1
+ <test id="http-coldfusion-cfide-unprotected" key="/CFIDE/adminapi/base.cfc?wsdl" status="vulnerable-exploited" scan-id="4" vulnerable-since="20141110T165124356" pci-compliance-status="fail" port="123" protocol="udp">
2
2
  <Paragraph>
3
3
  <UnorderedList>
4
4
  <ListItem>Running HTTP service</ListItem>
@@ -1,4 +1,4 @@
1
1
  service.configurations
2
2
  service.fingerprints
3
3
  service.name
4
- service.tests
4
+ service.tests
@@ -14,4 +14,4 @@
14
14
 
15
15
  <tests>
16
16
  </tests>
17
- </service>
17
+ </service>
@@ -6,6 +6,7 @@ vulnerability.modified
6
6
  vulnerability.nexpose_id
7
7
  vulnerability.pci_severity
8
8
  vulnerability.published
9
+ vulnerability.risk_score
9
10
  vulnerability.references
10
11
  vulnerability.severity
11
12
  vulnerability.solution
@@ -6,6 +6,7 @@
6
6
  cvssScore="7.5"
7
7
  cvssVector="(AV:N/AC:L/Au:N/C:P/I:P/A:P)"
8
8
  published="19970101T000000000"
9
+ riskScore="123.4567"
9
10
  added="20041101T000000000"
10
11
  modified="20111117T000000000">
11
12
 
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.10.0
4
+ version: 4.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Martin
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-07 00:00:00.000000000 Z
11
+ date: 2024-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dradis-plugins
@@ -96,7 +96,7 @@ dependencies:
96
96
  version: 0.5.2
97
97
  description: This add-on allows you to upload and parse output produced from Nexpose
98
98
  scanner into Dradis.
99
- email:
99
+ email:
100
100
  executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
@@ -130,6 +130,7 @@ files:
130
130
  - lib/nexpose/vulnerability.rb
131
131
  - lib/tasks/thorfile.rb
132
132
  - spec/fixtures/files/full.xml
133
+ - spec/fixtures/files/full_with_duplicate_node.xml
133
134
  - spec/fixtures/files/simple.xml
134
135
  - spec/fixtures/files/ssl.xml
135
136
  - spec/nexpose_upload_spec.rb
@@ -156,7 +157,7 @@ homepage: https://dradis.com/integrations/nexpose.html
156
157
  licenses:
157
158
  - GPL-2
158
159
  metadata: {}
159
- post_install_message:
160
+ post_install_message:
160
161
  rdoc_options: []
161
162
  require_paths:
162
163
  - lib
@@ -171,12 +172,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
171
172
  - !ruby/object:Gem::Version
172
173
  version: '0'
173
174
  requirements: []
174
- rubygems_version: 3.1.4
175
- signing_key:
175
+ rubygems_version: 3.3.7
176
+ signing_key:
176
177
  specification_version: 4
177
178
  summary: Nexpose add-on for the Dradis Framework.
178
179
  test_files:
179
180
  - spec/fixtures/files/full.xml
181
+ - spec/fixtures/files/full_with_duplicate_node.xml
180
182
  - spec/fixtures/files/simple.xml
181
183
  - spec/fixtures/files/ssl.xml
182
184
  - spec/nexpose_upload_spec.rb