publicscripts/nessus-group/nessus-group.rb

353 lines
8.5 KiB
Ruby
Raw Permalink Normal View History

2009-02-13 11:54:36 +00:00
#!/usr/bin/env ruby
# $Id$
# $URL$
# parse nbe files and group IPs of matching vulnerabilities
require 'getoptlong'
require 'rexml/streamlistener'
require 'rexml/document'
@vulns = Hash.new
Vuln = Struct.new(:ip, :fullip, :descr, :weight)
Weight= { 'Security Note' => 0,
'Security Warning' => 1,
'Security Hole' => 2,
'NOTE' => 0,
'INFO' => 1,
'REPORT' => 2
}
module Color
AnsiAttributes =
{
'clear' => 0,
'reset' => 0,
'bold' => 1,
'dark' => 2,
'underline' => 4,
'underscore' => 4,
'blink' => 5,
'reverse' => 7,
'concealed' => 8,
'black' => 30, 'on_black' => 40,
'red' => 31, 'on_red' => 41,
'green' => 32, 'on_green' => 42,
'yellow' => 33, 'on_yellow' => 43,
'blue' => 34, 'on_blue' => 44,
'magenta' => 35, 'on_magenta' => 45,
'cyan' => 36, 'on_cyan' => 46,
'white' => 37, 'on_white' => 47
}
#
# Return a string with ANSI codes substituted. Derived from code
# written by The FaerieMUD Consortium.
#
def self.ansi(*attrs)
attr = attrs.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';')
attr = "\e[%sm" % attr if ! attr.empty?
return attr
end
end
class XMLListener
Vuln = Struct.new(:ip, :fullip, :descr, :weight)
def initialize()
@curpath = Array.new
@curpathstr = ""
@curhost = ""
@vulns = Hash.new
end
def clearhost
@curhost = ""
end
def clearreport
@curport = ""
@curseverity = ""
@curpluginID = ""
@curpluginName = ""
@curdata = ""
end
def gethash
return @vulns
end
def tag_start(name, attrs)
@curpath.push name
@curpathstr = @curpath.join('/')
# puts @curpathstr
if @curpathstr == 'NessusClientData/Report/ReportHost'
clearhost
end
end
def tag_end(name)
@curpath.pop
@curpathstr = @curpath.join('/')
if @curpathstr == 'NessusClientData/Report/ReportHost'
# nu is een report volledig, dus toevoegen aan standaard meuk ofzo
# nog even uitzoeken /hoe/
if ! @curseverity.nil? and @curseverity.to_i > 0
if ! @vulns[@curpluginID].nil?
@vulns[@curpluginID].push Vuln.new(@curhost, "#{@curhost} : #{@curport}",
@curdata.gsub(/\\n/, "\n").gsub(/\\r/, "\r").gsub(/\\\\/, "\\"), @curseverity.to_i - 1)
else
@vulns[@curpluginID] = [ Vuln.new(@curhost, "#{@curhost} : #{@curport}",
@curdata.gsub(/\\n/, "\n").gsub(/\\r/, "\r").gsub(/\\\\/, "\\"), @curseverity.to_i - 1) ]
end
end
clearreport
end
end
def text(text)
if @curpathstr == 'NessusClientData/Report/ReportHost/HostName'
@curhost = text
end
if @curpathstr == 'NessusClientData/Report/ReportHost/ReportItem/port'
@curport = text
end
if @curpathstr == 'NessusClientData/Report/ReportHost/ReportItem/severity'
@curseverity = text
# is hier precies 1 hoger dan ik zelf bedacht had
# severity 0 = boeie
# severity 1 = note
# severity 2 = warning
# severity 3 = hole
end
if @curpathstr == 'NessusClientData/Report/ReportHost/ReportItem/pluginID'
@curpluginID = text
end
if @curpathstr == 'NessusClientData/Report/ReportHost/ReportItem/pluginName'
@curpluginName = text
end
if @curpathstr == 'NessusClientData/Report/ReportHost/ReportItem/data'
@curdata = text
end
end
end
def usage
puts <<EOT
nessus-group.rb [-a] <NBE-file>
-i <ids> ids is a comma seperated list of NessusIDs to be displayed
(default: all)
-f display full list of descriptions (default: only the first)
-C do not display colors
-s give a summary of the IDs found
-t use NessusId 21643 to generate a table of SSL-weaknesses (tab delimited)
EOT
exit
end
def parse_options
@options = {}
begin
opts = GetoptLong.new(
[ "-i", GetoptLong::REQUIRED_ARGUMENT ],
[ "-f", GetoptLong::NO_ARGUMENT ],
[ "-h", "--help", GetoptLong::NO_ARGUMENT ],
[ "-C", GetoptLong::NO_ARGUMENT ],
[ "-s", GetoptLong::NO_ARGUMENT ],
[ "-t", GetoptLong::NO_ARGUMENT ]
)
opts.quiet=true
opts.each do |opt, arg|
@options[opt] = arg
end
if @options["-h"]
usage
end
rescue GetoptLong::InvalidOption
print "#{$!}\n"
usage
end
# default values
if @options["-f"].nil?
@options["-f"] = false
end
if @options["-C"].nil?
@options["-C"] = false
end
if @options["-s"].nil?
@options["-s"] = false
end
return @options
end
def colorize(string, color)
if @options["-C"]
string
else
"#{Color.ansi(color)}#{string}#{Color.ansi("clear")}"
end
end
def display(nessusid)
next if nessusid.nil?
puts "=========================================="
if @vulns[nessusid].nil?
puts "NessusID: #{nessusid} not found"
return
end
puts "NessusID: #{nessusid} IPs: #{@vulns[nessusid].collect{|i| i[:ip]}.join(" ")}"
if ! @options["-f"]
case @vulns[nessusid][0][:weight]
when 2 then puts colorize("Severity: Security Hole", "on_red")
when 1 then puts colorize("Severity: Security Warning", "on_yellow")
when 0 then puts colorize("Severity: Security Note", "on_cyan")
end
puts "Full IPs:\n#{@vulns[nessusid].collect{|i| i[:fullip]}.join("\n")}"
puts "First description (these can differ per IP!):"
puts "#{@vulns[nessusid][0][:descr]}"
else
@vulns[nessusid].collect{|vuln|
case vuln[:weight]
when 2 then puts colorize("Severity: Security Hole #{vuln[:fullip]}", "on_red")
when 1 then puts colorize("Severity: Security Warning #{vuln[:fullip]}", "on_yellow")
when 0 then puts colorize("Severity: Security Note #{vuln[:fullip]}", "on_cyan")
end
puts "Description:"
puts "#{vuln[:descr]}"
}
end
puts
end
def summary(weight)
case weight
when 2 then puts colorize("Severity: Security Hole", "on_red")
when 1 then puts colorize("Severity: Security Warning", "on_yellow")
when 0 then puts colorize("Severity: Security Note", "on_cyan")
end
@vulns.keys.sort.each{|nessusid|
if @vulns[nessusid][0][:weight] == weight
puts "NessusID: #{nessusid} IPs: #{@vulns[nessusid].collect{|i| i[:ip]}.join(" ")}"
end
}
puts
end
def ssltable
sslID="21643"
if @vulns[sslID].nil?
puts "No SSL-ports found"
return
end
puts "IP port EXP LOW MD5 SSLv2"
@vulns[sslID].collect{|vuln|
port = vuln[:fullip].sub(/.*\(/, "").sub(/\/.*/, "")
exp = vuln[:descr].match(/Enc=[^\(]*\(40\)/)
low = vuln[:descr].match(/Enc=[^\(]*\(56\)/)
md5 = vuln[:descr].match(/Mac=MD5/)
sslv2 = vuln[:descr].match(/SSLv2/)
puts "#{vuln[:ip]} #{port} #{exp ? "X" : "-"} #{low ? "X" : "-"} #{md5 ? "X" : "-"} #{sslv2 ? "X" : "-"}"
}
end
def highestweight(nessusid)
highest = 0
@vulns[nessusid].each{|i|
highest = i[:weight] if i[:weight] > highest
}
highest
end
def readnbe(inputfile)
File.open(inputfile).each_line{|line|
sl = Array.new
sl = line.split('|', 7)
if sl[0] == "results"
next if sl[4].nil?
if ! @vulns[sl[4]].nil?
@vulns[sl[4]].push Vuln.new(sl[2], "#{sl[2]} : #{sl[3]}",
sl[6].gsub(/\\n/, "\n").gsub(/\\r/, "\r").gsub(/\\\\/, "\\"), Weight[sl[5]])
else
@vulns[sl[4]] = [ Vuln.new(sl[2], "#{sl[2]} : #{sl[3]}",
sl[6].gsub(/\\n/, "\n").gsub(/\\r/, "\r").gsub(/\\\\/, "\\"), Weight[sl[5]]) ]
end
end
}
end
def readnsr(inputfile)
File.open(inputfile).each_line{|line|
sl = Array.new
sl = line.split('|')
next if sl[3].nil?
if ! @vulns[sl[2]].nil?
@vulns[sl[2]].push Vuln.new(sl[0], "#{sl[0]} : #{sl[1]}",
sl[4].gsub(/;;/, "\n"), Weight[sl[3]])
else
@vulns[sl[2]] = [ Vuln.new(sl[0], "#{sl[0]} : #{sl[1]}",
sl[4].gsub(/;/, "\n"), Weight[sl[3]]) ]
end
}
end
def readnessus(inputfile)
list = XMLListener.new
source = File.new(inputfile)
REXML::Document.parse_stream(source, list)
@vulns = list.gethash
end
parse_options
if ARGV[0].nil? or ARGV[0].empty? or ! FileTest.exists?(ARGV[0])
usage
end
# de regels in .nbe bestanden beginnen met 'timestamps|' of 'results|'
# de regels in .nsr bestanden beginnen met een IP-adres
# de regels in .nessus bestanden beginnen met een '<' (de eerste regel is: <NessusClientData>)
line = File.open(ARGV[0]).readline
if line.match(/^(timestamps|results)\|/)
readnbe(ARGV[0])
elsif line.match(/^\d+\.\d+\.\d+\.\d+/)
readnsr(ARGV[0])
elsif line.match(/^<NessusClientData>/)
readnessus(ARGV[0])
end
if @options["-s"]
summary(Weight['Security Hole'])
summary(Weight['Security Warning'])
summary(Weight['Security Note'])
exit
end
if @options["-t"]
ssltable
exit
end
if @options["-i"]
@options["-i"].split(",").each{|key|
display(key)
}
else
@vulns.keys.sort.each{|key|
display(key) if highestweight(key) == Weight['Security Hole']
}
@vulns.keys.sort.each{|key|
display(key) if highestweight(key) == Weight['Security Warning']
}
@vulns.keys.sort.each{|key|
display(key) if highestweight(key) == Weight['Security Note']
}
end