module Prenus module Output class Htmlout < Baseout # # Initialises the Htmlout class into an object # # @return [Object] # Returns the Htmlout object # # @input # events - the hash object with all the vulnerability events within it - @see Nessusin#import_nessus_files # hosts - the hash object with all the hosts within it - @see Nessusin#import_nessus_files # options - the hash object with the configuration objections within it. These options include the output folder etc, and are used within many of the methods below # # @example # object = Prenus::Output::Htmlout(events,hosts,options) # def initialize(events, hosts, options) super #prepare folder - copy js files FileUtils.cp(File.expand_path($root_dir + '/lib/js/jquery.min.js'), options[:output] + '/jquery.min.js') FileUtils.cp(File.expand_path($root_dir + '/lib/js/highcharts.js'), options[:output] + '/highcharts.js') FileUtils.cp(File.expand_path($root_dir + '/lib/js/jquery.dataTables.js'), options[:output] + '/jquery.dataTables.js') #prepare folder - copy css files FileUtils.cp(File.expand_path($root_dir + '/lib/css/table.css'), options[:output] + '/table.css') #prepare folder - copy image files FileUtils.cp(File.expand_path($root_dir + '/lib/images/back_disabled.png'), options[:output] + '/back_disabled.png') FileUtils.cp(File.expand_path($root_dir + '/lib/images/back_enabled.png'), options[:output] + '/back_enabled.png') FileUtils.cp(File.expand_path($root_dir + '/lib/images/back_enabled_hover.png'), options[:output] + '/back_enabled_hover.png') FileUtils.cp(File.expand_path($root_dir + '/lib/images/forward_disabled.png'), options[:output] + '/forward_disabled.png') FileUtils.cp(File.expand_path($root_dir + '/lib/images/forward_enabled.png'), options[:output] + '/forward_enabled.png') FileUtils.cp(File.expand_path($root_dir + '/lib/images/forward_enabled_hover.png'), options[:output] + '/forward_enabled_hover.png') FileUtils.cp(File.expand_path($root_dir + '/lib/images/sort_asc.png'), options[:output] + '/sort_asc.png') FileUtils.cp(File.expand_path($root_dir + '/lib/images/sort_asc_disabled.png'), options[:output] + '/sort_asc_disabled.png') FileUtils.cp(File.expand_path($root_dir + '/lib/images/sort_both.png'), options[:output] + '/sort_both.png') FileUtils.cp(File.expand_path($root_dir + '/lib/images/sort_desc.png'), options[:output] + '/sort_desc.png') FileUtils.cp(File.expand_path($root_dir + '/lib/images/sort_desc_disabled.png'), options[:output] + '/sort_desc_disabled.png') end # # Run the Htmlout class - this will generate all the necessary HTML files and copy other dependencies (JS/CSS/PNGs) to the target folder # # @return # Returns nothing # # @example # object.run # def run self.print_hosts # generate all the host_*.html files self.print_index # generate the index.html file self.print_vulns # generate all the vuln_*.html files self.print_vuln_overview # generate the vuln_overview.html file end # # Generate the index.html file, outputting it to the nominated output directory + /index.html # @see @options[:output] # # @return # Returns nothing # # @example # print_index # def print_index File.open(@options[:output] + "/index.html",'w') do |f| html_header(f,"Home") bar_js(f,"bar_graph","Top 20 Hosts",@hosts.sort_by{|k,v| v[:total_excl_info]}.reverse.take(20)) crit_total = 0 high_total = 0 med_total = 0 low_total = 0 @events.each do |k,v| crit_total += 1 if v[:severity] == 4 high_total += 1 if v[:severity] == 3 med_total += 1 if v[:severity] == 2 low_total += 1 if v[:severity] == 1 end pie_data = [] pie_data << ['Low',low_total.to_i,'green'] if @options[:severity] <= 1 and low_total.to_i > 0 pie_data << ['Medium',med_total.to_i,'orange'] if @options[:severity] <= 2 and med_total.to_i > 0 pie_data << ['High',high_total.to_i,'red'] if @options[:severity] <= 3 and high_total.to_i > 0 pie_data << ['Critical',crit_total.to_i,'purple'] if @options[:severity] <= 4 and crit_total.to_i > 0 pie_js(f,"pie_graph","Unique Vulnerability Breakdown","Unique Vuln Breakdown",pie_data,"document.location.href = 'vuln_overview.html';") crit_total = 0 high_total = 0 med_total = 0 low_total = 0 @hosts.each do |id,values| crit_total += values[:crit].to_i high_total += values[:high].to_i med_total += values[:med].to_i low_total += values[:low].to_i end pie_data = [] pie_data << ['Low',low_total.to_i,'green'] if @options[:severity] <= 1 and low_total.to_i > 0 pie_data << ['Medium',med_total.to_i,'orange'] if @options[:severity] <= 2 and med_total.to_i > 0 pie_data << ['High',high_total.to_i,'red'] if @options[:severity] <= 3 and high_total.to_i > 0 pie_data << ['Critical',crit_total.to_i,'purple'] if @options[:severity] <= 4 and crit_total.to_i > 0 pie_js(f,"pie_graph2","Total Vunerability Breakdown","Total Vuln Breakdown",pie_data,"document.location = href= 'vuln_overview.html';") target_lookup = "var target_lookup = {" @hosts.each_with_index do |host,index| if host[1][:hostname] == "" target_lookup += "'" + host[1][:ip] + "'" else target_lookup += "'" + host[1][:hostname] + " (" + host[1][:ip] + ")" + "'" end target_lookup += ": " + host[0].to_s target_lookup += "," unless index == @hosts.length - 1 end target_lookup += "}" f.puts target_lookup close_html_header(f) unless @options[:indexfile].nil? IO.copy_stream(File.open(@options[:indexfile]),f) end body = '
' body += '
' body += '
' body += '
' body += '
' body += '
' body += '

