dradis-saint 3.18.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/.github/issue_template.md +16 -0
- data/.github/pull_request_template.md +36 -0
- data/.gitignore +12 -0
- data/CHANGELOG.md +39 -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 +166 -0
data/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Saint add-on for Dradis
|
|
2
|
+
|
|
3
|
+
This add-on will enable the user to upload Saint output files in the XML format (.xml) to create a structure of Dradis nodes, issues, and evidences that contain the same information about the hosts and vulnerabilities in the original file.
|
|
4
|
+
|
|
5
|
+
The add-on requires Dradis 3.0 or higher.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## More information
|
|
9
|
+
|
|
10
|
+
See the Dradis Framework's [README.md](https://github.com/dradis/dradis-ce/blob/master/README.md)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Contributing
|
|
14
|
+
|
|
15
|
+
See the Dradis Framework's [CONTRIBUTING.md](https://github.com/dradis/dradis-ce/blob/master/CONTRIBUTING.md)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## License
|
|
19
|
+
|
|
20
|
+
Dradis Framework and all its components are released under [GNU General Public License version 2.0](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Feature requests and bugs
|
|
24
|
+
|
|
25
|
+
Please use the [Dradis Framework issue tracker](https://github.com/dradis/dradis-ce/issues) for add-on improvements and bug reports.
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'bundler/gem_tasks'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
|
2
|
+
|
|
3
|
+
# Maintain your gem's version:
|
|
4
|
+
require 'dradis/plugins/saint/version'
|
|
5
|
+
version = Dradis::Plugins::Saint::VERSION::STRING
|
|
6
|
+
|
|
7
|
+
# Describe your gem and declare its dependencies:
|
|
8
|
+
Gem::Specification.new do |s|
|
|
9
|
+
s.platform = Gem::Platform::RUBY
|
|
10
|
+
s.name = 'dradis-saint'
|
|
11
|
+
s.version = version
|
|
12
|
+
s.authors = ['Daniel Martin']
|
|
13
|
+
s.email = ['etd@nomejortu.com']
|
|
14
|
+
s.homepage = 'http://dradisframework.org'
|
|
15
|
+
s.summary = 'Saint upload add-on for Dradis Framework.'
|
|
16
|
+
s.description = 'This add-on allows you to upload and parse reports from Saint.'
|
|
17
|
+
s.license = 'GPL-2'
|
|
18
|
+
|
|
19
|
+
s.files = `git ls-files`.split($\)
|
|
20
|
+
|
|
21
|
+
s.add_dependency 'dradis-plugins', '~> 3.8'
|
|
22
|
+
s.add_dependency 'nokogiri'
|
|
23
|
+
s.add_dependency 'rake', '~> 13.0'
|
|
24
|
+
|
|
25
|
+
s.add_development_dependency 'bundler', '~> 1.6'
|
|
26
|
+
s.add_dependency 'combustion', '~> 0.6.0'
|
|
27
|
+
s.add_dependency 'rspec-rails'
|
|
28
|
+
end
|
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 = 18
|
|
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
|