dradis-openvas 3.6.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 +7 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +23 -0
- data/Guardfile +8 -0
- data/LICENSE +339 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/dradis-openvas.gemspec +32 -0
- data/lib/dradis-openvas.rb +8 -0
- data/lib/dradis/plugins/openvas.rb +11 -0
- data/lib/dradis/plugins/openvas/engine.rb +9 -0
- data/lib/dradis/plugins/openvas/field_processor.rb +42 -0
- data/lib/dradis/plugins/openvas/gem_version.rb +19 -0
- data/lib/dradis/plugins/openvas/importer.rb +101 -0
- data/lib/dradis/plugins/openvas/version.rb +13 -0
- data/lib/openvas/result.rb +163 -0
- data/lib/openvas/v6/result.rb +12 -0
- data/lib/openvas/v7/result.rb +61 -0
- data/lib/tasks/thorfile.rb +26 -0
- data/spec/fixtures/files/result.xml +48 -0
- data/spec/fixtures/files/result2.xml +68 -0
- data/spec/fixtures/files/v7/report_v7.xml +427 -0
- data/spec/openvas/result_spec.rb +35 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/fixture_loader.rb +5 -0
- data/templates/result.fields +18 -0
- data/templates/result.sample +48 -0
- data/templates/result.template +31 -0
- metadata +123 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
module Dradis::Plugins::OpenVAS
|
2
|
+
|
3
|
+
# This processor defers to ::OpenVAS::Result class to extract all the
|
4
|
+
# relevant information exposed through the :result template.
|
5
|
+
class FieldProcessor < Dradis::Plugins::Upload::FieldProcessor
|
6
|
+
|
7
|
+
def post_initialize(args={})
|
8
|
+
|
9
|
+
# Figure out if v6 or v7
|
10
|
+
@openvas_object = case detect_version(data)
|
11
|
+
when :v6
|
12
|
+
::OpenVAS::V6::Result.new(data)
|
13
|
+
when :v7
|
14
|
+
::OpenVAS::V7::Result.new(data)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def value(args={})
|
19
|
+
field = args[:field]
|
20
|
+
|
21
|
+
# fields in the template are of the form <foo>.<field>, where <foo>
|
22
|
+
# is common across all fields for a given template (and meaningless).
|
23
|
+
_, name = field.split('.')
|
24
|
+
|
25
|
+
@openvas_object.try(name) || 'n/a'
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
# There is no clear-cut way to determine the version of the file from the
|
30
|
+
# contents (see thread below) so we have to do some guess work.
|
31
|
+
#
|
32
|
+
# See:
|
33
|
+
# http://lists.wald.intevation.org/pipermail/openvas-discuss/2014-October/006907.html
|
34
|
+
# http://lists.wald.intevation.org/pipermail/openvas-discuss/2014-November/007092.html
|
35
|
+
def detect_version(xml_data)
|
36
|
+
# Gross over simplification. May need to smarten in the future.
|
37
|
+
xml_data.at_xpath('./description[contains(text(), "Summary:")]') ? :v6 : :v7
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dradis
|
2
|
+
module Plugins
|
3
|
+
module OpenVAS
|
4
|
+
# Returns the version of the currently loaded OpenVAS as a <tt>Gem::Version</tt>
|
5
|
+
def self.gem_version
|
6
|
+
Gem::Version.new VERSION::STRING
|
7
|
+
end
|
8
|
+
|
9
|
+
module VERSION
|
10
|
+
MAJOR = 3
|
11
|
+
MINOR = 6
|
12
|
+
TINY = 0
|
13
|
+
PRE = nil
|
14
|
+
|
15
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Dradis::Plugins::OpenVAS
|
2
|
+
class Importer < Dradis::Plugins::Upload::Importer
|
3
|
+
|
4
|
+
# The framework will call this function if the user selects this plugin from
|
5
|
+
# the dropdown list and uploads a file.
|
6
|
+
# @returns true if the operation was successful, false otherwise
|
7
|
+
def import(params={})
|
8
|
+
file_content = File.read( params[:file] )
|
9
|
+
|
10
|
+
# Parse contents
|
11
|
+
logger.info{'Parsing OpenVAS output file...'}
|
12
|
+
@doc = Nokogiri::XML(file_content)
|
13
|
+
logger.info{'Done'}
|
14
|
+
|
15
|
+
|
16
|
+
# Detect valid OpenVAS XML
|
17
|
+
if @doc.xpath('/report').empty?
|
18
|
+
error = "No report results were detected in the uploaded file (/report). Ensure you uploaded an OpenVAS XML report."
|
19
|
+
logger.fatal{ error }
|
20
|
+
content_service.create_note text: error
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
@doc.xpath('/report/report/results/result').each do |xml_result|
|
26
|
+
process_result(xml_result)
|
27
|
+
end
|
28
|
+
|
29
|
+
logger.info{ "Report processed." }
|
30
|
+
return true
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
attr_accessor :host_node
|
35
|
+
|
36
|
+
def process_result(xml_result)
|
37
|
+
# Extract host
|
38
|
+
host_label = xml_result.at_xpath('./host').text()
|
39
|
+
self.host_node = content_service.create_node(label: host_label, type: :host)
|
40
|
+
|
41
|
+
# Uniquely identify this issue
|
42
|
+
nvt_oid = xml_result.at_xpath('./nvt')[:oid]
|
43
|
+
|
44
|
+
logger.info{ "\t\t => Creating new issue (#{nvt_oid})" }
|
45
|
+
|
46
|
+
issue_text = template_service.process_template(template: 'result', data: xml_result)
|
47
|
+
issue = content_service.create_issue(text: issue_text, id: nvt_oid)
|
48
|
+
|
49
|
+
|
50
|
+
# Add evidence. It doesn't look like OpenVAS provides much in terms of
|
51
|
+
# instance-specific evidence though.
|
52
|
+
logger.info{ "\t\t => Adding reference to this host" }
|
53
|
+
|
54
|
+
port_info = xml_result.at_xpath('./port').text
|
55
|
+
evidence_content = "\n#[Port]#\n#{port_info}\n\n"
|
56
|
+
|
57
|
+
# There is no way of knowing where OpenVAS is going to place the evidence
|
58
|
+
# for each issue. For example:
|
59
|
+
#
|
60
|
+
# A) 1.3.6.1.4.1.25623.1.0.900498 - 'Apache Web ServerVersion Detection'
|
61
|
+
# uses the full <description> field:
|
62
|
+
#
|
63
|
+
# <description>Detected Apache Tomcat version: 2.2.22
|
64
|
+
# Location: 80/tcp
|
65
|
+
# CPE: cpe:/a:apache:http_server:2.2.22
|
66
|
+
#
|
67
|
+
# Concluded from version identification result:
|
68
|
+
# Server: Apache/2.2.22
|
69
|
+
# </description>
|
70
|
+
#
|
71
|
+
# B) 1.3.6.1.4.1.25623.1.0.103122 - 'Apache Web Server ETag Header
|
72
|
+
# Information Disclosure Weakness' uses the 'Information that was gathered'
|
73
|
+
# meta-field inside <description>
|
74
|
+
#
|
75
|
+
# <description>
|
76
|
+
# Summary:
|
77
|
+
# A weakness has been discovered in Apache web servers that are
|
78
|
+
# configured to use the FileETag directive. Due to the way in which
|
79
|
+
# [...]
|
80
|
+
#
|
81
|
+
# Solution:
|
82
|
+
# OpenBSD has released a patch to address this issue.
|
83
|
+
# [...]
|
84
|
+
#
|
85
|
+
# Information that was gathered:
|
86
|
+
# Inode: 5753015
|
87
|
+
# Size: 604
|
88
|
+
# </description>
|
89
|
+
#
|
90
|
+
# C) 1.3.6.1.4.1.25623.1.0.10766 - 'Apache UserDir Sensitive Information Disclosure'
|
91
|
+
# doesn't provide any per-instance information.
|
92
|
+
#
|
93
|
+
# Best thing to do is to include the full <description> field and let the user deal with it.
|
94
|
+
description = xml_result.at_xpath('./description').text()
|
95
|
+
evidence_content << "\n#[Description]#\n#{description}\n\n"
|
96
|
+
|
97
|
+
content_service.create_evidence(issue: issue, node: host_node, content: evidence_content)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module OpenVAS
|
2
|
+
# This class represents each of the /report/report/results/result elements in
|
3
|
+
# the OpenVAS XML document.
|
4
|
+
#
|
5
|
+
# It provides a convenient way to access the information scattered all over
|
6
|
+
# the XML in attributes and nested tags.
|
7
|
+
#
|
8
|
+
# Instead of providing separate methods for each supported property we rely
|
9
|
+
# on Ruby's #method_missing to do most of the work.
|
10
|
+
class Result
|
11
|
+
|
12
|
+
# Accepts an XML node from Nokogiri::XML.
|
13
|
+
def initialize(xml_node)
|
14
|
+
@xml = xml_node
|
15
|
+
end
|
16
|
+
|
17
|
+
# List of supported tags. They can be attributes, simple descendans or
|
18
|
+
# collections (e.g. <bid/>, <cve/>, <xref/>)
|
19
|
+
def supported_tags
|
20
|
+
[
|
21
|
+
# attributes
|
22
|
+
# NONE
|
23
|
+
|
24
|
+
# simple tags
|
25
|
+
:threat, :description, :original_threat, :notes, :overrides,
|
26
|
+
|
27
|
+
# nested tags
|
28
|
+
:name, :cvss_base, :risk_factor, :cve, :bid, :xref,
|
29
|
+
|
30
|
+
# fields inside :description
|
31
|
+
:summary, :info_gathered, :insight, :impact, :impact_level, :affected_software, :solution
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
# This allows external callers (and specs) to check for implemented
|
36
|
+
# properties
|
37
|
+
def respond_to?(method, include_private=false)
|
38
|
+
return true if supported_tags.include?(method.to_sym)
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
# This method is invoked by Ruby when a method that is not defined in this
|
43
|
+
# instance is called.
|
44
|
+
#
|
45
|
+
# In our case we inspect the @method@ parameter and try to find the
|
46
|
+
# attribute, simple descendent or collection that it maps to in the XML
|
47
|
+
# tree.
|
48
|
+
def method_missing(method, *args)
|
49
|
+
|
50
|
+
# We could remove this check and return nil for any non-recognized tag.
|
51
|
+
# The problem would be that it would make tricky to debug problems with
|
52
|
+
# typos. For instance: <>.potr would return nil instead of raising an
|
53
|
+
# exception
|
54
|
+
unless supported_tags.include?(method)
|
55
|
+
super
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
# first we try the attributes: port, svc_name, protocol, severity,
|
60
|
+
# plugin_id, plugin_name, plugin_family
|
61
|
+
# translations_table = {
|
62
|
+
# # @port = xml.attributes["port"]
|
63
|
+
# # @svc_name = xml.attributes["svc_name"]
|
64
|
+
# # @protocol = xml.attributes["protocol"]
|
65
|
+
# # @severity = xml.attributes["severity"]
|
66
|
+
# :plugin_id => 'pluginID',
|
67
|
+
# :plugin_name => 'pluginName',
|
68
|
+
# :plugin_family => 'pluginFamily'
|
69
|
+
# }
|
70
|
+
# method_name = translations_table.fetch(method, method.to_s)
|
71
|
+
# return @xml.attributes[method_name].value if @xml.attributes.key?(method_name)
|
72
|
+
method_name = method.to_s
|
73
|
+
|
74
|
+
|
75
|
+
# first we try the children tags: :threat, :description, :original_threat,
|
76
|
+
# :notes, :overrides
|
77
|
+
tag = @xml.at_xpath("./#{method_name}")
|
78
|
+
if tag
|
79
|
+
return tag.text
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# nested tags: :name, :cvss_base, :risk_factor, :cve, :bid, :xref,
|
84
|
+
tag = @xml.at_xpath("./nvt/#{method_name}")
|
85
|
+
if tag
|
86
|
+
return tag.text
|
87
|
+
end
|
88
|
+
|
89
|
+
# fields inside :description
|
90
|
+
if description_fields.key?(method)
|
91
|
+
return description_fields[method]
|
92
|
+
end
|
93
|
+
|
94
|
+
# nothing found, the tag is valid but not present in this ReportItem
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
# This method parses the <description> tag of the <result> entry and
|
100
|
+
# extracts the available fields, in a similar way to what Note#fields
|
101
|
+
# does.
|
102
|
+
def description_fields
|
103
|
+
if @description_fields.nil?
|
104
|
+
delimiters = {
|
105
|
+
'Affected Software/OS:' => :affected_software,
|
106
|
+
'Impact:' => :impact,
|
107
|
+
'Impact Level:' => :impact_level,
|
108
|
+
'Information that was gathered:' => :info_gathered,
|
109
|
+
'Solution:' => :solution,
|
110
|
+
'Summary:' => :summary,
|
111
|
+
'Vulnerability Insight:' => :insight
|
112
|
+
}
|
113
|
+
|
114
|
+
@description_fields = {}
|
115
|
+
current_field = nil
|
116
|
+
buffer = ''
|
117
|
+
clean_line = nil
|
118
|
+
|
119
|
+
@xml.at_xpath('./description').text().each_line do |line|
|
120
|
+
clean_line = line.lstrip
|
121
|
+
if clean_line.empty?
|
122
|
+
buffer << "\n"
|
123
|
+
next
|
124
|
+
end
|
125
|
+
|
126
|
+
# For some reason Impact Level: is followed by the content instead of a new
|
127
|
+
# line like the other fields
|
128
|
+
if clean_line =~ /Impact Level: (.*)/
|
129
|
+
@description_fields[:impact_level] = $1
|
130
|
+
|
131
|
+
# we terminate the previous field and unassign :current_field
|
132
|
+
if current_field
|
133
|
+
@description_fields[delimiters[current_field]] = buffer
|
134
|
+
current_field = nil
|
135
|
+
buffer = ''
|
136
|
+
end
|
137
|
+
next
|
138
|
+
end
|
139
|
+
|
140
|
+
if delimiters.key?(clean_line.rstrip)
|
141
|
+
if current_field
|
142
|
+
# we need the conditional for the 1st iteration
|
143
|
+
@description_fields[delimiters[current_field]] = buffer
|
144
|
+
end
|
145
|
+
current_field = clean_line.rstrip
|
146
|
+
buffer = ''
|
147
|
+
next
|
148
|
+
end
|
149
|
+
|
150
|
+
buffer << clean_line
|
151
|
+
end
|
152
|
+
# wrap up the last field whose contents are already in the buffer.
|
153
|
+
@description_fields[delimiters[current_field]] = buffer
|
154
|
+
end
|
155
|
+
|
156
|
+
@description_fields
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
require 'openvas/v6/result'
|
163
|
+
require 'openvas/v7/result'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module OpenVAS::V6
|
2
|
+
|
3
|
+
# The format is given by the OMPv4 :get_reports call (OpenVASv6 uses OMPv4).
|
4
|
+
#
|
5
|
+
# ::OpenVAS::Result already implements v6, so basically there is nothing to
|
6
|
+
# overwrite here. Great OO-design? Probably not!
|
7
|
+
#
|
8
|
+
# See:
|
9
|
+
# http://www.openvas.org/omp-4-0.html#command_get_reports
|
10
|
+
class Result < ::OpenVAS::Result
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module OpenVAS::V7
|
2
|
+
|
3
|
+
# The format is given by the OMPv5 :get_reports call (OpenVASv7 uses OMPv5).
|
4
|
+
#
|
5
|
+
# See:
|
6
|
+
# http://www.openvas.org/omp-5-0.html#command_get_reports
|
7
|
+
class Result < ::OpenVAS::Result
|
8
|
+
|
9
|
+
|
10
|
+
private
|
11
|
+
# This method parses the <tags> tag of the <result> entry and extracts the
|
12
|
+
# available fields. Previous versions of the format (e.g. OpenVAS v6)
|
13
|
+
# included these embedded fields in the <description> tag instead of the
|
14
|
+
# <tags> tag, hence the not-so-intuitive name.
|
15
|
+
def description_fields
|
16
|
+
if @tag_fields.nil?
|
17
|
+
delimiters = {
|
18
|
+
# Not supported via .fields
|
19
|
+
# 'cvss_base_vector='
|
20
|
+
'impact=' => :impact,
|
21
|
+
|
22
|
+
# Not supported via .fields
|
23
|
+
# 'vuldetect='
|
24
|
+
'insight=' => :insight,
|
25
|
+
'solution=' => :solution,
|
26
|
+
'summary=' => :summary,
|
27
|
+
'affected=' => :affected_software
|
28
|
+
|
29
|
+
# Missing fields, these used to be available under <description> but it
|
30
|
+
# doesn't look like they are under <tags>
|
31
|
+
# 'Impact Level:' => :impact_level,
|
32
|
+
# 'Information that was gathered:' => :info_gathered,
|
33
|
+
}
|
34
|
+
|
35
|
+
@tag_fields = {}
|
36
|
+
current_field = nil
|
37
|
+
buffer = ''
|
38
|
+
clean_line = nil
|
39
|
+
|
40
|
+
@xml.at_xpath('./nvt/tags').text().split('|').each do |tag_line|
|
41
|
+
clean_line = tag_line.lstrip
|
42
|
+
|
43
|
+
if clean_line.empty?
|
44
|
+
buffer << "\n"
|
45
|
+
next
|
46
|
+
end
|
47
|
+
|
48
|
+
delimiters.keys.each do |tag_name|
|
49
|
+
if tag_line.starts_with?(tag_name)
|
50
|
+
@tag_fields[delimiters[tag_name]] = clean_line[tag_name.length..-1]
|
51
|
+
next
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
@tag_fields
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|