dradis-saint 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/CHANGELOG.md +7 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +20 -0
- data/LICENSE +339 -0
- data/README.md +25 -0
- data/Rakefile +1 -0
- data/dradis-saint.gemspec +28 -0
- data/lib/dradis-saint.rb +7 -0
- data/lib/dradis/plugins/saint.rb +11 -0
- data/lib/dradis/plugins/saint/engine.rb +13 -0
- data/lib/dradis/plugins/saint/field_processor.rb +31 -0
- data/lib/dradis/plugins/saint/gem_version.rb +18 -0
- data/lib/dradis/plugins/saint/importer.rb +130 -0
- data/lib/dradis/plugins/saint/version.rb +11 -0
- data/lib/saint/base.rb +29 -0
- data/lib/saint/evidence.rb +18 -0
- data/lib/saint/vulnerability.rb +15 -0
- data/lib/tasks/thorfile.rb +19 -0
- data/spec/dradis/plugins/saint/field_processor_spec.rb +39 -0
- data/spec/dradis/plugins/saint/importer_spec.rb +33 -0
- data/spec/fixtures/files/evidence-01.xml +8 -0
- data/spec/fixtures/files/full_report.xml +45 -0
- data/spec/fixtures/files/host-01.xml +5 -0
- data/spec/fixtures/files/saint_metasploitable_sample.xml +718 -0
- data/spec/fixtures/files/vulnerability-01.xml +17 -0
- data/spec/saint/evidence_spec.rb +8 -0
- data/spec/saint/host_spec.rb +8 -0
- data/spec/saint/vulnerability_spec.rb +8 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/xml_element.rb +10 -0
- data/templates/evidence.fields +5 -0
- data/templates/evidence.sample +8 -0
- data/templates/evidence.template +14 -0
- data/templates/vulnerability.fields +14 -0
- data/templates/vulnerability.sample +35 -0
- data/templates/vulnerability.template +41 -0
- metadata +165 -0
data/lib/dradis-saint.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Dradis
|
|
2
|
+
module Plugins
|
|
3
|
+
module Saint
|
|
4
|
+
|
|
5
|
+
class FieldProcessor < Dradis::Plugins::Upload::FieldProcessor
|
|
6
|
+
ALLOWED_DATA_NAMES = %w{evidence vulnerability host}
|
|
7
|
+
|
|
8
|
+
def post_initialize(args={})
|
|
9
|
+
raise 'Unhandled data name!' unless ALLOWED_DATA_NAMES.include?(data.name)
|
|
10
|
+
@saint_object =
|
|
11
|
+
"::Saint::#{data.name.capitalize}".constantize.new(data)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def value(args={})
|
|
15
|
+
field = args[:field]
|
|
16
|
+
_, name = field.split('.')
|
|
17
|
+
|
|
18
|
+
# We cannot send the message 'class' to the saint_object because it
|
|
19
|
+
# evaluates to the object's Ruby class. We temporarily rename the
|
|
20
|
+
# field to 'vuln_class' and switch it back later when needed.
|
|
21
|
+
if name == 'class'
|
|
22
|
+
name = 'vuln_class'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@saint_object.try(name) || 'n/a'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Dradis
|
|
2
|
+
module Plugins
|
|
3
|
+
module Saint
|
|
4
|
+
def self.gem_version
|
|
5
|
+
Gem::Version.new VERSION::STRING
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module VERSION
|
|
9
|
+
MAJOR = 3
|
|
10
|
+
MINOR = 10
|
|
11
|
+
TINY = 0
|
|
12
|
+
PRE = nil
|
|
13
|
+
|
|
14
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
module Dradis::Plugins::Saint
|
|
2
|
+
class Importer < Dradis::Plugins::Upload::Importer
|
|
3
|
+
def import(params={})
|
|
4
|
+
@issues = {}
|
|
5
|
+
@hosts = {}
|
|
6
|
+
file_content = File.read(params[:file])
|
|
7
|
+
|
|
8
|
+
logger.info {'Parsing SAINT output file...'}
|
|
9
|
+
doc = Nokogiri::XML( file_content )
|
|
10
|
+
logger.info{'Done.'}
|
|
11
|
+
|
|
12
|
+
if doc.xpath('/report').empty?
|
|
13
|
+
error = "No reports were detected in the uploaded file (/report). Ensure you uploaded a SAINT XML report."
|
|
14
|
+
logger.fatal{ error }
|
|
15
|
+
content_service.create_note text: error
|
|
16
|
+
return false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
doc.xpath('/report').each do |xml_report|
|
|
20
|
+
logger.info {'Processing report...'}
|
|
21
|
+
|
|
22
|
+
# Process <host> tags
|
|
23
|
+
xml_report.xpath('./overview/hosts/host').each do |host|
|
|
24
|
+
process_host_item(host)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Process <vulnerability> tags
|
|
28
|
+
xml_report.xpath('./details/vulnerability').each do |vuln|
|
|
29
|
+
process_vuln_issue(vuln)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Process <vulnerabilities> tag
|
|
33
|
+
xml_report.xpath('./overview/vulnerabilities/host_info').each do |xml_host_info|
|
|
34
|
+
host_name = xml_host_info.xpath('./hostname').first.text
|
|
35
|
+
xml_host_info.xpath('./vulnerability').each do |evidence|
|
|
36
|
+
process_evidence(evidence, host_name)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
logger.info {'Report processed...'}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def process_evidence(xml_evidence, host_node_name)
|
|
49
|
+
# Associate the xml tag as evidence
|
|
50
|
+
xml_evidence.name = 'evidence'
|
|
51
|
+
evidence_desc = xml_evidence.xpath('./description').first.text
|
|
52
|
+
|
|
53
|
+
# Find the host node
|
|
54
|
+
host_node = @hosts[host_node_name]
|
|
55
|
+
|
|
56
|
+
if !host_node
|
|
57
|
+
logger.error { "[WARNING] Cannot find an associated host for '#{evidence_desc}'." }
|
|
58
|
+
return
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Find the related issue
|
|
62
|
+
issue_plugin_id = Digest::SHA1.hexdigest(evidence_desc)
|
|
63
|
+
issue = @issues[issue_plugin_id]
|
|
64
|
+
|
|
65
|
+
evidence_text = template_service.process_template(template: 'evidence', data: xml_evidence)
|
|
66
|
+
|
|
67
|
+
if issue
|
|
68
|
+
# Create Dradis evidence
|
|
69
|
+
logger.info{ "\t\t => Creating new evidence..." }
|
|
70
|
+
content_service.create_evidence(issue: issue, node: host_node, content: evidence_text)
|
|
71
|
+
else
|
|
72
|
+
# Create Note in Host
|
|
73
|
+
logger.info{ "\t\t => Creating note for host node..." }
|
|
74
|
+
note_text = "#[Title]#\n#{evidence_desc}\n\n" + evidence_text
|
|
75
|
+
content_service.create_note(text: note_text, node: host_node)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def process_host_item(xml_host)
|
|
80
|
+
# Create Dradis node
|
|
81
|
+
host_name = xml_host.xpath('./hostname').first.text || "Unnamed host"
|
|
82
|
+
host_node = content_service.create_node(label: host_name, type: :host)
|
|
83
|
+
logger.info{ "\tHost: #{host_name}" }
|
|
84
|
+
|
|
85
|
+
# Save the host for later to be linked to evidences
|
|
86
|
+
@hosts[host_name] = host_node
|
|
87
|
+
|
|
88
|
+
# Add properties to the node
|
|
89
|
+
if xml_host.xpath('./ipaddr').first
|
|
90
|
+
host_node.set_property(:ip, xml_host.xpath('./ipaddr').first.text)
|
|
91
|
+
end
|
|
92
|
+
if xml_host.xpath('./hosttype').first
|
|
93
|
+
host_node.set_property(:os, xml_host.xpath('./hosttype').first.text)
|
|
94
|
+
end
|
|
95
|
+
host_node.set_property(:hostname, host_name)
|
|
96
|
+
host_node.save
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def process_vuln_issue(xml_vuln)
|
|
100
|
+
element_desc = xml_vuln.xpath('./description').first.text
|
|
101
|
+
|
|
102
|
+
# Check if the vulnerability is a real issue or a service
|
|
103
|
+
if real_issue?(xml_vuln)
|
|
104
|
+
# Create Dradis Issue
|
|
105
|
+
logger.info{ "\t\t => Creating new issue..." }
|
|
106
|
+
plugin_id = Digest::SHA1.hexdigest(element_desc)
|
|
107
|
+
|
|
108
|
+
issue_text = template_service.process_template(template: 'vulnerability', data: xml_vuln)
|
|
109
|
+
issue = content_service.create_issue(text: issue_text, id: plugin_id)
|
|
110
|
+
else
|
|
111
|
+
# Create Note in Host
|
|
112
|
+
logger.info{ "\t\t => Creating note for host node..." }
|
|
113
|
+
|
|
114
|
+
note_details = xml_vuln.xpath('./vuln_details').first.text
|
|
115
|
+
note_text = "#[Title]#\n#{element_desc}\n\n" + note_details
|
|
116
|
+
|
|
117
|
+
host_name = xml_vuln.xpath('./hostname').first.text || "Unnamed host"
|
|
118
|
+
host_node = @hosts[host_name]
|
|
119
|
+
content_service.create_note(text: note_text, node: host_node)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Save the issue for later to be linked to evidences
|
|
123
|
+
@issues[plugin_id] = issue
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def real_issue?(xml_vuln)
|
|
127
|
+
xml_vuln.xpath('./severity').first.text != 'Service'
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
data/lib/saint/base.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Saint
|
|
2
|
+
class Base
|
|
3
|
+
def initialize(xml_node)
|
|
4
|
+
@xml = xml_node
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def supported_tags
|
|
8
|
+
[]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def respond_to?(method, include_private=false)
|
|
12
|
+
return true if supported_tags.include?(method.to_sym)
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def method_missing(method, *args)
|
|
17
|
+
unless supported_tags.include?(method)
|
|
18
|
+
super
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
return process_field_value(method)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def process_field_value(method)
|
|
26
|
+
raise "Method #process_field_value not overridden!"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Saint
|
|
2
|
+
class Evidence < Base
|
|
3
|
+
def supported_tags
|
|
4
|
+
[ :port, :severity, :vuln_class, :cve, :cvss_base_score ]
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def process_field_value(method)
|
|
8
|
+
# We cannot send the message 'class' to the saint_object because it
|
|
9
|
+
# evaluates to the object's Ruby class. We temporarily rename the
|
|
10
|
+
# field to 'vuln_class' and switch it back later when needed.
|
|
11
|
+
if method == :vuln_class
|
|
12
|
+
method = :class
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
@xml.xpath("./#{method.to_s}").first.try(:text)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Saint
|
|
2
|
+
class Vulnerability < Base
|
|
3
|
+
def supported_tags
|
|
4
|
+
[
|
|
5
|
+
:description, :hostname, :ipaddr, :hosttype, :scan_time, :status,
|
|
6
|
+
:severity, :cve, :cvss_base_score, :impact, :background, :problem,
|
|
7
|
+
:resolution, :reference
|
|
8
|
+
]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def process_field_value(method)
|
|
12
|
+
@xml.xpath("./#{method.to_s}").first.try(:text)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class SaintTasks < Thor
|
|
2
|
+
include Rails.application.config.dradis.thor_helper_module
|
|
3
|
+
|
|
4
|
+
namespace "dradis:plugins:saint"
|
|
5
|
+
|
|
6
|
+
desc "upload FILE", "upload Saint XML file"
|
|
7
|
+
def upload(file_path)
|
|
8
|
+
require 'config/environment'
|
|
9
|
+
|
|
10
|
+
unless File.exists?(file_path)
|
|
11
|
+
$stderr.puts "** the file [#{file_path}] does not exist"
|
|
12
|
+
exit(-1)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
detect_and_set_project_scope
|
|
16
|
+
importer = Dradis::Plugins::Saint::Importer.new(task_options)
|
|
17
|
+
importer.import(file: file_path)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Dradis::Plugins::Saint::FieldProcessor do
|
|
4
|
+
let (:xml_file) { File.expand_path('spec/fixtures/files/full_report.xml') }
|
|
5
|
+
|
|
6
|
+
before do
|
|
7
|
+
@doc = Nokogiri::XML(File.read(xml_file))
|
|
8
|
+
@doc = @doc.xpath('./report/overview').first
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "#value" do
|
|
12
|
+
context "for hosts and vulnerabilities" do
|
|
13
|
+
before do
|
|
14
|
+
@test_xml = @doc.xpath('./hosts/host').first
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "returns the value of the item's tag" do
|
|
18
|
+
processor = described_class.new(data: @test_xml)
|
|
19
|
+
value = processor.value(field: 'host.hostname')
|
|
20
|
+
|
|
21
|
+
expect(value).to eq("Test Hostname")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context "for evidences" do
|
|
26
|
+
before do
|
|
27
|
+
@test_xml = @doc.xpath('./vulnerabilities/host_info/vulnerability').first
|
|
28
|
+
@test_xml.name = 'evidence'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "returns the value for the class attribute" do
|
|
32
|
+
processor = described_class.new(data: @test_xml)
|
|
33
|
+
value = processor.value(field: 'evidence.class')
|
|
34
|
+
|
|
35
|
+
expect(value).to eq("Test Vuln class")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Dradis::Plugins::Saint::Importer do
|
|
4
|
+
before(:each) do
|
|
5
|
+
# Stub template service
|
|
6
|
+
templates_dir = File.expand_path('../../../../../templates', __FILE__)
|
|
7
|
+
expect_any_instance_of(Dradis::Plugins::TemplateService)
|
|
8
|
+
.to receive(:default_templates_dir).and_return(templates_dir)
|
|
9
|
+
|
|
10
|
+
plugin = Dradis::Plugins::Saint
|
|
11
|
+
|
|
12
|
+
@content_service = Dradis::Plugins::ContentService::Base.new(
|
|
13
|
+
logger: Logger.new(STDOUT),
|
|
14
|
+
plugin: plugin
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
@importer = described_class.new(
|
|
18
|
+
content_service: @content_service
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "creates the appropriate Dradis items" do
|
|
23
|
+
allow(@content_service).to receive(:create_issue) do |args|
|
|
24
|
+
OpenStruct.new(args)
|
|
25
|
+
end
|
|
26
|
+
allow(@content_service).to receive(:create_note) do |args|
|
|
27
|
+
OpenStruct.new(args)
|
|
28
|
+
end
|
|
29
|
+
expect(@content_service).to receive(:create_node).with(hash_including label: '192.168.150.163').once
|
|
30
|
+
|
|
31
|
+
@importer.import(file: 'spec/fixtures/files/saint_metasploitable_sample.xml')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<report>
|
|
2
|
+
<summary>
|
|
3
|
+
</summary>
|
|
4
|
+
<overview>
|
|
5
|
+
<hosts>
|
|
6
|
+
<host>
|
|
7
|
+
<hostname>Test Hostname</hostname>
|
|
8
|
+
<ipaddr>Test Ipaddr</ipaddr>
|
|
9
|
+
<hosttype>Test Hosttype</hosttype>
|
|
10
|
+
</host>
|
|
11
|
+
</hosts>
|
|
12
|
+
<vulnerabilities>
|
|
13
|
+
<host_info>
|
|
14
|
+
<hostname>Test Hostname</hostname>
|
|
15
|
+
<vulnerability>
|
|
16
|
+
<port>Test Port</port>
|
|
17
|
+
<severity>Test Severity</severity>
|
|
18
|
+
<description>Test Description</description>
|
|
19
|
+
<class>Test Vuln class</class>
|
|
20
|
+
<cve>Test Cve</cve>
|
|
21
|
+
<cvss_base_score>Test Cvss base score</cvss_base_score>
|
|
22
|
+
</vulnerability>
|
|
23
|
+
</host_info>
|
|
24
|
+
</vulnerabilities>
|
|
25
|
+
</overview>
|
|
26
|
+
<details>
|
|
27
|
+
<vulnerability>
|
|
28
|
+
<description>Test Description</description>
|
|
29
|
+
<hostname>Test Hostname</hostname>
|
|
30
|
+
<ipaddr>Test Ipaddr</ipaddr>
|
|
31
|
+
<hosttype>Test Hosttype</hosttype>
|
|
32
|
+
<scan_time>Test Scan time</scan_time>
|
|
33
|
+
<status>Test Status</status>
|
|
34
|
+
<severity>Test Severity</severity>
|
|
35
|
+
<cve>Test Cve</cve>
|
|
36
|
+
<cvss_base_score>Test Cvss base score</cvss_base_score>
|
|
37
|
+
<impact>Test Impact</impact>
|
|
38
|
+
<background>Test Background</background>
|
|
39
|
+
<problem>Test Problem</problem>
|
|
40
|
+
<resolution>Test Resolution</resolution>
|
|
41
|
+
<reference>Test Reference</reference>
|
|
42
|
+
<vuln_details></vuln_details>
|
|
43
|
+
</vulnerability>
|
|
44
|
+
</details>
|
|
45
|
+
</report>
|