All Hosts

' ips = [] @hosts.each do |host| ips << host[1][:ip] end body += '' ips.sort_by{|ip| ip.split('.').map{|octet| octet.to_i}}.each do |ip| @hosts.select{|k,v| v[:ip] == ip}.each do |k,v| tmp_actual_v_count = 0 tmp_actual_v_count += v[:low].to_i if @options[:severity] <= 1 and v[:low].to_i > 0 tmp_actual_v_count += v[:med].to_i if @options[:severity] <= 2 and v[:med].to_i > 0 tmp_actual_v_count += v[:high].to_i if @options[:severity] <= 3 and v[:high].to_i > 0 tmp_actual_v_count += v[:crit].to_i if @options[:severity] <= 4 and v[:crit].to_i > 0 body += '' end end body += '
IPHostnameOSVulnerability Count (Low to Critical)
' if tmp_actual_v_count > 0 body += '' + ip + '' else body += ip end body += '' + v[:hostname] + '' + v[:os] + '' + v[:total_excl_info].to_s + '
' body += '' body_text(f,body) close_all(f) end end # # Generate the vuln_overview.html file, outputting it to the nominated output directory + /vuln_overview.html # @see @options[:output] # # @return # Returns nothing # # @example # print_vuln_overvire # def print_vuln_overview File.open(@options[:output] + "/vuln_overview.html", 'w') do |f| html_header(f,"Vulns Overview") close_html_header(f) body = 'Home

Vulnerabilities

' body += '' @events.each do |k,v| next if v[:severity].to_i < @options[:severity].to_i body += '' body += '' end body += '
Nessus IDSeverityNameFamilyPortsNumber of impacted hosts
' + k.to_s body += '' + v[:severity].to_s + '' + v[:plugin_name] + '' + v[:family].to_s + '' impacted_hosts = [] v[:ports].each_with_index do |(k2,v2),index| body += k2.to_s body += ", " unless index == v[:ports].length - 1 v2[:hosts].each do |h,w| impacted_hosts << h end end impacted_hosts.uniq! body += '' + impacted_hosts.count.to_s + '
' body += '' body_text(f,body) close_all(f) end end # # Generates the various vuln_*.html files, outputting them to the nominated output directory # @see @options[:output] # # @return # Returns nothing # # @example # print_vulns # def print_vulns @events.each do |id,values| next if values[:severity].to_i < @options[:severity].to_i File.open(@options[:output] + "/vuln_" + id.to_s + ".html", 'w') do |f| html_header(f,id.to_s) close_html_header(f) body = 'Home
Nessus ID: ' + id.to_s + '
Name: ' + values[:plugin_name] + '
Severity: ' + values[:severity].to_s + '
Family: ' + values[:family] + '
Ports: ' impacted_hosts = [] values[:ports].each_with_index {|(k,v),index| body += k.to_s v[:hosts].each do |h,w| impacted_hosts << h end body += ", " unless index == values[:ports].length - 1 } body += '

