risu 1.4.3
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.
- data/KNOWNISSUES.markdown +50 -0
- data/LICENSE +25 -0
- data/NEWS.markdown +112 -0
- data/README.markdown +126 -0
- data/Rakefile +37 -0
- data/TODO.markdown +69 -0
- data/bin/risu +12 -0
- data/lib/nessusdb.rb +38 -0
- data/lib/nessusdb/cli.rb +9 -0
- data/lib/nessusdb/cli/application.rb +402 -0
- data/lib/nessusdb/cli/banner.rb +25 -0
- data/lib/nessusdb/exceptions.rb +8 -0
- data/lib/nessusdb/exceptions/invaliddocument.rb +10 -0
- data/lib/nessusdb/listener.rb +274 -0
- data/lib/nessusdb/models.rb +18 -0
- data/lib/nessusdb/models/familyselection.rb +12 -0
- data/lib/nessusdb/models/host.rb +359 -0
- data/lib/nessusdb/models/individualpluginselection.rb +14 -0
- data/lib/nessusdb/models/item.rb +183 -0
- data/lib/nessusdb/models/plugin.rb +98 -0
- data/lib/nessusdb/models/pluginspreference.rb +12 -0
- data/lib/nessusdb/models/policy.rb +17 -0
- data/lib/nessusdb/models/reference.rb +13 -0
- data/lib/nessusdb/models/report.rb +26 -0
- data/lib/nessusdb/models/serverpreference.rb +13 -0
- data/lib/nessusdb/models/version.rb +12 -0
- data/lib/nessusdb/nessusdocument.rb +66 -0
- data/lib/nessusdb/parsers.rb +8 -0
- data/lib/nessusdb/prawn_templater.rb +38 -0
- data/lib/nessusdb/schema.rb +145 -0
- data/lib/nessusdb/templates/assets.rb +21 -0
- data/lib/nessusdb/templates/cover_sheet.rb +42 -0
- data/lib/nessusdb/templates/data/nessuslogo.jpg +0 -0
- data/lib/nessusdb/templates/exec_summary.rb +56 -0
- data/lib/nessusdb/templates/executive_summary.rb +182 -0
- data/lib/nessusdb/templates/finding_statistics.rb +23 -0
- data/lib/nessusdb/templates/findings_host.rb +49 -0
- data/lib/nessusdb/templates/findings_summary.rb +68 -0
- data/lib/nessusdb/templates/findings_summary_with_pluginid.rb +68 -0
- data/lib/nessusdb/templates/graphs.rb +33 -0
- data/lib/nessusdb/templates/host_summary.rb +40 -0
- data/lib/nessusdb/templates/ms_patch_summary.rb +37 -0
- data/lib/nessusdb/templates/ms_update_summary.rb +43 -0
- data/lib/nessusdb/templates/pci_compliance.rb +66 -0
- data/lib/nessusdb/templates/technical_findings.rb +116 -0
- data/risu.gemspec +44 -0
- metadata +247 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module NessusDB
|
4
|
+
module Models
|
5
|
+
|
6
|
+
# Item Model
|
7
|
+
#
|
8
|
+
# @author Jacob Hammack <jacob.hammack@hammackj.com>
|
9
|
+
class Item < ActiveRecord::Base
|
10
|
+
belongs_to :host
|
11
|
+
belongs_to :plugin
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
# Queries for all risks in the database
|
16
|
+
#
|
17
|
+
# @return [ActiveRecord::Relation] with the query results
|
18
|
+
def risks
|
19
|
+
where(:severity => [0,1,2,3])
|
20
|
+
end
|
21
|
+
|
22
|
+
# Queries for all the high risks in the database
|
23
|
+
#
|
24
|
+
# @return [ActiveRecord::Relation] with the query results
|
25
|
+
def high_risks
|
26
|
+
where(:severity => 3)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Queries for all the medium risks in the database
|
30
|
+
#
|
31
|
+
# @return [ActiveRecord::Relation] with the query results
|
32
|
+
def medium_risks
|
33
|
+
where(:severity => 2)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Queries for all the low risks in the database
|
37
|
+
#
|
38
|
+
# @return [ActiveRecord::Relation] with the query results
|
39
|
+
def low_risks
|
40
|
+
where(:severity => 1)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Queries for all the info risks in the database
|
44
|
+
#
|
45
|
+
# @return [ActiveRecord::Relation] with the query results
|
46
|
+
def info_risks
|
47
|
+
where(:severity => 0)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Queries for all the unique high risks in the database
|
51
|
+
#
|
52
|
+
# @return [ActiveRecord::Relation] with the query results
|
53
|
+
def high_risks_unique
|
54
|
+
where(:severity => 3).joins(:plugin).order("plugins.cvss_base_score").group(:plugin_id)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Queries for all the unique high findings and sorts them by count
|
58
|
+
#
|
59
|
+
# @return [ActiveRecord::Relation] with the query results
|
60
|
+
def high_risks_unique_sorted
|
61
|
+
select("items.*").select("count(*) as count_all").where(:severity => 3).group(:plugin_id).order("count_all DESC")
|
62
|
+
end
|
63
|
+
|
64
|
+
# Queries for all the unique medium risks in the database
|
65
|
+
#
|
66
|
+
# @return [ActiveRecord::Relation] with the query results
|
67
|
+
def medium_risks_unique
|
68
|
+
where(:severity => 2).joins(:plugin).order(:cvss_base_score).group(:plugin_id)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Queries for all the unique medium findings and sorts them by count
|
72
|
+
#
|
73
|
+
# @return [ActiveRecord::Relation] with the query results
|
74
|
+
def medium_risks_unique_sorted
|
75
|
+
select("items.*").select("count(*) as count_all").where(:severity => 2).group(:plugin_id).order("count_all DESC")
|
76
|
+
end
|
77
|
+
|
78
|
+
# Queries for all the unique low risks in the database
|
79
|
+
#
|
80
|
+
# @return [ActiveRecord::Relation] with the query results
|
81
|
+
def low_risks_unique
|
82
|
+
where(:severity => 1).joins(:plugin).order(:cvss_base_score).group(:plugin_id)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Queries for all the unique low findings and sorts them by count
|
86
|
+
#
|
87
|
+
# @return [ActiveRecord::Relation] with the query results
|
88
|
+
def low_risks_unique_sorted
|
89
|
+
select("items.*").select("count(*) as count_all").where(:severity => 1).group(:plugin_id).order("count_all DESC")
|
90
|
+
end
|
91
|
+
|
92
|
+
# Queries for all the unique info risks in the database
|
93
|
+
#
|
94
|
+
# @return [ActiveRecord::Relation] with the query results
|
95
|
+
def info_risks_unique
|
96
|
+
where(:severity => 0).joins(:plugin).order(:cvss_base_score).group(:plugin_id)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Queries for all the unique info findings and sorts them by count
|
100
|
+
#
|
101
|
+
# @return [ActiveRecord::Relation] with the query results
|
102
|
+
def info_risks_unique_sorted
|
103
|
+
select("items.*").select("count(*) as count_all").where(:severity => 0).group(:plugin_id).order("count_all DESC")
|
104
|
+
end
|
105
|
+
|
106
|
+
# Queries for all the risks grouped by service type, used for the Vulnerbilities by Service graph
|
107
|
+
#
|
108
|
+
# @return [ActiveRecord::Relation] with the query results
|
109
|
+
def risks_by_service(limit=10)
|
110
|
+
select("items.*").select("count(*) as count_all").where("svc_name != 'unknown' and svc_name != 'general'").group(:svc_name).order("count_all DESC").limit(limit)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Queries for all the high risks by plugin
|
114
|
+
#
|
115
|
+
# @todo update this
|
116
|
+
#
|
117
|
+
# @return [ActiveRecord::Relation] with the query results
|
118
|
+
def risks_by_plugin(limit=10)
|
119
|
+
select("items.*").select("count(*) as count_all").joins(:plugin).where("plugin_id != 1").where(:severity => 3).group(:plugin_id).order("count_all DESC").limit(limit)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Queries for all the risks by host
|
123
|
+
#
|
124
|
+
# @return [ActiveRecord::Relation] with the query results
|
125
|
+
def risks_by_host(limit=10)
|
126
|
+
select("items.*").select("count(*) as count_all").joins(:host).where("plugin_id != 1").where(:severity => [3, 2]).group(:host_id).order("count_all DESC").limit(limit)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Queries for all the hosts with the Microsoft patch summary plugin (38153)
|
130
|
+
#
|
131
|
+
# @return [ActiveRecord::Relation] with the query results
|
132
|
+
def ms_patches
|
133
|
+
where(:plugin_id => 38153).joins(:host)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Queries for all host with the Microsoft Update Summary plugin(12028)
|
137
|
+
#
|
138
|
+
# @return [ActiveRecord::Relation] with the query results
|
139
|
+
def ms_update
|
140
|
+
where(:plugin_id => 12028).joins(:host)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Generates a Graph of all the risks by service
|
144
|
+
#
|
145
|
+
# @return [StringIO] Object containing the generated PNG image
|
146
|
+
def risks_by_service_graph(limit=10)
|
147
|
+
g = Gruff::Pie.new(GRAPH_WIDTH)
|
148
|
+
g.title = sprintf "Top %d Findings By Service", Item.risks_by_service(limit).all.count
|
149
|
+
g.sort = false
|
150
|
+
g.theme = {
|
151
|
+
:colors => %w(red green blue orange yellow purple black grey brown pink),
|
152
|
+
:background_colors => %w(white white)
|
153
|
+
}
|
154
|
+
|
155
|
+
Item.risks_by_service(limit).all.each { |service|
|
156
|
+
g.data(service.svc_name, Item.find(:all, :conditions => {:svc_name => service.svc_name}).count)
|
157
|
+
}
|
158
|
+
|
159
|
+
StringIO.new(g.to_blob)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Generates a Graph of all the risks by severity
|
163
|
+
#
|
164
|
+
# @return [StringIO] Object containing the generated PNG image
|
165
|
+
def risks_by_severity_graph
|
166
|
+
g = Gruff::Bar.new(GRAPH_WIDTH)
|
167
|
+
g.title = "Findings By Severity"
|
168
|
+
g.sort = false
|
169
|
+
g.theme = {
|
170
|
+
:background_colors => %w(white white)
|
171
|
+
}
|
172
|
+
|
173
|
+
g.data("High", Item.high_risks.count, "red") unless Item.high_risks.count == 0
|
174
|
+
g.data("Medium", Item.medium_risks.count, "orange") unless Item.medium_risks.count == 0
|
175
|
+
g.data("Low", Item.low_risks.count, "yellow") unless Item.low_risks.count == 0
|
176
|
+
g.data("Info", Item.info_risks.count, "blue") unless Item.info_risks.count == 0
|
177
|
+
|
178
|
+
StringIO.new(g.to_blob)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module NessusDB
|
4
|
+
module Models
|
5
|
+
|
6
|
+
# Plugin Model
|
7
|
+
#
|
8
|
+
# @author Jacob Hammack
|
9
|
+
class Plugin < ActiveRecord::Base
|
10
|
+
has_many :items
|
11
|
+
belongs_to :family
|
12
|
+
has_many :references
|
13
|
+
has_many :individual_plugin_selections
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
# Queries for all risks based on Plugin.risk_factor
|
18
|
+
#
|
19
|
+
# @return [Array] of all risks
|
20
|
+
def risks
|
21
|
+
critical_risks + high_risks + medium_risks + low_risks + none_risks
|
22
|
+
end
|
23
|
+
|
24
|
+
# Queries for all the critical risks based on Plugin.risk_factor
|
25
|
+
#
|
26
|
+
# @return [ActiveRelation] of Critical Risks
|
27
|
+
def critical_risks
|
28
|
+
where(:risk_factor => "Critical")
|
29
|
+
end
|
30
|
+
|
31
|
+
#Queries for all the critical risks based on Plugin.risk_factor
|
32
|
+
#
|
33
|
+
# @return [ActiveRelation] of High Risks
|
34
|
+
def high_risks
|
35
|
+
where(:risk_factor => "High")
|
36
|
+
end
|
37
|
+
|
38
|
+
# Queries for all the critical risks based on Plugin.risk_factor
|
39
|
+
#
|
40
|
+
# @return [ActiveRelation] of Medium Risks
|
41
|
+
def medium_risks
|
42
|
+
where(:risk_factor => "Medium")
|
43
|
+
end
|
44
|
+
|
45
|
+
# Queries for all the critical risks based on Plugin.risk_factor
|
46
|
+
#
|
47
|
+
# @return [ActiveRelation] of Low Risks
|
48
|
+
def low_risks
|
49
|
+
where(:risk_factor => "Low")
|
50
|
+
end
|
51
|
+
|
52
|
+
# Queries for all the critical risks based on Plugin.risk_factor
|
53
|
+
#
|
54
|
+
# @return [ActiveRelation] of None Risks
|
55
|
+
def none_risks
|
56
|
+
where(:risk_factor => "None")
|
57
|
+
end
|
58
|
+
|
59
|
+
# Creates a graph based on the top plugins sorted by count
|
60
|
+
#
|
61
|
+
# @return Filename of the created graph
|
62
|
+
def top_by_count_graph(limit=10)
|
63
|
+
g = Gruff::Bar.new(GRAPH_WIDTH)
|
64
|
+
g.title = sprintf "Top %d High Findings By Plugin", Item.risks_by_plugin(limit).all.count
|
65
|
+
g.sort = false
|
66
|
+
g.theme = {
|
67
|
+
:colors => %w(red green blue orange yellow purple black grey brown pink),
|
68
|
+
:background_colors => %w(white white)
|
69
|
+
}
|
70
|
+
|
71
|
+
Item.risks_by_plugin(limit).all.each do |plugin|
|
72
|
+
plugin_name = Plugin.find_by_id(plugin.plugin_id).plugin_name
|
73
|
+
|
74
|
+
#We need to filter the names a little to make everything look nice on the graph
|
75
|
+
plugin_name = case plugin.plugin_id
|
76
|
+
when 35362 then plugin_name.split(":")[0]
|
77
|
+
when 34477 then plugin_name.split(":")[0]
|
78
|
+
when 35635 then plugin_name.split(":")[0]
|
79
|
+
when 21564 then "VNC Remote Authentication Bypass"
|
80
|
+
when 38664 then "Intel Common Base Agent Remote Command Execution"
|
81
|
+
when 42411 then "Windows SMB Shares Unprivileged Access"
|
82
|
+
else
|
83
|
+
plugin_name = Plugin.find_by_id(plugin.plugin_id).plugin_name
|
84
|
+
end
|
85
|
+
|
86
|
+
if plugin_name =~ /^(MS\d{2}-\d{3}):/
|
87
|
+
plugin_name = $1
|
88
|
+
end
|
89
|
+
|
90
|
+
g.data(plugin_name, Item.where(:plugin_id => plugin.plugin_id).count)
|
91
|
+
end
|
92
|
+
|
93
|
+
StringIO.new(g.to_blob)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module NessusDB
|
4
|
+
module Models
|
5
|
+
|
6
|
+
# Policy Model
|
7
|
+
#
|
8
|
+
# @author Jacob Hammack
|
9
|
+
class Policy < ActiveRecord::Base
|
10
|
+
has_many :family_selections
|
11
|
+
has_many :individual_plugin_selections
|
12
|
+
has_many :reports
|
13
|
+
has_many :plugins_preferences
|
14
|
+
has_many :server_preferences
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module NessusDB
|
4
|
+
module Models
|
5
|
+
|
6
|
+
# Report Model
|
7
|
+
#
|
8
|
+
# @author Jacob Hammack <jacob.hammack@hammackj.com>
|
9
|
+
class Report < ActiveRecord::Base
|
10
|
+
has_many :hosts
|
11
|
+
belongs_to :policy
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
attr_accessor :title, :author, :company, :classification
|
16
|
+
|
17
|
+
#
|
18
|
+
#@scan_date = Host.where("start is not null").first[:start].to_s
|
19
|
+
#
|
20
|
+
def scan_date
|
21
|
+
Host.where("start is not null").first[:start].to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module NessusDB
|
4
|
+
|
5
|
+
# A Object to represet the Nessus xml file in memory
|
6
|
+
#
|
7
|
+
# @author Jacob Hammack <jacob.hammack@hammackj.com>
|
8
|
+
class NessusDocument
|
9
|
+
|
10
|
+
# Creates a instance of the NessusDocument class
|
11
|
+
#
|
12
|
+
def initialize document
|
13
|
+
@document = document
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks the validness of a NessusDocument
|
17
|
+
#
|
18
|
+
# @return [Boolean] True if valid, False if invalid
|
19
|
+
def valid?
|
20
|
+
if File.exist?(@document)
|
21
|
+
@parser = LibXML::XML::Parser.file @document
|
22
|
+
doc = @parser.parse
|
23
|
+
|
24
|
+
if doc.root.name == nil
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
if doc.root.name == "NessusClientData_v2"
|
29
|
+
return true
|
30
|
+
elsif doc.root.name == "NessusClientData"
|
31
|
+
return false
|
32
|
+
else
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
else
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Invokes the SAX parser on the XML document
|
41
|
+
#
|
42
|
+
def parse
|
43
|
+
@parser = LibXML::XML::SaxParser.file @document
|
44
|
+
@parser.callbacks = NessusSaxListener.new
|
45
|
+
@parser.parse
|
46
|
+
end
|
47
|
+
|
48
|
+
# Fixes the ip field if nil and replaces it with the name if its an ip
|
49
|
+
#
|
50
|
+
def fix_ips
|
51
|
+
@hosts = Host.all
|
52
|
+
|
53
|
+
@hosts.each do |host|
|
54
|
+
if host.ip == nil
|
55
|
+
begin
|
56
|
+
ip = IPAddr.new host.name
|
57
|
+
host.ip = ip.to_string
|
58
|
+
host.save
|
59
|
+
rescue ArgumentError => ae
|
60
|
+
next
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|