dradis-openvas 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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