Synopsis:
' + values[:synopsis] + '

Description:
' + values[:description] + '

Solution:
' + values[:solution] + '

See Also:
' values[:see_also].each do |val| val.split("\n").each do |val2| body += '' + val2 + '
' end end body +='

CVE: ' + values[:cve].to_s + '
CVSS Base Score: ' + values[:cvss_base_score].to_s + '
CVSS Vector: ' + values[:cvss_vector].to_s + '
' body += '
' body += '

Hosts

' body += '' impacted_hosts.uniq.each do |host| values[:ports].each{|k,v| v[:hosts].each do |h,w| if h == host body += '' body += '\n" end end } end body += '
Host IPHostnameOSPortResult
' + @hosts[host][:ip] + '' + @hosts[host][:hostname] + '' + @hosts[host][:os] + '' + k.to_s + '' + w.to_s.gsub(/<\/?[^>]*>/, "").gsub("\n","
\n") + "
' body += '' body_text(f,body) close_all(f) end end end # # Generates the various host_*.html files, outputting them to the nominated output directory # @see @options[:output] # # @return # Returns nothing # # @example # print_hosts # def print_hosts @hosts.each do |id,values| File.open(@options[:output] + "/host_" + id.to_s + ".html", 'w') do |f| html_header(f,values[:ip]) if values[:total_excl_info] == 0 pie_js(f,"pie_graph","Criticality Breakdown","Criticality Breakdown",[['Informational ONLY',values[:info].to_i,'blue']]) else pie_data = [] pie_data << ['Low',values[:low].to_i,'green'] if @options[:severity] <= 1 and values[:low].to_i > 0 pie_data << ['Medium',values[:med].to_i,'orange'] if @options[:severity] <= 2 and values[:med].to_i > 0 pie_data << ['High',values[:high].to_i,'red'] if @options[:severity] <= 3 and values[:high].to_i > 0 pie_data << ['Critical',values[:crit].to_i,'purple'] if @options[:severity] <= 4 and values[:crit].to_i > 0 pie_js(f,"pie_graph","Criticality Breakdown","Criticality Breakdown",pie_data,"document.location.href = '#' + event.point.name;") end close_html_header(f) body = 'Home
Hostname: ' + values[:hostname] + '
IP: ' + values[:ip] + '
OS: ' + values[:os] + '
' body += '

Overview

' body += '
' body += '
' body += '

Vulnerabilities

' if @options[:severity] <= 4 and values[:crit].to_i > 0 body += '

Critical

' body += '' @events.sort_by{|k,v| v[:port].to_s}.each do |vuln_id,vuln_data| vuln_data[:ports].each {|k,v| v[:hosts].each do |h,v2| if h == id and vuln_data[:severity] == 4 body += '' end end } end body += '
Nessus IDNameSynopsisResultFamilyPort
' + vuln_id.to_s + '' + vuln_data[:plugin_name] + '' + vuln_data[:synopsis] + '' + v2.to_s.gsub(/<\/?[^>]*>/, "").gsub("\n","
") + '
' + vuln_data[:family] + '' + k.to_s + '
' end if @options[:severity] <= 3 and values[:high].to_i > 0 body += '

High

' body += '' @events.sort_by{|k,v| v[:port].to_s}.each do |vuln_id,vuln_data| vuln_data[:ports].each {|k,v| v[:hosts].each do |h,v2| if h == id and vuln_data[:severity] == 3 body += '' end end } end body += '
Nessus IDNameSynopsisResultFamilyPort
' + vuln_id.to_s + '' + vuln_data[:plugin_name] + '' + vuln_data[:synopsis] + '' + v2.to_s.gsub(/<\/?[^>]*>/, "").gsub("\n","
") + '
' + vuln_data[:family] + '' + k.to_s + '
' end if @options[:severity] <= 2 and values[:med].to_i > 0 body += '

