dradis-qualys 4.1.0 → 4.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/dradis/plugins/qualys/asset/importer.rb +115 -0
- data/lib/dradis/plugins/qualys/engine.rb +13 -0
- data/lib/dradis/plugins/qualys/field_processor.rb +23 -3
- data/lib/dradis/plugins/qualys/gem_version.rb +1 -1
- data/lib/dradis/plugins/qualys/vuln/importer.rb +107 -0
- data/lib/dradis/plugins/qualys/was/importer.rb +113 -0
- data/lib/dradis/plugins/qualys.rb +4 -1
- data/lib/dradis-qualys.rb +4 -0
- data/lib/qualys/asset/evidence.rb +74 -0
- data/lib/qualys/asset/vulnerability.rb +87 -0
- data/lib/qualys/element.rb +31 -29
- data/lib/qualys/was/qid.rb +85 -0
- data/lib/qualys/was/vulnerability.rb +68 -0
- data/lib/tasks/thorfile.rb +15 -1
- data/spec/fixtures/files/simple_asset.xml +126 -0
- data/spec/fixtures/files/simple_was.xml +134 -0
- data/spec/qualys/asset/importer_spec.rb +41 -0
- data/spec/qualys/{importer_spec.rb → vuln/importer_spec.rb} +5 -50
- data/spec/qualys/was/importer_spec.rb +41 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/spec_macros.rb +46 -0
- data/templates/asset-evidence.fields +9 -0
- data/templates/asset-evidence.sample +14 -0
- data/templates/asset-evidence.template +11 -0
- data/templates/asset-issue.fields +14 -0
- data/templates/asset-issue.sample +21 -0
- data/templates/asset-issue.template +22 -0
- data/templates/element.fields +1 -0
- data/templates/element.template +4 -0
- data/templates/was-evidence.fields +6 -0
- data/templates/was-evidence.sample +44 -0
- data/templates/was-evidence.template +11 -0
- data/templates/was-issue.fields +16 -0
- data/templates/was-issue.sample +24 -0
- data/templates/was-issue.template +28 -0
- metadata +34 -6
- data/lib/dradis/plugins/qualys/importer.rb +0 -88
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2da61b9fd75db0c8211be93f7449d215ac487a68f96bfed06890a0faeeca9df1
|
4
|
+
data.tar.gz: 612d0c2f9c12cec4f3c2c65eeb3a4d4ad1ae8bd9a091ef67894e077fc0e73079
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 998662ea3dd5a075ffc5eb764b781d3f4e56b5af56243317270616eeb59eb9cf7607823cb359b783f8f5f4417a224c8c4c068154f4382160fa89ef71ccb66851
|
7
|
+
data.tar.gz: ee2968f166612fd5bab2188ea0aba072dd50ca46f99b178a2ea4b24acb7816f888ccdfbdb781af0afcdc4c9c1e3591c0758a1ea146ae6f8b7652c6f4cd60da53
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
v4.4.0 (June 2022)
|
2
|
+
- Registers template mappings locally
|
3
|
+
|
4
|
+
v4.3.0 (April 2022)
|
5
|
+
- Adds Qualys Asset Scanner (ASSET) support
|
6
|
+
- Bugs fixes:
|
7
|
+
- Fixes WAS CVSS3 mapping and not importing fields to the issue
|
8
|
+
|
9
|
+
v4.2.0 (February 2022)
|
10
|
+
- Adds 'element.qualys_collection' as issue field
|
11
|
+
- Adds Qualys Web Application Scanner (WAS) support
|
12
|
+
|
1
13
|
v4.1.0 (November 2021)
|
2
14
|
- Add <dd>, <dt> support
|
3
15
|
- Remove orphaned <b> tags
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Dradis::Plugins::Qualys
|
2
|
+
module Asset
|
3
|
+
ROOT_PATH_NAME = 'ASSET_DATA_REPORT'.freeze
|
4
|
+
def self.meta
|
5
|
+
package = Dradis::Plugins::Qualys
|
6
|
+
|
7
|
+
{
|
8
|
+
name: package::Engine::plugin_name,
|
9
|
+
description: 'Upload Qualys Asset results (.xml)',
|
10
|
+
version: package.version
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
class Importer < Dradis::Plugins::Upload::Importer
|
15
|
+
def self.templates
|
16
|
+
{ evidence: 'asset-evidence', issue: 'asset-issue' }
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(args={})
|
20
|
+
args[:plugin] = Dradis::Plugins::Qualys
|
21
|
+
super(args)
|
22
|
+
|
23
|
+
@issue_lookup = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
# The framework will call this function if the user selects this plugin from
|
27
|
+
# the dropdown list and uploads a file.
|
28
|
+
# @returns true if the operation was successful, false otherwise
|
29
|
+
def import(params={})
|
30
|
+
file_content = File.read( params[:file] )
|
31
|
+
|
32
|
+
logger.info { 'Parsing Qualys ASSET XML output file...' }
|
33
|
+
doc = Nokogiri::XML(file_content)
|
34
|
+
logger.info { 'Done.' }
|
35
|
+
|
36
|
+
if doc.root.name != ROOT_PATH_NAME
|
37
|
+
error = 'No scan results were detected in the uploaded file. Ensure you uploaded a Qualys ASSET XML file.'
|
38
|
+
logger.fatal { error }
|
39
|
+
content_service.create_note text: error
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
doc.xpath('ASSET_DATA_REPORT/GLOSSARY/VULN_DETAILS_LIST/VULN_DETAILS').each do |xml_issue|
|
44
|
+
process_issue(xml_issue)
|
45
|
+
end
|
46
|
+
|
47
|
+
doc.xpath('ASSET_DATA_REPORT/HOST_LIST/HOST').each do |xml_node|
|
48
|
+
process_node(xml_node)
|
49
|
+
end
|
50
|
+
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
attr_accessor :issue_lookup
|
57
|
+
|
58
|
+
def process_node(xml_node)
|
59
|
+
logger.info { 'Creating node...' }
|
60
|
+
|
61
|
+
# Create host node
|
62
|
+
host_node = content_service.create_node(
|
63
|
+
label: xml_node.at_xpath('IP').text,
|
64
|
+
type: :host
|
65
|
+
)
|
66
|
+
|
67
|
+
%w[dns host_id operating_system qg_hostid tracking_method].each do |key|
|
68
|
+
prop = xml_node.at_xpath(key.upcase)
|
69
|
+
host_node.set_property(key.to_sym, prop.text) if prop
|
70
|
+
end
|
71
|
+
|
72
|
+
tags = xml_node.at_xpath('ASSET_TAGS/ASSET_TAG')
|
73
|
+
if tags
|
74
|
+
tags.each do |tag|
|
75
|
+
host_node.set_property(:asset_tags, tag.text)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
host_node.save
|
80
|
+
|
81
|
+
xml_node.xpath('./VULN_INFO_LIST/VULN_INFO').each do |xml_evidence|
|
82
|
+
process_evidence(xml_evidence, host_node)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def process_issue(xml_vuln)
|
87
|
+
qid = xml_vuln.at_xpath('QID').text
|
88
|
+
logger.info { "\t => Creating new issue (plugin_id: #{ qid })" }
|
89
|
+
issue_text = template_service.process_template(template: 'asset-issue', data: xml_vuln)
|
90
|
+
issue = content_service.create_issue(text: issue_text, id: qid)
|
91
|
+
|
92
|
+
issue_lookup[qid.to_i] = issue
|
93
|
+
end
|
94
|
+
|
95
|
+
def process_evidence(xml_evidence, node)
|
96
|
+
qid = xml_evidence.at_xpath('./QID').text
|
97
|
+
|
98
|
+
issue = issue_lookup[qid.to_i]
|
99
|
+
if issue
|
100
|
+
issue_id = issue.respond_to?(:id) ? issue.id : issue.to_issue.id
|
101
|
+
|
102
|
+
logger.info { "\t => Creating new evidence (plugin_id: #{qid})" }
|
103
|
+
logger.info { "\t\t => Issue: #{issue.title} (plugin_id: #{issue_id})" }
|
104
|
+
logger.info { "\t\t => Node: #{node.label} (#{node.id})" }
|
105
|
+
else
|
106
|
+
logger.info { "\t => Couldn't find QID for issue with ID=#{qid}" }
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
evidence_content = template_service.process_template(template: 'asset-evidence', data: xml_evidence)
|
111
|
+
content_service.create_evidence(issue: issue, node: node, content: evidence_content)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -5,5 +5,18 @@ module Dradis::Plugins::Qualys
|
|
5
5
|
include ::Dradis::Plugins::Base
|
6
6
|
description 'Processes Qualys output'
|
7
7
|
provides :upload
|
8
|
+
|
9
|
+
# Because this plugin provides two export modules, we have to overwrite
|
10
|
+
# the default .uploaders() method.
|
11
|
+
#
|
12
|
+
# See:
|
13
|
+
# Dradis::Plugins::Upload::Base in dradis-plugins
|
14
|
+
def self.uploaders
|
15
|
+
[
|
16
|
+
Dradis::Plugins::Qualys::Asset,
|
17
|
+
Dradis::Plugins::Qualys::Vuln,
|
18
|
+
Dradis::Plugins::Qualys::WAS
|
19
|
+
]
|
20
|
+
end
|
8
21
|
end
|
9
22
|
end
|
@@ -4,8 +4,19 @@ module Dradis
|
|
4
4
|
class FieldProcessor < Dradis::Plugins::Upload::FieldProcessor
|
5
5
|
|
6
6
|
def post_initialize(args={})
|
7
|
-
|
8
|
-
|
7
|
+
case data.name
|
8
|
+
when 'CAT'
|
9
|
+
@cat_object = data
|
10
|
+
@qualys_object = ::Qualys::Element.new(data.elements.first)
|
11
|
+
when 'QID'
|
12
|
+
@qualys_object = ::Qualys::WAS::QID.new(data)
|
13
|
+
when 'VULNERABILITY'
|
14
|
+
@qualys_object = ::Qualys::WAS::Vulnerability.new(data)
|
15
|
+
when 'VULN_DETAILS'
|
16
|
+
@qualys_object = ::Qualys::Asset::Vulnerability.new(data)
|
17
|
+
when 'VULN_INFO'
|
18
|
+
@qualys_object = ::Qualys::Asset::Evidence.new(data)
|
19
|
+
end
|
9
20
|
end
|
10
21
|
|
11
22
|
def value(args={})
|
@@ -13,8 +24,14 @@ module Dradis
|
|
13
24
|
|
14
25
|
# Fields in the template are of the form <foo>.<field>, where <foo>
|
15
26
|
# is common across all fields for a given template (and meaningless).
|
16
|
-
|
27
|
+
# However we can use it to identify the type of scan we're processing.
|
28
|
+
type, name = field.split('.')
|
29
|
+
|
30
|
+
%{element evidence}.include?(type) ? value_network(name) : value_was(name)
|
31
|
+
end
|
17
32
|
|
33
|
+
private
|
34
|
+
def value_network(name)
|
18
35
|
if %w{cat_fqdn cat_misc cat_port cat_protocol cat_value}.include?(name)
|
19
36
|
attribute = name[4..-1]
|
20
37
|
@cat_object[attribute] || 'n/a'
|
@@ -36,6 +53,9 @@ module Dradis
|
|
36
53
|
end
|
37
54
|
end
|
38
55
|
|
56
|
+
def value_was(name)
|
57
|
+
@qualys_object.try(name) || 'n/a'
|
58
|
+
end
|
39
59
|
end
|
40
60
|
end
|
41
61
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Dradis::Plugins::Qualys
|
2
|
+
module Vuln
|
3
|
+
def self.meta
|
4
|
+
package = Dradis::Plugins::Qualys
|
5
|
+
|
6
|
+
{
|
7
|
+
name: package::Engine::plugin_name,
|
8
|
+
description: 'Upload Qualys Vulnerability Management results (.xml)',
|
9
|
+
version: package.version
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
class Importer < Dradis::Plugins::Upload::Importer
|
14
|
+
attr_accessor :host_node
|
15
|
+
|
16
|
+
def self.templates
|
17
|
+
{ evidence: 'evidence', issue: 'element' }
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(args={})
|
21
|
+
args[:plugin] = Dradis::Plugins::Qualys
|
22
|
+
super(args)
|
23
|
+
end
|
24
|
+
|
25
|
+
# The framework will call this function if the user selects this plugin from
|
26
|
+
# the dropdown list and uploads a file.
|
27
|
+
# @returns true if the operation was successful, false otherwise
|
28
|
+
def import(params={})
|
29
|
+
file_content = File.read( params[:file] )
|
30
|
+
|
31
|
+
logger.info{'Parsing Qualys output file...'}
|
32
|
+
@doc = Nokogiri::XML( file_content )
|
33
|
+
logger.info{'Done.'}
|
34
|
+
|
35
|
+
if @doc.root.name != 'SCAN'
|
36
|
+
error = "No scan results were detected in the uploaded file. Ensure you uploaded a Qualys XML file."
|
37
|
+
logger.fatal{ error }
|
38
|
+
content_service.create_note text: error
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
|
42
|
+
@doc.xpath('SCAN/IP').each do |xml_host|
|
43
|
+
process_ip(xml_host)
|
44
|
+
end
|
45
|
+
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def process_ip(xml_host)
|
52
|
+
host_ip = xml_host['value']
|
53
|
+
logger.info{ "Host: %s" % host_ip }
|
54
|
+
|
55
|
+
self.host_node = content_service.create_node(label: host_ip, type: :host)
|
56
|
+
|
57
|
+
host_node.set_property(:ip, host_ip)
|
58
|
+
host_node.set_property(:hostname, xml_host['name'])
|
59
|
+
if (xml_os = xml_host.xpath('OS')) && xml_os.any?
|
60
|
+
host_node.set_property(:os, xml_os.text)
|
61
|
+
end
|
62
|
+
host_node.save
|
63
|
+
|
64
|
+
# We treat INFOS, SERVICES, PRACTICES, and VULNS the same way
|
65
|
+
# All of these are imported into Dradis as Issues
|
66
|
+
['INFOS', 'SERVICES', 'PRACTICES', 'VULNS'].each do |collection|
|
67
|
+
xml_host.xpath(collection).each do |xml_collection|
|
68
|
+
process_collection(collection, xml_collection)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_collection(collection, xml_collection)
|
74
|
+
xml_cats = xml_collection.xpath('CAT')
|
75
|
+
|
76
|
+
xml_cats.each do |xml_cat|
|
77
|
+
logger.info{ "\t#{ collection } - #{ xml_cat['value'] }" }
|
78
|
+
|
79
|
+
empty_dup_xml_cat = xml_cat.dup
|
80
|
+
empty_dup_xml_cat.children.remove
|
81
|
+
|
82
|
+
# For each INFOS/CAT/INFO, SERVICES/CAT/SERVICE, VULNS/CAT/VULN, etc.
|
83
|
+
xml_cat.xpath(collection.chop).each do |xml_element|
|
84
|
+
dup_xml_cat = empty_dup_xml_cat.dup
|
85
|
+
dup_xml_cat.add_child(xml_element.dup)
|
86
|
+
cat_number = xml_element[:number]
|
87
|
+
|
88
|
+
process_vuln(cat_number, dup_xml_cat)
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Takes a <CAT> element containing a single <VULN> element and processes an
|
95
|
+
# Issue and Evidence template out of it.
|
96
|
+
def process_vuln(vuln_number, xml_cat)
|
97
|
+
logger.info{ "\t\t => Creating new issue (plugin_id: #{ vuln_number })" }
|
98
|
+
issue_text = template_service.process_template(template: 'element', data: xml_cat)
|
99
|
+
issue = content_service.create_issue(text: issue_text, id: vuln_number)
|
100
|
+
|
101
|
+
logger.info{ "\t\t => Creating new evidence" }
|
102
|
+
evidence_content = template_service.process_template(template: 'evidence', data: xml_cat)
|
103
|
+
content_service.create_evidence(issue: issue, node: self.host_node, content: evidence_content)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Dradis::Plugins::Qualys
|
2
|
+
|
3
|
+
# This module knows how to parse Qualys Web Application Scanner format.
|
4
|
+
module WAS
|
5
|
+
def self.meta
|
6
|
+
package = Dradis::Plugins::Qualys
|
7
|
+
|
8
|
+
{
|
9
|
+
name: package::Engine::plugin_name,
|
10
|
+
description: 'Upload Qualys WAS results (.xml)',
|
11
|
+
version: package.version
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
class Importer < Dradis::Plugins::Upload::Importer
|
16
|
+
def self.templates
|
17
|
+
{ evidence: 'was-evidence', issue: 'was-issue' }
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(args={})
|
21
|
+
args[:plugin] = Dradis::Plugins::Qualys
|
22
|
+
super(args)
|
23
|
+
|
24
|
+
@issue_lookup = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def import(params={})
|
28
|
+
file_content = File.read(params[:file])
|
29
|
+
|
30
|
+
logger.info { 'Parsing Qualys WAS XML output file...'}
|
31
|
+
doc = Nokogiri::XML(file_content)
|
32
|
+
logger.info { 'Done.' }
|
33
|
+
|
34
|
+
if doc.root.name != 'WAS_SCAN_REPORT'
|
35
|
+
error = 'Document doesn\'t seem to be in the Qualys WAS XML format.'
|
36
|
+
logger.fatal { error }
|
37
|
+
content_service.create_note text: error
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
logger.info { 'Global Summary information'}
|
42
|
+
|
43
|
+
xml_global_summary = doc.at_xpath('WAS_SCAN_REPORT/SUMMARY/GLOBAL_SUMMARY')
|
44
|
+
logger.info { 'Security Risk: ' + xml_global_summary.at_xpath('./SECURITY_RISK').text }
|
45
|
+
logger.info { 'Vulnerabilities found: ' + xml_global_summary.at_xpath('./VULNERABILITY').text }
|
46
|
+
|
47
|
+
xml_webapp = doc.at_xpath('WAS_SCAN_REPORT/APPENDIX/WEBAPP')
|
48
|
+
process_webapp(xml_webapp)
|
49
|
+
|
50
|
+
doc.xpath('WAS_SCAN_REPORT/GLOSSARY/QID_LIST/QID').each do |xml_qid|
|
51
|
+
process_issue(xml_qid)
|
52
|
+
end
|
53
|
+
|
54
|
+
doc.xpath('WAS_SCAN_REPORT/RESULTS/VULNERABILITY_LIST/VULNERABILITY').each do |xml_vulnerability|
|
55
|
+
process_evidence(xml_vulnerability)
|
56
|
+
end
|
57
|
+
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
attr_accessor :webapp_node, :issue_lookup
|
63
|
+
|
64
|
+
def process_evidence(xml_vulnerability)
|
65
|
+
id = xml_vulnerability.at_xpath('./ID').text
|
66
|
+
|
67
|
+
issue = issue_lookup[xml_vulnerability.at_xpath('./QID').text.to_i]
|
68
|
+
if issue
|
69
|
+
issue_id = issue.respond_to?(:id) ? issue.id : issue.to_issue.id
|
70
|
+
|
71
|
+
logger.info{ "\t => Creating new evidence (plugin_id: #{id})" }
|
72
|
+
logger.info{ "\t\t => Issue: #{issue.title} (plugin_id: #{issue_id})" }
|
73
|
+
logger.info{ "\t\t => Node: #{webapp_node.label} (#{webapp_node.id})" }
|
74
|
+
else
|
75
|
+
logger.info{ "\t => Couldn't find QID for evidence with ID=#{id}" }
|
76
|
+
return
|
77
|
+
end
|
78
|
+
|
79
|
+
evidence_content = template_service.process_template(template: 'was-evidence', data: xml_vulnerability)
|
80
|
+
content_service.create_evidence(issue: issue, node: webapp_node, content: evidence_content)
|
81
|
+
end
|
82
|
+
|
83
|
+
def process_issue(xml_qid)
|
84
|
+
qid = xml_qid.at_xpath('QID').text
|
85
|
+
logger.info{ "\t => Creating new issue (plugin_id: #{ qid })" }
|
86
|
+
issue_text = template_service.process_template(template: 'was-issue', data: xml_qid)
|
87
|
+
issue = content_service.create_issue(text: issue_text, id: qid)
|
88
|
+
|
89
|
+
issue_lookup[qid.to_i] = issue
|
90
|
+
end
|
91
|
+
|
92
|
+
def process_webapp(xml_webapp)
|
93
|
+
id = xml_webapp.at_xpath('./ID').text
|
94
|
+
name = xml_webapp.at_xpath('./NAME').text
|
95
|
+
url = xml_webapp.at_xpath('./URL').text
|
96
|
+
scope = xml_webapp.at_xpath('./SCOPE').text
|
97
|
+
|
98
|
+
uri = URI(url)
|
99
|
+
@webapp_node = content_service.create_node(label: uri.host, type: :host)
|
100
|
+
|
101
|
+
webapp_node.set_property('qualys.webapp.id', id)
|
102
|
+
webapp_node.set_property('qualys.webapp.name', name)
|
103
|
+
webapp_node.set_property('qualys.webapp.url', url)
|
104
|
+
webapp_node.set_property('qualys.webapp.scope', scope)
|
105
|
+
webapp_node.save!
|
106
|
+
|
107
|
+
logger.info { 'Webapp name: ' + name }
|
108
|
+
logger.info { 'Webapp URL: ' + url }
|
109
|
+
logger.info { 'Webapp scope: ' + scope }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -7,5 +7,8 @@ end
|
|
7
7
|
|
8
8
|
require 'dradis/plugins/qualys/engine'
|
9
9
|
require 'dradis/plugins/qualys/field_processor'
|
10
|
-
require 'dradis/plugins/qualys/importer'
|
11
10
|
require 'dradis/plugins/qualys/version'
|
11
|
+
|
12
|
+
require 'dradis/plugins/qualys/asset/importer'
|
13
|
+
require 'dradis/plugins/qualys/vuln/importer'
|
14
|
+
require 'dradis/plugins/qualys/was/importer'
|
data/lib/dradis-qualys.rb
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Qualys::Asset
|
2
|
+
# This class represents each of the ASSET_DATA_REPORT/GLOSSARY/VULN_INFO_LIST/
|
3
|
+
# VULN_INFO elements in the Qualys Asset 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 Evidence
|
11
|
+
# Accepts an XML node from Nokogiri::XML.
|
12
|
+
def initialize(xml_node)
|
13
|
+
@xml = xml_node
|
14
|
+
end
|
15
|
+
|
16
|
+
# List of supported tags. They can be attributes, simple descendans or
|
17
|
+
# collections (e.g. <references/>, <tags/>)
|
18
|
+
def supported_tags
|
19
|
+
[
|
20
|
+
# simple tags
|
21
|
+
:first_round, :last_round, :result, :ssl, :times_found,
|
22
|
+
:type, :vuln_status,
|
23
|
+
|
24
|
+
:cvss_base, :cvss3_final
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
# This allows external callers (and specs) to check for implemented
|
29
|
+
# properties
|
30
|
+
def respond_to?(method, include_private=false)
|
31
|
+
return true if supported_tags.include?(method.to_sym)
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
# This method is invoked by Ruby when a method that is not defined in this
|
36
|
+
# instance is called.
|
37
|
+
#
|
38
|
+
# In our case we inspect the @method@ parameter and try to find the
|
39
|
+
# attribute, simple descendent or collection that it maps to in the XML
|
40
|
+
# tree.
|
41
|
+
def method_missing(method, *args)
|
42
|
+
# We could remove this check and return nil for any non-recognized tag.
|
43
|
+
# The problem would be that it would make tricky to debug problems with
|
44
|
+
# typos. For instance: <>.potr would return nil instead of raising an
|
45
|
+
# exception
|
46
|
+
unless supported_tags.include?(method)
|
47
|
+
super
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
process_field_value(method.to_s)
|
52
|
+
end
|
53
|
+
|
54
|
+
def process_field_value(method)
|
55
|
+
tag = @xml.at_xpath("./#{method.upcase}")
|
56
|
+
|
57
|
+
if tag && !tag.text.blank?
|
58
|
+
if tags_with_html_content.include?(method)
|
59
|
+
Qualys.cleanup_html(tag.text)
|
60
|
+
else
|
61
|
+
tag.text
|
62
|
+
end
|
63
|
+
else
|
64
|
+
'n/a'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def tags_with_html_content
|
71
|
+
%w[result]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Qualys::Asset
|
2
|
+
# This class represents each of the ASSET_DATA_REPORT/GLOSSARY/VULN_DETAILS_LIST/
|
3
|
+
# VULN_DETAILS elements in the Qualys Asset 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 Vulnerability
|
11
|
+
# Accepts an XML node from Nokogiri::XML.
|
12
|
+
def initialize(xml_node)
|
13
|
+
@xml = xml_node
|
14
|
+
end
|
15
|
+
|
16
|
+
# List of supported tags. They can be attributes, simple descendans or
|
17
|
+
# collections (e.g. <references/>, <tags/>)
|
18
|
+
def supported_tags
|
19
|
+
[
|
20
|
+
# simple tags
|
21
|
+
:category, :impact, :last_update, :pci_flag, :qid, :result,
|
22
|
+
:severity, :solution, :threat, :title,
|
23
|
+
|
24
|
+
:cvss3_base, :cvss3_temporal, :cvss_base, :cvss_temporal
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
# This allows external callers (and specs) to check for implemented
|
29
|
+
# properties
|
30
|
+
def respond_to?(method, include_private=false)
|
31
|
+
return true if supported_tags.include?(method.to_sym)
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
# This method is invoked by Ruby when a method that is not defined in this
|
36
|
+
# instance is called.
|
37
|
+
#
|
38
|
+
# In our case we inspect the @method@ parameter and try to find the
|
39
|
+
# attribute, simple descendent or collection that it maps to in the XML
|
40
|
+
# tree.
|
41
|
+
def method_missing(method, *args)
|
42
|
+
# We could remove this check and return nil for any non-recognized tag.
|
43
|
+
# The problem would be that it would make tricky to debug problems with
|
44
|
+
# typos. For instance: <>.potr would return nil instead of raising an
|
45
|
+
# exception
|
46
|
+
unless supported_tags.include?(method)
|
47
|
+
super
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
process_field_value(method.to_s)
|
52
|
+
end
|
53
|
+
|
54
|
+
def process_field_value(method)
|
55
|
+
tag = @xml.at_xpath("./#{method.upcase}")
|
56
|
+
|
57
|
+
if method.starts_with?('cvss')
|
58
|
+
process_cvss_field(method)
|
59
|
+
elsif tag && !tag.text.blank?
|
60
|
+
if tags_with_html_content.include?(method)
|
61
|
+
Qualys.cleanup_html(tag.text)
|
62
|
+
else
|
63
|
+
tag.text
|
64
|
+
end
|
65
|
+
else
|
66
|
+
'n/a'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def process_cvss_field(method)
|
71
|
+
translations_table = {
|
72
|
+
cvss_base: 'CVSS_SCORE/CVSS_BASE',
|
73
|
+
cvss_temporal: 'CVSS_SCORE/CVSS_TEMPORAL',
|
74
|
+
cvss3_base: 'CVSS3_SCORE/CVSS3_BASE',
|
75
|
+
cvss3_temporal: 'CVSS3_SCORE/CVSS3_TEMPORAL'
|
76
|
+
}
|
77
|
+
|
78
|
+
@xml.xpath("./#{translations_table[method.to_sym]}").text
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def tags_with_html_content
|
84
|
+
%w[threat impact solution]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|