dradis-qualys 4.1.0 → 4.2.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 +4 -0
- data/lib/dradis/plugins/qualys/engine.rb +12 -0
- data/lib/dradis/plugins/qualys/field_processor.rb +19 -3
- data/lib/dradis/plugins/qualys/gem_version.rb +1 -1
- data/lib/dradis/plugins/qualys/vuln/importer.rb +103 -0
- data/lib/dradis/plugins/qualys/was/importer.rb +109 -0
- data/lib/dradis/plugins/qualys.rb +3 -1
- data/lib/dradis-qualys.rb +2 -0
- data/lib/qualys/element.rb +31 -29
- data/lib/qualys/was/qid.rb +71 -0
- data/lib/qualys/was/vulnerability.rb +68 -0
- data/lib/tasks/thorfile.rb +15 -1
- data/spec/fixtures/files/simple_was.xml +127 -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 +50 -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 +21 -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: 3ba4d6ad211ac3a9b0b671a70ca898f8b4209476b0a60f2da30e8e4421a805f6
|
4
|
+
data.tar.gz: 5d2e7db73a7a40784fa6c4334e060daa1cbf730655d489efe594f3fd8acb7b07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d612c8715670f02a26c9be3efc6f9406c5543e3e5b76725d56baf394a0cce8eeafe4fd2c8cab28e2558b70d60b9e72a79c25a29a9266df52e0efa32a963425ae
|
7
|
+
data.tar.gz: e3ae22e600fa0b9ad4f96215ed9e981f4f932bc38480ee48608f28c578c1fe53d426550c30b0a6228d0109d36f422eb7deee3fc6df62359c55cc39396c42848c
|
data/CHANGELOG.md
CHANGED
@@ -5,5 +5,17 @@ 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::Vuln,
|
17
|
+
Dradis::Plugins::Qualys::WAS
|
18
|
+
]
|
19
|
+
end
|
8
20
|
end
|
9
21
|
end
|
@@ -4,8 +4,15 @@ 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
|
+
end
|
9
16
|
end
|
10
17
|
|
11
18
|
def value(args={})
|
@@ -13,8 +20,14 @@ module Dradis
|
|
13
20
|
|
14
21
|
# Fields in the template are of the form <foo>.<field>, where <foo>
|
15
22
|
# is common across all fields for a given template (and meaningless).
|
16
|
-
|
23
|
+
# However we can use it to identify the type of scan we're processing.
|
24
|
+
type, name = field.split('.')
|
25
|
+
|
26
|
+
%{element evidence}.include?(type) ? value_network(name) : value_was(name)
|
27
|
+
end
|
17
28
|
|
29
|
+
private
|
30
|
+
def value_network(name)
|
18
31
|
if %w{cat_fqdn cat_misc cat_port cat_protocol cat_value}.include?(name)
|
19
32
|
attribute = name[4..-1]
|
20
33
|
@cat_object[attribute] || 'n/a'
|
@@ -36,6 +49,9 @@ module Dradis
|
|
36
49
|
end
|
37
50
|
end
|
38
51
|
|
52
|
+
def value_was(name)
|
53
|
+
@qualys_object.try(name) || 'n/a'
|
54
|
+
end
|
39
55
|
end
|
40
56
|
end
|
41
57
|
end
|
@@ -0,0 +1,103 @@
|
|
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 initialize(args={})
|
17
|
+
args[:plugin] = Dradis::Plugins::Qualys
|
18
|
+
super(args)
|
19
|
+
end
|
20
|
+
|
21
|
+
# The framework will call this function if the user selects this plugin from
|
22
|
+
# the dropdown list and uploads a file.
|
23
|
+
# @returns true if the operation was successful, false otherwise
|
24
|
+
def import(params={})
|
25
|
+
file_content = File.read( params[:file] )
|
26
|
+
|
27
|
+
logger.info{'Parsing Qualys output file...'}
|
28
|
+
@doc = Nokogiri::XML( file_content )
|
29
|
+
logger.info{'Done.'}
|
30
|
+
|
31
|
+
if @doc.root.name != 'SCAN'
|
32
|
+
error = "No scan results were detected in the uploaded file. Ensure you uploaded a Qualys XML file."
|
33
|
+
logger.fatal{ error }
|
34
|
+
content_service.create_note text: error
|
35
|
+
return false
|
36
|
+
end
|
37
|
+
|
38
|
+
@doc.xpath('SCAN/IP').each do |xml_host|
|
39
|
+
process_ip(xml_host)
|
40
|
+
end
|
41
|
+
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def process_ip(xml_host)
|
48
|
+
host_ip = xml_host['value']
|
49
|
+
logger.info{ "Host: %s" % host_ip }
|
50
|
+
|
51
|
+
self.host_node = content_service.create_node(label: host_ip, type: :host)
|
52
|
+
|
53
|
+
host_node.set_property(:ip, host_ip)
|
54
|
+
host_node.set_property(:hostname, xml_host['name'])
|
55
|
+
if (xml_os = xml_host.xpath('OS')) && xml_os.any?
|
56
|
+
host_node.set_property(:os, xml_os.text)
|
57
|
+
end
|
58
|
+
host_node.save
|
59
|
+
|
60
|
+
# We treat INFOS, SERVICES, PRACTICES, and VULNS the same way
|
61
|
+
# All of these are imported into Dradis as Issues
|
62
|
+
['INFOS', 'SERVICES', 'PRACTICES', 'VULNS'].each do |collection|
|
63
|
+
xml_host.xpath(collection).each do |xml_collection|
|
64
|
+
process_collection(collection, xml_collection)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def process_collection(collection, xml_collection)
|
70
|
+
xml_cats = xml_collection.xpath('CAT')
|
71
|
+
|
72
|
+
xml_cats.each do |xml_cat|
|
73
|
+
logger.info{ "\t#{ collection } - #{ xml_cat['value'] }" }
|
74
|
+
|
75
|
+
empty_dup_xml_cat = xml_cat.dup
|
76
|
+
empty_dup_xml_cat.children.remove
|
77
|
+
|
78
|
+
# For each INFOS/CAT/INFO, SERVICES/CAT/SERVICE, VULNS/CAT/VULN, etc.
|
79
|
+
xml_cat.xpath(collection.chop).each do |xml_element|
|
80
|
+
dup_xml_cat = empty_dup_xml_cat.dup
|
81
|
+
dup_xml_cat.add_child(xml_element.dup)
|
82
|
+
cat_number = xml_element[:number]
|
83
|
+
|
84
|
+
process_vuln(cat_number, dup_xml_cat)
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Takes a <CAT> element containing a single <VULN> element and processes an
|
91
|
+
# Issue and Evidence template out of it.
|
92
|
+
def process_vuln(vuln_number, xml_cat)
|
93
|
+
logger.info{ "\t\t => Creating new issue (plugin_id: #{ vuln_number })" }
|
94
|
+
issue_text = template_service.process_template(template: 'element', data: xml_cat)
|
95
|
+
issue = content_service.create_issue(text: issue_text, id: vuln_number)
|
96
|
+
|
97
|
+
logger.info{ "\t\t => Creating new evidence" }
|
98
|
+
evidence_content = template_service.process_template(template: 'evidence', data: xml_cat)
|
99
|
+
content_service.create_evidence(issue: issue, node: self.host_node, content: evidence_content)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,109 @@
|
|
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 initialize(args={})
|
17
|
+
args[:plugin] = Dradis::Plugins::Qualys
|
18
|
+
super(args)
|
19
|
+
|
20
|
+
@issue_lookup = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def import(params={})
|
24
|
+
file_content = File.read(params[:file])
|
25
|
+
|
26
|
+
logger.info { 'Parsing Qualys WAS XML output file...'}
|
27
|
+
doc = Nokogiri::XML(file_content)
|
28
|
+
logger.info { 'Done.' }
|
29
|
+
|
30
|
+
if doc.root.name != 'WAS_SCAN_REPORT'
|
31
|
+
error = 'Document doesn\'t seem to be in the Qualys WAS XML format.'
|
32
|
+
logger.fatal { error }
|
33
|
+
content_service.create_note text: error
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
logger.info { 'Global Summary information'}
|
38
|
+
|
39
|
+
xml_global_summary = doc.at_xpath('WAS_SCAN_REPORT/SUMMARY/GLOBAL_SUMMARY')
|
40
|
+
logger.info { 'Security Risk: ' + xml_global_summary.at_xpath('./SECURITY_RISK').text }
|
41
|
+
logger.info { 'Vulnerabilities found: ' + xml_global_summary.at_xpath('./VULNERABILITY').text }
|
42
|
+
|
43
|
+
xml_webapp = doc.at_xpath('WAS_SCAN_REPORT/APPENDIX/WEBAPP')
|
44
|
+
process_webapp(xml_webapp)
|
45
|
+
|
46
|
+
doc.xpath('WAS_SCAN_REPORT/GLOSSARY/QID_LIST/QID').each do |xml_qid|
|
47
|
+
process_issue(xml_qid)
|
48
|
+
end
|
49
|
+
|
50
|
+
doc.xpath('WAS_SCAN_REPORT/RESULTS/VULNERABILITY_LIST/VULNERABILITY').each do |xml_vulnerability|
|
51
|
+
process_evidence(xml_vulnerability)
|
52
|
+
end
|
53
|
+
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
attr_accessor :webapp_node, :issue_lookup
|
59
|
+
|
60
|
+
def process_evidence(xml_vulnerability)
|
61
|
+
id = xml_vulnerability.at_xpath('./ID').text
|
62
|
+
|
63
|
+
issue = issue_lookup[xml_vulnerability.at_xpath('./QID').text.to_i]
|
64
|
+
if issue
|
65
|
+
issue_id = issue.respond_to?(:id) ? issue.id : issue.to_issue.id
|
66
|
+
|
67
|
+
logger.info{ "\t => Creating new evidence (plugin_id: #{id})" }
|
68
|
+
logger.info{ "\t\t => Issue: #{issue.title} (plugin_id: #{issue_id})" }
|
69
|
+
logger.info{ "\t\t => Node: #{webapp_node.label} (#{webapp_node.id})" }
|
70
|
+
else
|
71
|
+
logger.info{ "\t => Couldn't find QID for evidence with ID=#{id}" }
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
evidence_content = template_service.process_template(template: 'was-evidence', data: xml_vulnerability)
|
76
|
+
content_service.create_evidence(issue: issue, node: webapp_node, content: evidence_content)
|
77
|
+
end
|
78
|
+
|
79
|
+
def process_issue(xml_qid)
|
80
|
+
qid = xml_qid.at_xpath('QID').text
|
81
|
+
logger.info{ "\t => Creating new issue (plugin_id: #{ qid })" }
|
82
|
+
issue_text = template_service.process_template(template: 'was-issue', data: xml_qid)
|
83
|
+
issue = content_service.create_issue(text: issue_text, id: qid)
|
84
|
+
|
85
|
+
issue_lookup[qid.to_i] = issue
|
86
|
+
end
|
87
|
+
|
88
|
+
def process_webapp(xml_webapp)
|
89
|
+
id = xml_webapp.at_xpath('./ID').text
|
90
|
+
name = xml_webapp.at_xpath('./NAME').text
|
91
|
+
url = xml_webapp.at_xpath('./URL').text
|
92
|
+
scope = xml_webapp.at_xpath('./SCOPE').text
|
93
|
+
|
94
|
+
uri = URI(url)
|
95
|
+
@webapp_node = content_service.create_node(label: uri.host, type: :host)
|
96
|
+
|
97
|
+
webapp_node.set_property('qualys.webapp.id', id)
|
98
|
+
webapp_node.set_property('qualys.webapp.name', name)
|
99
|
+
webapp_node.set_property('qualys.webapp.url', url)
|
100
|
+
webapp_node.set_property('qualys.webapp.scope', scope)
|
101
|
+
webapp_node.save!
|
102
|
+
|
103
|
+
logger.info { 'Webapp name: ' + name }
|
104
|
+
logger.info { 'Webapp URL: ' + url }
|
105
|
+
logger.info { 'Webapp scope: ' + scope }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -7,5 +7,7 @@ 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/vuln/importer'
|
13
|
+
require 'dradis/plugins/qualys/was/importer'
|
data/lib/dradis-qualys.rb
CHANGED
data/lib/qualys/element.rb
CHANGED
@@ -1,4 +1,27 @@
|
|
1
1
|
module Qualys
|
2
|
+
|
3
|
+
def self.cleanup_html(source)
|
4
|
+
result = source.dup
|
5
|
+
result.gsub!(/"/, '"')
|
6
|
+
result.gsub!(/</, '<')
|
7
|
+
result.gsub!(/>/, '>')
|
8
|
+
|
9
|
+
result.gsub!(/<p>/i, "\n\n")
|
10
|
+
result.gsub!(/<br>/i, "\n")
|
11
|
+
result.gsub!(/ /, "")
|
12
|
+
result.gsub!(/<a href=\"(.*?)\"\s?target=\"_blank\">(.*?)<\/a>/i) { "\"#{$2.strip}\":#{$1.strip}" }
|
13
|
+
result.gsub!(/<pre>(.*?)<\/pre>/im) { |m| "\n\nbc.. #{$1.strip}\n\np. \n" }
|
14
|
+
result.gsub!(/<b>(.*?)<\/b>/i) { "*#{$1.strip}*" }
|
15
|
+
result.gsub!(/<b>|<\/b>/i, "")
|
16
|
+
result.gsub!(/<i>(.*?)<\/i>/i) { "_#{$1.strip}_" }
|
17
|
+
|
18
|
+
result.gsub!(/<dl>|<\/dl>/i, "\n")
|
19
|
+
result.gsub!(/<dt>(.*?)<\/dt>/i) { "* #{$1.strip}" }
|
20
|
+
result.gsub!(/<dd>(.*?)<\/dd>/i) { "** #{$1.strip}" }
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
|
2
25
|
# This class represents each of the /SCAN/IP/[INFOS|SERVICES|VULNS|PRACTICES]/CAT/[INFO|SERVICE|VULN|PRACTICE]
|
3
26
|
# elements in the Qualys XML document.
|
4
27
|
#
|
@@ -25,7 +48,10 @@ module Qualys
|
|
25
48
|
:consequence, :solution, :compliance, :result,
|
26
49
|
|
27
50
|
# multiple tags
|
28
|
-
:vendor_reference_list, :cve_id_list, :bugtraq_id_list
|
51
|
+
:vendor_reference_list, :cve_id_list, :bugtraq_id_list,
|
52
|
+
|
53
|
+
# category
|
54
|
+
:qualys_collection
|
29
55
|
]
|
30
56
|
end
|
31
57
|
|
@@ -66,10 +92,10 @@ module Qualys
|
|
66
92
|
return @xml.attributes[method_name].value if @xml.attributes.key?(method_name)
|
67
93
|
|
68
94
|
# Then we try simple children tags: TITLE, LAST_UPDATE, CVSS_BASE...
|
69
|
-
tag = @xml.
|
95
|
+
tag = @xml.at_xpath("./#{method_name.upcase}")
|
70
96
|
if tag && !tag.text.blank?
|
71
97
|
if tags_with_html_content.include?(method)
|
72
|
-
return cleanup_html(tag.text)
|
98
|
+
return Qualys::cleanup_html(tag.text)
|
73
99
|
else
|
74
100
|
return tag.text
|
75
101
|
end
|
@@ -77,11 +103,8 @@ module Qualys
|
|
77
103
|
'n/a'
|
78
104
|
end
|
79
105
|
|
80
|
-
|
81
|
-
|
82
|
-
# @xml.xpath("./references/reference").collect{|entry| {:source => entry['source'], :text => entry.text} }
|
83
|
-
elsif method == 'tags'
|
84
|
-
# @xml.xpath("./tags/tag").collect(&:text)
|
106
|
+
if method_name == 'qualys_collection'
|
107
|
+
@xml.name
|
85
108
|
else
|
86
109
|
# nothing found, the tag is valid but not present in this ReportItem
|
87
110
|
return nil
|
@@ -90,27 +113,6 @@ module Qualys
|
|
90
113
|
|
91
114
|
private
|
92
115
|
|
93
|
-
def cleanup_html(source)
|
94
|
-
result = source.dup
|
95
|
-
result.gsub!(/"/, '"')
|
96
|
-
result.gsub!(/</, '<')
|
97
|
-
result.gsub!(/>/, '>')
|
98
|
-
|
99
|
-
result.gsub!(/<p>/i, "\n\n")
|
100
|
-
result.gsub!(/<br>/i, "\n")
|
101
|
-
result.gsub!(/ /, "")
|
102
|
-
result.gsub!(/<a href=\"(.*?)\"\s?target=\"_blank\">(.*?)<\/a>/i) { "\"#{$2.strip}\":#{$1.strip}" }
|
103
|
-
result.gsub!(/<pre>(.*?)<\/pre>/im) { |m| "\n\nbc.. #{$1.strip}\n\np. \n" }
|
104
|
-
result.gsub!(/<b>(.*?)<\/b>/i) { "*#{$1.strip}*" }
|
105
|
-
result.gsub!(/<b>|<\/b>/i, "")
|
106
|
-
result.gsub!(/<i>(.*?)<\/i>/i) { "_#{$1.strip}_" }
|
107
|
-
|
108
|
-
result.gsub!(/<dl>|<\/dl>/i, "\n")
|
109
|
-
result.gsub!(/<dt>(.*?)<\/dt>/i) { "* #{$1.strip}" }
|
110
|
-
result.gsub!(/<dd>(.*?)<\/dd>/i) { "** #{$1.strip}" }
|
111
|
-
result
|
112
|
-
end
|
113
|
-
|
114
116
|
def tags_with_html_content
|
115
117
|
[:consequence, :diagnosis, :solution]
|
116
118
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Qualys::WAS
|
2
|
+
# This class represents each of the WAS_SCAN_REPORT/GLOSSARY/QID_LIST/QID
|
3
|
+
# elements in the Qualys WAS 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 QID
|
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, :cwe, :description, :group, :impact, :owasp, :qid,
|
22
|
+
:severity, :solution, :title, :wasc,
|
23
|
+
|
24
|
+
:cvss_base, :cvss_temporal, :cvss3_base, :cvss3_temporal, :cvss3_vector
|
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
|
+
method_name = method.to_s
|
52
|
+
|
53
|
+
# Then we try simple children tags: TITLE, LAST_UPDATE, CVSS_BASE...
|
54
|
+
tag = @xml.at_xpath("./#{method_name.upcase}")
|
55
|
+
if tag && !tag.text.blank?
|
56
|
+
if tags_with_html_content.include?(method)
|
57
|
+
return Qualys::cleanup_html(tag.text)
|
58
|
+
else
|
59
|
+
return tag.text
|
60
|
+
end
|
61
|
+
else
|
62
|
+
'n/a'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def tags_with_html_content
|
68
|
+
[:description, :impact, :solution]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Qualys::WAS
|
2
|
+
# This class represents each of the WAS_SCAN_REPORT/RESULTS/VULNERABILITY_LIST/
|
3
|
+
# VULNERABILITY elements in the Qualys WAS 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
|
+
:access_paths, :ajax, :authentication, :ignored, :potential, :url
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
# This allows external callers (and specs) to check for implemented
|
26
|
+
# properties
|
27
|
+
def respond_to?(method, include_private=false)
|
28
|
+
return true if supported_tags.include?(method.to_sym)
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
# This method is invoked by Ruby when a method that is not defined in this
|
33
|
+
# instance is called.
|
34
|
+
#
|
35
|
+
# In our case we inspect the @method@ parameter and try to find the
|
36
|
+
# attribute, simple descendent or collection that it maps to in the XML
|
37
|
+
# tree.
|
38
|
+
def method_missing(method, *args)
|
39
|
+
# We could remove this check and return nil for any non-recognized tag.
|
40
|
+
# The problem would be that it would make tricky to debug problems with
|
41
|
+
# typos. For instance: <>.potr would return nil instead of raising an
|
42
|
+
# exception
|
43
|
+
unless supported_tags.include?(method)
|
44
|
+
super
|
45
|
+
return
|
46
|
+
end
|
47
|
+
|
48
|
+
method_name = method.to_s
|
49
|
+
|
50
|
+
# Then we try simple children tags: TITLE, LAST_UPDATE, CVSS_BASE...
|
51
|
+
tag = @xml.at_xpath("./#{method_name.upcase}")
|
52
|
+
if tag && !tag.text.blank?
|
53
|
+
if tags_with_html_content.include?(method)
|
54
|
+
return Qualys::cleanup_html(tag.text)
|
55
|
+
else
|
56
|
+
return tag.text
|
57
|
+
end
|
58
|
+
else
|
59
|
+
'n/a'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def tags_with_html_content
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/tasks/thorfile.rb
CHANGED
@@ -14,8 +14,22 @@ class QualysTasks < Thor
|
|
14
14
|
|
15
15
|
detect_and_set_project_scope
|
16
16
|
|
17
|
-
importer = Dradis::Plugins::Qualys::Importer.new(task_options)
|
17
|
+
importer = Dradis::Plugins::Qualys::Vuln::Importer.new(task_options)
|
18
18
|
importer.import(file: file_path)
|
19
19
|
end
|
20
20
|
|
21
|
+
desc "upload_was FILE", "upload Qualys WAS XML results"
|
22
|
+
def upload_was(file_path)
|
23
|
+
require 'config/environment'
|
24
|
+
|
25
|
+
unless File.exists?(file_path)
|
26
|
+
$stderr.puts "** the file [#{file_path}] does not exist"
|
27
|
+
exit -1
|
28
|
+
end
|
29
|
+
|
30
|
+
detect_and_set_project_scope
|
31
|
+
|
32
|
+
importer = Dradis::Plugins::Qualys::WAS::Importer.new(task_options)
|
33
|
+
importer.import(file: file_path)
|
34
|
+
end
|
21
35
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
2
|
+
<WAS_SCAN_REPORT>
|
3
|
+
<HEADER>
|
4
|
+
<NAME>Scan Report</NAME>
|
5
|
+
<DESCRIPTION>Vulnerabilities of all selected scans are consolidated into one report so that you can view their evolution.</DESCRIPTION>
|
6
|
+
<GENERATION_DATETIME>10 Nov 2021 10:00AM GMT-0500</GENERATION_DATETIME>
|
7
|
+
<COMPANY_INFO>
|
8
|
+
<NAME>Sample Company</NAME>
|
9
|
+
<ADDRESS>Sample Address</ADDRESS>
|
10
|
+
<CITY>Sample City</CITY>
|
11
|
+
<STATE>Sample State</STATE>
|
12
|
+
<COUNTRY>Sample Country</COUNTRY>
|
13
|
+
<ZIP_CODE>00000</ZIP_CODE>
|
14
|
+
</COMPANY_INFO>
|
15
|
+
<USER_INFO>
|
16
|
+
<NAME>Test User</NAME>
|
17
|
+
<USERNAME>test_user</USERNAME>
|
18
|
+
<ROLE>PC User,VM User</ROLE>
|
19
|
+
</USER_INFO>
|
20
|
+
</HEADER>
|
21
|
+
<FILTERS>
|
22
|
+
<FILTER>
|
23
|
+
<NAME>REMEDIATION</NAME>
|
24
|
+
<VALUE>Include patched findings</VALUE>
|
25
|
+
</FILTER>
|
26
|
+
<FILTER>
|
27
|
+
<NAME>REMEDIATION</NAME>
|
28
|
+
<VALUE>Show ignored findings </VALUE>
|
29
|
+
</FILTER>
|
30
|
+
</FILTERS>
|
31
|
+
<TARGET>
|
32
|
+
<SCAN>Test Scan</SCAN>
|
33
|
+
</TARGET>
|
34
|
+
<SUMMARY>
|
35
|
+
<GLOBAL_SUMMARY>
|
36
|
+
<SECURITY_RISK>High</SECURITY_RISK>
|
37
|
+
<VULNERABILITY>31</VULNERABILITY>
|
38
|
+
<SENSITIVE_CONTENT>0</SENSITIVE_CONTENT>
|
39
|
+
<INFORMATION_GATHERED>30</INFORMATION_GATHERED>
|
40
|
+
</GLOBAL_SUMMARY>
|
41
|
+
<SUMMARY_STATS>
|
42
|
+
<SUMMARY_STAT>
|
43
|
+
<SCAN>test Scan</SCAN>
|
44
|
+
<DATE>12 Oct 2021</DATE>
|
45
|
+
<LEVEL5>5</LEVEL5>
|
46
|
+
<LEVEL4>2</LEVEL4>
|
47
|
+
<LEVEL3>9</LEVEL3>
|
48
|
+
<LEVEL2>2</LEVEL2>
|
49
|
+
<LEVEL1>13</LEVEL1>
|
50
|
+
<SENSITIVE_CONTENT>0</SENSITIVE_CONTENT>
|
51
|
+
<INFORMATION_GATHERED>30</INFORMATION_GATHERED>
|
52
|
+
</SUMMARY_STAT>
|
53
|
+
</SUMMARY_STATS>
|
54
|
+
</SUMMARY>
|
55
|
+
<RESULTS>
|
56
|
+
<VULNERABILITY_LIST>
|
57
|
+
<VULNERABILITY>
|
58
|
+
<UNIQUE_ID>test-id</UNIQUE_ID>
|
59
|
+
<ID>1</ID>
|
60
|
+
<DETECTION_ID>1</DETECTION_ID>
|
61
|
+
<QID>6</QID>
|
62
|
+
<URL>http://example.com</URL>
|
63
|
+
<ACCESS_PATH>
|
64
|
+
<URL>http://example.com</URL>
|
65
|
+
</ACCESS_PATH>
|
66
|
+
<AJAX>false</AJAX>
|
67
|
+
<AUTHENTICATION>Not Required</AUTHENTICATION>
|
68
|
+
<DETECTION_DATE>21 Aug 2021 10:00PM GMT-0500</DETECTION_DATE>
|
69
|
+
<POTENTIAL>false</POTENTIAL>
|
70
|
+
<PAYLOADS>
|
71
|
+
<PAYLOAD>
|
72
|
+
<NUM>1</NUM>
|
73
|
+
<PAYLOAD>N/A</PAYLOAD>
|
74
|
+
<REQUEST>
|
75
|
+
<METHOD>GET</METHOD>
|
76
|
+
<URL>http://example.com</URL>
|
77
|
+
<HEADERS>
|
78
|
+
<HEADER>
|
79
|
+
<key>Host</key>
|
80
|
+
<value><![CDATA[ example.com ]]></value>
|
81
|
+
</HEADER>
|
82
|
+
<HEADER>
|
83
|
+
<key>User-Agent</key>
|
84
|
+
<value>user-agent</value>
|
85
|
+
</HEADER>
|
86
|
+
<HEADER>
|
87
|
+
<key>Accept</key>
|
88
|
+
<value><![CDATA[ */*
|
89
|
+
</HEADER>
|
90
|
+
</HEADERS>
|
91
|
+
<BODY></BODY>
|
92
|
+
</REQUEST>
|
93
|
+
<RESPONSE>
|
94
|
+
<CONTENTS base64="true"></CONTENTS>
|
95
|
+
</RESPONSE>
|
96
|
+
</PAYLOAD>
|
97
|
+
</PAYLOADS>
|
98
|
+
<IGNORED>false</IGNORED>
|
99
|
+
</VULNERABILITY>
|
100
|
+
</VULNERABILITY_LIST>
|
101
|
+
</RESULTS>
|
102
|
+
<GLOSSARY>
|
103
|
+
<QID_LIST>
|
104
|
+
<QID>
|
105
|
+
<QID>6</QID>
|
106
|
+
<CATEGORY>Information Gathered</CATEGORY>
|
107
|
+
<SEVERITY>1</SEVERITY>
|
108
|
+
<TITLE>DNS Host Name</TITLE>
|
109
|
+
<GROUP>DIAG</GROUP>
|
110
|
+
<DESCRIPTION>The fully qualified domain name of this host, if it was obtained from a DNS server, is displayed in the RESULT section.</DESCRIPTION>
|
111
|
+
<IMPACT>N/A</IMPACT>
|
112
|
+
<SOLUTION>N/A</SOLUTION>
|
113
|
+
</QID>
|
114
|
+
</QID_LIST>
|
115
|
+
</GLOSSARY>
|
116
|
+
<APPENDIX>
|
117
|
+
<WEBAPP>
|
118
|
+
<ID>1</ID>
|
119
|
+
<NAME>Test</NAME>
|
120
|
+
<URL>http://example.com</URL>
|
121
|
+
<OWNER>Test User</OWNER>
|
122
|
+
<SCOPE>Limit to URL hostname</SCOPE>
|
123
|
+
<CUSTOM_ATTRIBUTES/>
|
124
|
+
<TAGS/>
|
125
|
+
</WEBAPP>
|
126
|
+
</APPENDIX>
|
127
|
+
</WAS_SCAN_REPORT>
|
@@ -5,37 +5,15 @@ module Dradis::Plugins
|
|
5
5
|
describe 'Qualys upload plugin' do
|
6
6
|
before(:each) do
|
7
7
|
# Stub template service
|
8
|
-
templates_dir = File.expand_path('
|
8
|
+
templates_dir = File.expand_path('../../../../templates', __FILE__)
|
9
9
|
expect_any_instance_of(Dradis::Plugins::TemplateService)
|
10
10
|
.to receive(:default_templates_dir).and_return(templates_dir)
|
11
11
|
|
12
|
-
|
13
|
-
plugin = Dradis::Plugins::Qualys
|
12
|
+
stub_content_service
|
14
13
|
|
15
|
-
@
|
16
|
-
logger: Logger.new(STDOUT),
|
17
|
-
plugin: plugin
|
18
|
-
)
|
19
|
-
|
20
|
-
@importer = Dradis::Plugins::Qualys::Importer.new(
|
14
|
+
@importer = Dradis::Plugins::Qualys::Vuln::Importer.new(
|
21
15
|
content_service: @content_service
|
22
16
|
)
|
23
|
-
|
24
|
-
# Stub dradis-plugins methods
|
25
|
-
#
|
26
|
-
# They return their argument hashes as objects mimicking
|
27
|
-
# Nodes, Issues, etc
|
28
|
-
allow(@content_service).to receive(:create_node) do |args|
|
29
|
-
obj = OpenStruct.new(args)
|
30
|
-
obj.define_singleton_method(:set_property) { |_, __| }
|
31
|
-
obj
|
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
|
39
17
|
end
|
40
18
|
|
41
19
|
let(:example_xml) { 'spec/fixtures/files/simple.xml' }
|
@@ -84,12 +62,12 @@ module Dradis::Plugins
|
|
84
62
|
expect_to_create_issue_with(
|
85
63
|
text: "Apache 1.3 HTTP Server Expect Header Cross-Site Scripting"
|
86
64
|
)
|
87
|
-
|
65
|
+
|
88
66
|
expect_to_create_issue_with(
|
89
67
|
text: "Apache Web Server ETag Header Information Disclosure Weakness",
|
90
68
|
text: "OpenBSD has released a \"patch\":ftp://ftp.openbsd.org/pub/OpenBSD/patches/3.2/common/008_httpd.patch that fixes this vulnerability. After installing the patch, inode numbers returned from the server are encoded using a private hash to avoid the release of sensitive information.\n\n\n\nCustomers"
|
91
69
|
)
|
92
|
-
|
70
|
+
|
93
71
|
run_import!
|
94
72
|
end
|
95
73
|
|
@@ -165,28 +143,5 @@ module Dradis::Plugins
|
|
165
143
|
@importer.import(file: 'spec/fixtures/files/no_result.xml')
|
166
144
|
end
|
167
145
|
end
|
168
|
-
|
169
|
-
|
170
|
-
def expect_to_create_node_with(label:)
|
171
|
-
expect(@content_service).to receive(:create_node).with(
|
172
|
-
hash_including label: label
|
173
|
-
).once
|
174
|
-
end
|
175
|
-
|
176
|
-
def expect_to_create_issue_with(text:)
|
177
|
-
expect(@content_service).to receive(:create_issue) do |args|
|
178
|
-
expect(args[:text]).to include text
|
179
|
-
OpenStruct.new(args)
|
180
|
-
end.once
|
181
|
-
end
|
182
|
-
|
183
|
-
def expect_to_create_evidence_with(content:, issue:, node_label:)
|
184
|
-
expect(@content_service).to receive(:create_evidence) do |args|
|
185
|
-
expect(args[:content]).to include content
|
186
|
-
expect(args[:issue].text).to include issue
|
187
|
-
expect(args[:node].label).to eq node_label
|
188
|
-
end.once
|
189
|
-
end
|
190
|
-
|
191
146
|
end
|
192
147
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Dradis::Plugins
|
5
|
+
describe 'Qualys upload plugin' do
|
6
|
+
before(:each) do
|
7
|
+
# Stub template service
|
8
|
+
templates_dir = File.expand_path('../../../../templates', __FILE__)
|
9
|
+
expect_any_instance_of(Dradis::Plugins::TemplateService)
|
10
|
+
.to receive(:default_templates_dir).and_return(templates_dir)
|
11
|
+
|
12
|
+
stub_content_service
|
13
|
+
|
14
|
+
@importer = Dradis::Plugins::Qualys::WAS::Importer.new(
|
15
|
+
content_service: @content_service
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:example_xml) { 'spec/fixtures/files/simple_was.xml' }
|
20
|
+
let(:run_import!) { @importer.import(file: example_xml) }
|
21
|
+
|
22
|
+
it 'creates nodes as needed' do
|
23
|
+
expect_to_create_node_with(label: 'example.com')
|
24
|
+
run_import!
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'creates issues as needed' do
|
28
|
+
expect_to_create_issue_with(text: 'DNS Host Name')
|
29
|
+
run_import!
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'creates evidence as needed' do
|
33
|
+
expect_to_create_evidence_with(
|
34
|
+
content: 'http://example.com',
|
35
|
+
issue: 'DNS Host Name',
|
36
|
+
node_label: 'example.com'
|
37
|
+
)
|
38
|
+
run_import!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,50 @@
|
|
1
|
+
module SpecMacros
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def stub_content_service
|
5
|
+
# Init services
|
6
|
+
plugin = Dradis::Plugins::Qualys
|
7
|
+
|
8
|
+
@content_service = Dradis::Plugins::ContentService::Base.new(
|
9
|
+
logger: Logger.new(STDOUT),
|
10
|
+
plugin: plugin
|
11
|
+
)
|
12
|
+
|
13
|
+
# Stub dradis-plugins methods
|
14
|
+
#
|
15
|
+
# They return their argument hashes as objects mimicking
|
16
|
+
# Nodes, Issues, etc
|
17
|
+
allow(@content_service).to receive(:create_node) do |args|
|
18
|
+
obj = OpenStruct.new(args)
|
19
|
+
obj.define_singleton_method(:set_property) { |_, __| }
|
20
|
+
obj
|
21
|
+
end
|
22
|
+
allow(@content_service).to receive(:create_issue) do |args|
|
23
|
+
OpenStruct.new(args)
|
24
|
+
end
|
25
|
+
allow(@content_service).to receive(:create_evidence) do |args|
|
26
|
+
OpenStruct.new(args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def expect_to_create_node_with(label:)
|
31
|
+
expect(@content_service).to receive(:create_node).with(
|
32
|
+
hash_including label: label
|
33
|
+
).once
|
34
|
+
end
|
35
|
+
|
36
|
+
def expect_to_create_issue_with(text:)
|
37
|
+
expect(@content_service).to receive(:create_issue) do |args|
|
38
|
+
expect(args[:text]).to include text
|
39
|
+
OpenStruct.new(args)
|
40
|
+
end.once
|
41
|
+
end
|
42
|
+
|
43
|
+
def expect_to_create_evidence_with(content:, issue:, node_label:)
|
44
|
+
expect(@content_service).to receive(:create_evidence) do |args|
|
45
|
+
expect(args[:content]).to include content
|
46
|
+
expect(args[:issue].text).to include issue
|
47
|
+
expect(args[:node].label).to eq node_label
|
48
|
+
end.once
|
49
|
+
end
|
50
|
+
end
|
data/templates/element.fields
CHANGED
data/templates/element.template
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
<VULNERABILITY>
|
2
|
+
<UNIQUE_ID>db9bd89e-a8d8-402d-a6ca-8f6ff8be426f</UNIQUE_ID>
|
3
|
+
<ID>827065910</ID>
|
4
|
+
<DETECTION_ID>20879664</DETECTION_ID>
|
5
|
+
<QID>150124</QID>
|
6
|
+
<URL>http://demo.hackmebank.net/index.jsp?content=personal_loans.htm</URL>
|
7
|
+
<ACCESS_PATH>
|
8
|
+
<URL>http://demo.hackmebank.net/index.jsp</URL>
|
9
|
+
</ACCESS_PATH>
|
10
|
+
<AJAX>false</AJAX>
|
11
|
+
<AUTHENTICATION>Not Required</AUTHENTICATION>
|
12
|
+
<DETECTION_DATE>11 Oct 2021 07:16PM GMT-0500</DETECTION_DATE>
|
13
|
+
<POTENTIAL>false</POTENTIAL>
|
14
|
+
<PAYLOADS>
|
15
|
+
<PAYLOAD>
|
16
|
+
<NUM>1</NUM>
|
17
|
+
<PAYLOAD>N/A</PAYLOAD>
|
18
|
+
<REQUEST>
|
19
|
+
<METHOD>GET</METHOD>
|
20
|
+
<URL>http://demo.hackmebank.net/index.jsp?content=business.htm</URL>
|
21
|
+
<HEADERS>
|
22
|
+
<HEADER>
|
23
|
+
<key>Host</key>
|
24
|
+
<value><![CDATA[ demo.hackmebank.net
|
25
|
+
</HEADER>
|
26
|
+
<HEADER>
|
27
|
+
<key>User-Agent</key>
|
28
|
+
<value><![CDATA[ Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15
|
29
|
+
</HEADER>
|
30
|
+
<HEADER>
|
31
|
+
<key>Accept</key>
|
32
|
+
<value><![CDATA[ */*
|
33
|
+
</HEADER>
|
34
|
+
</HEADERS>
|
35
|
+
<BODY></BODY>
|
36
|
+
</REQUEST>
|
37
|
+
<RESPONSE>
|
38
|
+
<CONTENTS base64="true"><![CDATA[VGhlIFVSSSB3YXMgZnJhbWVkLgo=
|
39
|
+
]]></CONTENTS>
|
40
|
+
</RESPONSE>
|
41
|
+
</PAYLOAD>
|
42
|
+
</PAYLOADS>
|
43
|
+
<IGNORED>false</IGNORED>
|
44
|
+
</VULNERABILITY>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
was-issue.category
|
2
|
+
was-issue.cvss_base
|
3
|
+
was-issue.cvss_temporal
|
4
|
+
was-issue.cvss3_base
|
5
|
+
was-issue.cvss3_temporal
|
6
|
+
was-issue.cvss3_vector
|
7
|
+
was-issue.cwe
|
8
|
+
was-issue.description
|
9
|
+
was-issue.group
|
10
|
+
was-issue.impact
|
11
|
+
was-issue.owasp
|
12
|
+
was-issue.qid
|
13
|
+
was-issue.severity
|
14
|
+
was-issue.solution
|
15
|
+
was-issue.title
|
16
|
+
was-issue.wasc
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<QID>
|
2
|
+
<QID>150001</QID>
|
3
|
+
<CATEGORY>Confirmed Vulnerability</CATEGORY>
|
4
|
+
<SEVERITY>5</SEVERITY>
|
5
|
+
<TITLE>Reflected Cross-Site Scripting (XSS) Vulnerabilities</TITLE>
|
6
|
+
<GROUP>XSS</GROUP>
|
7
|
+
<OWASP>A7</OWASP>
|
8
|
+
<WASC>WASC-8</WASC>
|
9
|
+
<CWE>CWE-79</CWE>
|
10
|
+
<CVSS_BASE>4.3</CVSS_BASE>
|
11
|
+
<CVSS_TEMPORAL>3.9</CVSS_TEMPORAL>
|
12
|
+
<CVSS_V3>
|
13
|
+
<BASE>6.1</BASE>
|
14
|
+
<TEMPORAL>5.8</TEMPORAL>
|
15
|
+
<ATTACK_VECTOR>Network</ATTACK_VECTOR>
|
16
|
+
</CVSS_V3>
|
17
|
+
<DESCRIPTION><![CDATA[XSS vulnerabilities occur when the Web application echoes user-supplied data in an HTML response sent to the Web browser. For example, a Web application might include the user's name as part of a welcome message or display a home address when confirming a shipping destination. If the user-supplied data contain characters that are interpreted as part of an HTML element instead of literal text, then an attacker can modify the HTML that is received by the victim's Web browser.
|
18
|
+
<P>
|
19
|
+
The XSS payload is echoed in HTML document returned by the request. An XSS payload may consist of HTML, JavaScript or other content that will be rendered by the browser. In order to exploit this vulnerability, a malicious user would need to trick a victim into visiting the URL with the XSS payload.]]></DESCRIPTION>
|
20
|
+
<IMPACT>XSS exploits pose a significant threat to a Web application, its users and user data. XSS exploits target the users of a Web application rather than the Web application itself. An exploit can lead to theft of the user's credentials and personal or financial information. Complex exploits and attack scenarios are possible via XSS because it enables an attacker to execute dynamic code. Consequently, any capability or feature available to the Web browser (for example HTML, JavaScript, Flash and Java applets) can be used to as a part of a compromise.</IMPACT>
|
21
|
+
<SOLUTION><![CDATA[Filter all data collected from the client including user-supplied content and browser content such as Referrer and User-Agent headers.
|
22
|
+
<P>
|
23
|
+
Any data collected from the client and displayed in a Web page should be HTML-encoded to ensure the content is rendered as text instead of an HTML element or JavaScript.]]></SOLUTION>
|
24
|
+
</QID>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#[Title]#
|
2
|
+
%was-issue.title%
|
3
|
+
|
4
|
+
#[Severity]#
|
5
|
+
%was-issue.severity%
|
6
|
+
|
7
|
+
#[Categories]#
|
8
|
+
Category: %was-issue.category%
|
9
|
+
Group: %was-issue.group%
|
10
|
+
OWASP: %was-issue.owasp%
|
11
|
+
CWE: %was-issue.cwe%
|
12
|
+
|
13
|
+
#[CVSSv3.Vector]#
|
14
|
+
%was-issue.cvss3_vector%
|
15
|
+
|
16
|
+
#[CVSSv3.BaseScore]#
|
17
|
+
%was-issue.cvss3_base%
|
18
|
+
|
19
|
+
#[CVSSv3.TemporalScore]#
|
20
|
+
%was-issue.cvss3_temporal%
|
21
|
+
|
22
|
+
#[Description]#
|
23
|
+
%was-issue.description%
|
24
|
+
|
25
|
+
%was-issue.impact%
|
26
|
+
|
27
|
+
#[Solution]#
|
28
|
+
%was-issue.solution%
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dradis-qualys
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Martin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dradis-plugins
|
@@ -119,23 +119,35 @@ files:
|
|
119
119
|
- lib/dradis/plugins/qualys/engine.rb
|
120
120
|
- lib/dradis/plugins/qualys/field_processor.rb
|
121
121
|
- lib/dradis/plugins/qualys/gem_version.rb
|
122
|
-
- lib/dradis/plugins/qualys/importer.rb
|
123
122
|
- lib/dradis/plugins/qualys/version.rb
|
123
|
+
- lib/dradis/plugins/qualys/vuln/importer.rb
|
124
|
+
- lib/dradis/plugins/qualys/was/importer.rb
|
124
125
|
- lib/qualys/element.rb
|
126
|
+
- lib/qualys/was/qid.rb
|
127
|
+
- lib/qualys/was/vulnerability.rb
|
125
128
|
- lib/tasks/thorfile.rb
|
126
129
|
- spec/.keep
|
127
130
|
- spec/fixtures/files/no_result.xml
|
128
131
|
- spec/fixtures/files/simple.xml
|
132
|
+
- spec/fixtures/files/simple_was.xml
|
129
133
|
- spec/fixtures/files/two_hosts_common_issue.xml
|
130
134
|
- spec/qualys/element_spec.rb
|
131
|
-
- spec/qualys/importer_spec.rb
|
135
|
+
- spec/qualys/vuln/importer_spec.rb
|
136
|
+
- spec/qualys/was/importer_spec.rb
|
132
137
|
- spec/spec_helper.rb
|
138
|
+
- spec/support/spec_macros.rb
|
133
139
|
- templates/element.fields
|
134
140
|
- templates/element.sample
|
135
141
|
- templates/element.template
|
136
142
|
- templates/evidence.fields
|
137
143
|
- templates/evidence.sample
|
138
144
|
- templates/evidence.template
|
145
|
+
- templates/was-evidence.fields
|
146
|
+
- templates/was-evidence.sample
|
147
|
+
- templates/was-evidence.template
|
148
|
+
- templates/was-issue.fields
|
149
|
+
- templates/was-issue.sample
|
150
|
+
- templates/was-issue.template
|
139
151
|
homepage: http://dradisframework.org
|
140
152
|
licenses:
|
141
153
|
- GPL-2
|
@@ -155,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
167
|
- !ruby/object:Gem::Version
|
156
168
|
version: '0'
|
157
169
|
requirements: []
|
158
|
-
rubygems_version: 3.1.
|
170
|
+
rubygems_version: 3.1.4
|
159
171
|
signing_key:
|
160
172
|
specification_version: 4
|
161
173
|
summary: Qualys add-on for the Dradis Framework.
|
@@ -163,7 +175,10 @@ test_files:
|
|
163
175
|
- spec/.keep
|
164
176
|
- spec/fixtures/files/no_result.xml
|
165
177
|
- spec/fixtures/files/simple.xml
|
178
|
+
- spec/fixtures/files/simple_was.xml
|
166
179
|
- spec/fixtures/files/two_hosts_common_issue.xml
|
167
180
|
- spec/qualys/element_spec.rb
|
168
|
-
- spec/qualys/importer_spec.rb
|
181
|
+
- spec/qualys/vuln/importer_spec.rb
|
182
|
+
- spec/qualys/was/importer_spec.rb
|
169
183
|
- spec/spec_helper.rb
|
184
|
+
- spec/support/spec_macros.rb
|
@@ -1,88 +0,0 @@
|
|
1
|
-
module Dradis::Plugins::Qualys
|
2
|
-
class Importer < Dradis::Plugins::Upload::Importer
|
3
|
-
|
4
|
-
attr_accessor :host_node
|
5
|
-
|
6
|
-
# The framework will call this function if the user selects this plugin from
|
7
|
-
# the dropdown list and uploads a file.
|
8
|
-
# @returns true if the operation was successful, false otherwise
|
9
|
-
def import(params={})
|
10
|
-
file_content = File.read( params[:file] )
|
11
|
-
|
12
|
-
logger.info{'Parsing Qualys output file...'}
|
13
|
-
@doc = Nokogiri::XML( file_content )
|
14
|
-
logger.info{'Done.'}
|
15
|
-
|
16
|
-
if @doc.root.name != 'SCAN'
|
17
|
-
error = "No scan results were detected in the uploaded file. Ensure you uploaded a Qualys XML file."
|
18
|
-
logger.fatal{ error }
|
19
|
-
content_service.create_note text: error
|
20
|
-
return false
|
21
|
-
end
|
22
|
-
|
23
|
-
@doc.xpath('SCAN/IP').each do |xml_host|
|
24
|
-
process_ip(xml_host)
|
25
|
-
end
|
26
|
-
|
27
|
-
return true
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def process_ip(xml_host)
|
33
|
-
host_ip = xml_host['value']
|
34
|
-
logger.info{ "Host: %s" % host_ip }
|
35
|
-
|
36
|
-
self.host_node = content_service.create_node(label: host_ip, type: :host)
|
37
|
-
|
38
|
-
host_node.set_property(:ip, host_ip)
|
39
|
-
host_node.set_property(:hostname, xml_host['name'])
|
40
|
-
if (xml_os = xml_host.xpath('OS')) && xml_os.any?
|
41
|
-
host_node.set_property(:os, xml_os.text)
|
42
|
-
end
|
43
|
-
host_node.save
|
44
|
-
|
45
|
-
# We treat INFOS, SERVICES, PRACTICES, and VULNS the same way
|
46
|
-
# All of these are imported into Dradis as Issues
|
47
|
-
['INFOS', 'SERVICES', 'PRACTICES', 'VULNS'].each do |collection|
|
48
|
-
xml_host.xpath(collection).each do |xml_collection|
|
49
|
-
process_collection(collection, xml_collection)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def process_collection(collection, xml_collection)
|
55
|
-
xml_cats = xml_collection.xpath('CAT')
|
56
|
-
|
57
|
-
xml_cats.each do |xml_cat|
|
58
|
-
logger.info{ "\t#{ collection } - #{ xml_cat['value'] }" }
|
59
|
-
|
60
|
-
empty_dup_xml_cat = xml_cat.dup
|
61
|
-
empty_dup_xml_cat.children.remove
|
62
|
-
|
63
|
-
# For each INFOS/CAT/INFO, SERVICES/CAT/SERVICE, VULNS/CAT/VULN, etc.
|
64
|
-
xml_cat.xpath(collection.chop).each do |xml_element|
|
65
|
-
dup_xml_cat = empty_dup_xml_cat.dup
|
66
|
-
dup_xml_cat.add_child(xml_element.dup)
|
67
|
-
cat_number = xml_element[:number]
|
68
|
-
|
69
|
-
process_vuln(collection, cat_number, dup_xml_cat)
|
70
|
-
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# Takes a <CAT> element containing a single <VULN> element and processes an
|
76
|
-
# Issue and Evidence template out of it.
|
77
|
-
def process_vuln(collection, vuln_number, xml_cat)
|
78
|
-
logger.info{ "\t\t => Creating new issue (plugin_id: #{ vuln_number })" }
|
79
|
-
issue_text = template_service.process_template(template: 'element', data: xml_cat)
|
80
|
-
issue_text << "\n\n#[qualys_collection]#\n#{ collection }"
|
81
|
-
issue = content_service.create_issue(text: issue_text, id: vuln_number)
|
82
|
-
|
83
|
-
logger.info{ "\t\t => Creating new evidence" }
|
84
|
-
evidence_content = template_service.process_template(template: 'evidence', data: xml_cat)
|
85
|
-
content_service.create_evidence(issue: issue, node: self.host_node, content: evidence_content)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|