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.
@@ -0,0 +1,8 @@
1
+ # hook to the framework base clases
2
+ require 'dradis-plugins'
3
+
4
+ # load this add-on's engine
5
+ require 'dradis/plugins/openvas'
6
+
7
+ # load supporting OpenVAS classes
8
+ require 'openvas/result'
@@ -0,0 +1,11 @@
1
+ module Dradis
2
+ module Plugins
3
+ module OpenVAS
4
+ end
5
+ end
6
+ end
7
+
8
+ require 'dradis/plugins/openvas/engine'
9
+ require 'dradis/plugins/openvas/field_processor'
10
+ require 'dradis/plugins/openvas/importer'
11
+ require 'dradis/plugins/openvas/version'
@@ -0,0 +1,9 @@
1
+ module Dradis::Plugins::OpenVAS
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Dradis::Plugins::OpenVAS
4
+
5
+ include ::Dradis::Plugins::Base
6
+ description 'Processes OpenVAS XML v6 or v7 format'
7
+ provides :upload
8
+ end
9
+ end
@@ -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,13 @@
1
+ require_relative 'gem_version'
2
+
3
+ module Dradis
4
+ module Plugins
5
+ module OpenVAS
6
+ # Returns the version of the currently loaded OpenVAS as a
7
+ # <tt>Gem::Version</tt>.
8
+ def self.version
9
+ gem_version
10
+ end
11
+ end
12
+ end
13
+ 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