Medium

' body += '' @events.sort_by{|k,v| v[:port].to_s}.each do |vuln_id,vuln_data| vuln_data[:ports].each {|k,v| v[:hosts].each do |h,v2| if h == id and vuln_data[:severity] == 2 body += '' end end } end body += '
Nessus IDNameSynopsisResultFamilyPort
' + vuln_id.to_s + '' + vuln_data[:plugin_name] + '' + vuln_data[:synopsis] + '' + v2.to_s.gsub(/<\/?[^>]*>/, "").gsub("\n","
") + '
' + vuln_data[:family] + '' + k.to_s + '
' end if @options[:severity] <= 1 and values[:low].to_i > 0 body += '

Low

' body += '' @events.sort_by{|k,v| v[:port].to_s}.each do |vuln_id,vuln_data| vuln_data[:ports].each {|k,v| v[:hosts].each do |h,v2| if h == id and vuln_data[:severity] == 1 body += '' end end } end body += '
Nessus IDNameSynopsisResultFamilyPort
' + vuln_id.to_s + '' + vuln_data[:plugin_name] + '' + vuln_data[:synopsis] + '' + v2.to_s.gsub(/<\/?[^>]*>/, "").gsub("\n","
") + '
' + vuln_data[:family] + '' + k.to_s + '
' end if @options[:severity] <= 0 and values[:info].to_i > 0 body += '

Informational

' body += '' @events.sort_by{|k,v| v[:port].to_s}.each do |vuln_id,vuln_data| vuln_data[:ports].each {|k,v| v[:hosts].each do |h,v2| if h == id and vuln_data[:severity] == 0 body += '' end end } end body += '
Nessus IDNameSynopsisResultFamilyPort
' + vuln_id.to_s + '' + vuln_data[:plugin_name] + '' + vuln_data[:synopsis] + '' + v2.to_s.gsub(/<\/?[^>]*>/, "").gsub("\n","
") + '
' + vuln_data[:family] + '' + k.to_s + '
' end body += "" body += '
' body_text(f,body) close_all(f) end end end # # Prints the universal HTML header into the nominated output file # # @return # Returns nothing # # @input # fp - the file pointer (which should be opened already by the calling method) which this method prints its output into # title - the title field which is printed as the HTML title # # @example # File.open(@options[:output] + "/file.html", 'w') do |f| # html_header(f,"Title") # end # def html_header(fp,title) fp.puts <<-eos #{title} eos end # # Prints out miscellanrous HTML text into the nominated output file # # @return # Returns nothing # # @input # fp - the file pointer (which should be opened already by the calling method) which this method prints its output into # text - the text to print out to the output file # # @example # File.open(@options[:output] + "/file.html",'w') do |f| # tmpline = "Sometext
" # tmpline += "Some more text here
" # body_text(f,tmpline) # end # def body_text(fp,text) fp.puts text end # # Closes out the HTML of the page in the nominated output file # # @return # Returns nothing # # @input # fp - the file pointer (which should be opened already by the calling method) which this method prints its output into # # @example # @see html_header's @example # def close_all(fp) fp.puts <<-eos eos end # # Prints the Highcharts javascript for a bar graph into the nominated output file # # @return # Returns nothing # # @input # fp - the file pointer (which should be opened already by the calling method) which this method prints its output into # renderto - the highchart renderTo parameter (which is then referenced in the html's div id) # title - the highchart's title # data - a hash of hosts data @see Nessusin#import_nessus_files # # @example # File.open(@options[:output] + "/file.html",'w') do |f| # bar_js(f,"bargraph1","Some title",{0 => {:hostname => 'hostname',:ip => 'ip'}}) # # body_text(f,"
") # end # def bar_js(fp,renderto,title,data) fp.puts <<-eos $(function () { var chart; $(document).ready(function() { chart = new Highcharts.Chart({ credits: { enabled: false }, chart: { renderTo: '#{renderto}', type: 'bar' }, title: { text: '#{title}' }, xAxis: { categories: [ eos data.each_with_index do |entry,index| tmpline = "'" if entry[1][:hostname] == "" tmpline += entry[1][:ip] else tmpline += entry[1][:hostname] + " (" + entry[1][:ip] + ")" end tmpline += "'" tmpline += "," unless index == data.length - 1 fp.puts tmpline end fp.puts <<-eos ] }, yAxis: { min: 0, allowDecimals: false, title: { text: 'Findings' } }, legend: { backgroundColor: '#FFFFFF', reversed: true }, tooltip: { formatter: function() { return ''+ this.series.name +': '+ this.y +''; } }, plotOptions: { series: { stacking: 'normal', //threshold: 1, dataLabels: { enabled: true, color: '#000000', x: 0, align: 'center', formatter: function() { if (this.y !=0) { return this.y; } } }, events: { click: function(event) { //alert(target_lookup[event.point.category]) document.location.href = 'host_' + target_lookup[event.point.category] + '.html#' + event.currentTarget.name; //console.log(event) } } } }, series: [ eos if @options[:severity] <= 4 fp.puts "{name: 'Critical'," fp.puts "color: 'purple'," tmpline = "data: [" data.each_with_index do |entry,index| tmpline += entry[1][:crit].to_s tmpline += "," unless index == data.length - 1 end tmpline += "]" fp.puts tmpline fp.puts "}" end if @options[:severity] <= 3 fp.puts ",{name: 'High'," fp.puts "color: 'red'," tmpline = "data: [" data.each_with_index do |entry,index| tmpline += entry[1][:high].to_s tmpline += "," unless index == data.length - 1 end tmpline += "]" fp.puts tmpline fp.puts "}" end if @options[:severity] <= 2 fp.puts ",{name: 'Medium'," fp.puts "color: 'orange'," tmpline = "data: [" data.each_with_index do |entry,index| tmpline += entry[1][:med].to_s tmpline += "," unless index == data.length - 1 end tmpline += "]" fp.puts tmpline fp.puts "}" end if @options[:severity] <= 1 fp.puts ",{name: 'Low'," fp.puts "color: 'green'," tmpline = "data: [" data.each_with_index do |entry,index| tmpline += entry[1][:low].to_s tmpline += "," unless index == data.length - 1 end tmpline += "]" fp.puts tmpline fp.puts "}" end fp.puts <<-eos ] }); }); }); eos end # # Prints the Highcharts javascript for a pie graph into the nominated output file # # @return # Returns nothing # # @input # fp - the file pointer (which should be opened already by the calling method) which this method prints its output into # renderto - the highchart renderTo parameter (which is then referenced in the html's div id) # title - the highchart's title # seriesname - the highchart's series name # series - an array of array's with pie piece names and values # clickfunction - an optional string which is then used as the click event for a pie piece # # @example # File.open(@options[:output] + "/file.html",'w') do |f| # pie_js(f,"pie_graph","Vuln Breakdown","Vuln Breakdown",[['Low',2],['Medium',5],['High',3]],"document.location.href = 'vuln_overview.html#' + event.point.name;") # # body_text(f,"
") # end # def pie_js(fp,renderto,title,seriesname,series,clickfunction = nil) fp.puts <<-eos $(function () { var chart; $(document).ready(function() { chart = new Highcharts.Chart({ credits: { enabled: false }, chart: { renderTo: '#{renderto}', plotBackgroundColor: null, plotBorderWidth: null, plotShadow: false }, title: { text: '#{title}' }, tooltip: { formatter: function() { return ''+ this.point.name +': '+ Math.round(this.percentage) +' %'; } }, plotOptions: { pie: { size: '60%', allowPointSelect: true, cursor: 'pointer', dataLabels: { enabled: true, color: '#000000', connectorColor: '#000000', formatter: function() { return ''+ this.point.name +': '+ this.y; }, distance: 20 } eos unless clickfunction.nil? fp.puts ',events: { click: function(event) { ' + clickfunction + '} }' end fp.puts <<-eos } }, series: [{ type: 'pie', name: '#{seriesname}', data: [ eos series.each_with_index do |val,index| tmpline = "\t\t\t{name: '" + val[0] + "', y: " + val[1].to_s + ", color: '" + val[2] + "'}" tmpline += "," unless index == series.length - 1 fp.puts tmpline end fp.puts <<-eos ] }] }); }); }); eos end end end end