class Dawn::Reporter

Public Class Methods

new(options={}) click to toggle source
# File lib/dawn/reporter.rb, line 4
def initialize(options={})
  @engine = nil
  @ret = false

  @filename = options[:filename]
  @ret = options[:apply_all_code] unless options[:apply_all_code].nil?
  @format = options[:format] unless options[:format].nil?
  @engine = options[:engine] unless options[:engine].nil?

  @format = :tabular unless is_valid_format?(@format)
end

Public Instance Methods

report() click to toggle source
# File lib/dawn/reporter.rb, line 16
def report
  ascii_tabular_report  if @format == :tabular
  json_report           if @format == :json
  ascii_plain_report    if @format == :console
  html_report           if @format == :html
end

Private Instance Methods

ascii_plain_report() click to toggle source
# File lib/dawn/reporter.rb, line 279
def ascii_plain_report

  result = sprintf "%s\n", "scanning #{@engine.target}"
  result += sprintf "%s\n", "#{@engine.name} v#{@engine.get_mvc_version} detected" unless @engine.name == "Gemfile.lock"
  result += sprintf "%s\n", "#{@engine.force} v#{@engine.get_mvc_version} detected" if @engine.name == "Gemfile.lock"
  result += sprintf "%s\n", "applying all security checks"
  if @ret
    result += sprintf "%s\n", "#{@engine.applied_checks} security checks applied - #{@engine.skipped_checks} security checks skipped"
  else
    result += sprintf "%s\n", "no security checks in the knowledge base"
  end

  if @engine.count_vulnerabilities != 0
    result += sprintf "%s\n", "#{@engine.count_vulnerabilities} vulnerabilities found"
    @engine.vulnerabilities.each do |vuln|
      result += sprintf "%s\n", "#{vuln[:name]} check failed\n"
      result += sprintf "%s\n", "#{vuln[:message].justify(70)}"
      result += sprintf "%s\n", "Evidence:"
      vuln[:evidences].each do |evidence|
        result += sprintf "%s\n", "\t#{evidence}"
      end
      result += sprintf "\n\n"
    end
    if @engine.has_reflected_xss?
      result += sprintf "%s\n", "#{@engine.reflected_xss.count} reflected XSS found"
      @engine.reflected_xss.each do |vuln|
        result += sprintf "%s\n", "request parameter \"#{vuln[:sink_source]}\" is used without escaping in #{vuln[:sink_view]}. It was read here: #{vuln[:sink_file]}@#{vuln[:sink_line]}"
        result += sprintf "%s\n", "evidence: #{vuln[:sink_evidence]}"
      end
    end

  else
    result += sprintf "%s\n", "no vulnerabilities found."
  end

  if @engine.mitigated_issues.count != 0
    result += sprintf "%s\n", "#{@engine.mitigated_issues.count} mitigated vulnerabilities found"
    @engine.mitigated_issues.each do |vuln|
      result += sprintf "%s\n", "#{vuln[:name]} mitigated"
      vuln[:evidences].each do |evidence|
        result += sprintf "%s\n", evidence
      end
    end
  end

  write(result)
  true
end
ascii_tabular_report() click to toggle source
# File lib/dawn/reporter.rb, line 160
def ascii_tabular_report

  output = @engine.create_output_dir

  # 0_First table: executive summary
  rows = []
  rows << ['Dawn version', Dawn::VERSION] unless Dawn::RELEASE == "(development)"
  rows << ['Dawn development version', Dawn::VERSION] if Dawn::RELEASE == "(development)"
  rows << ['Scan started', @engine.scan_start]
  rows << ['Scan duration', "#{@engine.scan_time.round(3)} sec"]
  rows << ['Target', @engine.target]
  rows << ['Framework', "#{@engine.name} v#{@engine.get_mvc_version}" ] unless @engine.name == "Gemfile.lock"
  rows << ['Framework', "#{@engine.force} v#{@engine.get_mvc_version}" ] if @engine.name == "Gemfile.lock"
  if @ret
    rows << ['Applied checks', "#{@engine.applied_checks} security checks"]
    rows << ['Skipped checks', "#{@engine.skipped_checks} security checks"]
  else
    rows << ['Applied checks', "No security checks in the knowledge base"]
  end
  rows << ['Vulnerabilities found in dependencies', @engine.count_vulnerabilities]
  rows << ['Vulnerabilities mitigated by external factors', @engine.mitigated_issues.count] unless @engine.mitigated_issues.count == 0
  rows << ['Reflected XSS found', @engine.reflected_xss.count]
  table = Terminal::Table.new :title=>'Scan summary', :rows => rows
  write_table(File.join(output, 'summary.txt'), table)

  # 0_a) Application structure
  rows = []
  rows << ['Lines of code', 'to be added soon']
  rows << ['Cyclomatic complexity index', 'to be added soon']
  rows << ['Models', @engine.models.count]
  rows << ['Views', @engine.views.count]
  rows << ['Controllers', @engine.controllers.count]
  table = Terminal::Table.new :title=>'Application stats', :rows => rows
  write_table(File.join(output, 'statistics.txt'), table)

  if @engine.count_vulnerabilities > 0

    # 1) Vulnerabilities

    # 1_a) Third party gem vulnerabilities
    rows = []
    @engine.vulnerabilities.each do |vuln|
      rows << [vuln[:name].justify(10), vuln[:severity], vuln[:message].justify(30), vuln[:remediation].justify(15), vuln[:evidences].join.justify(15)]
      rows << :separator
    end
    table = Terminal::Table.new :title=>"Vulnerabilities", :headings=>['Issue', 'Severity', 'Description', 'Solution', 'Evidences'], :rows=>rows
    write_table(File.join(output, 'third_party_vulnerabilities.txt'), table)

    # 1_b) Refleted XXS
    rows = []
    if @engine.has_reflected_xss?
      @engine.reflected_xss.each do |vuln|
        rows << [vuln[:sink_source], vuln[:sink_view], "#{vuln[:sink_file]}@#{vuln[:sink_line]}",vuln[:sink_evidence]]
        rows << :separator
      end
      table = Terminal::Table.new :title=>"Reflected Cross Site Scripting", :headings=>['Sink name', 'View', 'Location the sink was read', 'Evidences'], :rows=>rows
      write_table(File.join(output, 'reflected_xss.txt'), table)
    end

  end

  if @engine.mitigated_issues.count > 0
    # 2_Mitigated issues
    rows = []
    @engine.mitigated_issues.each do |vuln|
      rows << [vuln[:name].justify(10), vuln[:message].justify(50), vuln[:evidences].join.justify(15)]
      rows << :separator
    end
    table = Terminal::Table.new :title=>"Mitigated issues", :headings=>['Issue', 'Description', 'Evidences'], :rows=>rows
    write_table(File.join(output, 'mitigated_issues.txt'), table)
  end

  true
end
html_report() click to toggle source
# File lib/dawn/reporter.rb, line 69
    def html_report
      output = @engine.create_output_dir if @filename.nil?
      html_head = "<!doctype html>\n<html>\n<head>\n<title>Dawnscanner report for #{File.basename(@engine.target)}</title>"
      html_head +=" <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css\" integrity=\"sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7\" crossorigin=\"anonymous\">"

      html_head += "<script src=\"https://code.jquery.com/jquery-2.2.0.min.js\"></script>"
      html_head += "<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css\" integrity=\"sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r\" crossorigin=\"anonymous\">"

      html_head += "<script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js\" integrity=\"sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS\" crossorigin=\"anonymous\"></script>"
      html_head += "<style type=\"text/css\">body{padding-top:20px;padding-bottom:40px}.container-narrow{margin:0 auto;max-width:700px}.container-narrow>hr{margin:30px 0}.jumbotron,.marketing{margin:60px 0}.jumbotron{text-align:center}.jumbotron h1{font-size:72px;line-height:1}.jumbotron .btn{font-size:21px;padding:14px 24px}.marketing p+h4{margin-top:28px}#wrap{min-height:100%;height:auto!important;height:100%;margin:0 auto -60px}#footer,#push{height:60px}#footer{background-color:#f5f5f5}@media (max-width:767px){#footer{margin-left:-20px;margin-right:-20px;padding-left:20px;padding-right:20px}}
</style>"
      html_head += "</head>\n"

      html_body =  "<body>\n"
      html_body +=  ""
      html_body += "<div id=\"wrap\">\n"
      html_body += "<div class=\"container-narrow\">\n"
      html_body += "<div class=\"masthead\">\n"
      html_body += "<ul class=\"nav nav-pills pull-right\">\n"
      html_body += "<li class=\"\"><a href=\"http://dawnscanner.org\">Home</a></li>\n"
      html_body += "<li class=\"active\"><a href=\"https://github.com/thesp0nge/dawnscanner\">Github repo</a></li>\n"
      html_body += "</ul>\n"
      html_body += "<h3 class=\"muted\">Dawnscanner</h3>\n"
      html_body += "</div>\n"
      html_body += "<h1>Security code review results for \"#{File.basename(@engine.target)}\"</h1>\n"
      html_body += "<hr />\n"
      html_body += "<h2>Scan detail</h2>\n"
      html_body += "<div class=\"row\">\n"
      html_body += "<p>The scan was last executed #{@engine.scan_start.strftime("%d %b %Y - %T")} and Dawn founds #{@engine.count_vulnerabilities} vulnerabilities</p>\n"
      html_body += "</div>\n"
      html_body += "<hr />\n"
      html_body += "<h2>Scan details</h2>\n"
      html_body += "<table class=\"table-striped table-bordered table\">\n"
      html_body += "<thead><tr><td>Key</td><td>Value</td></tr></thead>\n"
      html_body += "<tbody>\n"
      html_body += "<tr><td>Dawn version</td><td>#{Dawn::VERSION}</td></tr>" unless Dawn::RELEASE == "(development)\n"
      html_body += "<tr><td>Dawn development version</td><td>#{Dawn::VERSION}</td></tr>" if Dawn::RELEASE == "(development)\n"
      html_body += "<tr><td>Scan duration</td><td>#{@engine.scan_time.round(3)} sec</td></tr>\n"
      html_body += "<tr><td>Target</td><td>#{@engine.target}</td></tr>\n"
      html_body += "<tr><td>MVC detected framework</td><td>#{@engine.name} v#{@engine.get_mvc_version}</td></tr>" unless @engine.name == "Gemfile.lock\n"
      html_body += "<tr><td>MVC detected framework</td><td>#{@engine.force} v#{@engine.get_mvc_version}</td></tr>" if @engine.name == "Gemfile.lock\n"
      if @ret
        html_body += "<tr><td>Applied checks</td><td>#{@engine.applied_checks} security checks</td></tr>\n"
        html_body += "<tr><td>Skipped checks</td><td>#{@engine.skipped_checks} security checks</td></tr>\n"
      else
        html_body += "<tr><td>Applied checks</td><td>No security checks in the knowledge base</td></tr>\n"
      end

      html_body+="<tr><td>Vulnerabilities found</td><td>#{@engine.count_vulnerabilities}</td></tr>\n"
      html_body+="<tr><td>Mitigated issues found</td><td>#{@engine.mitigated_issues.count}</td></tr>\n"
      html_body+="<tr><td>Reflected XSS</td><td>#{@engine.reflected_xss.count}</td></tr>\n"
      html_body += "</tbody>\n"
      html_body += "</table>\n"


      if @engine.count_vulnerabilities > 0
        html_body += "<hr />\n"
        html_body += "<h2>Vulnerabilities found</h2>\n"
        html_body += "<table class=\"table-striped table-bordered table\">\n"
        html_body += "<thead><tr><td>Name</td><td>Severity</td><td>CVSS score</td><td>Description</td><td>Remediation</td></tr></thead>\n"

        html_body += "<tbody>"
        @engine.vulnerabilities.each do |vuln|
          html_body += "<tr><td><a href=\"#{vuln[:cve_link]}\">#{vuln[:name]}</a></td><td>#{vuln[:severity]}</td><td>#{vuln[:cvss_score]}</td><td>#{vuln[:message]}</td><td>#{vuln[:remediation]}</td></tr>\n"
        end
        html_body += "</tbody>\n"
        html_body += "</table>\n"
      end
      html_body += "<div id=\"push\"></div>\n"
      html_body += "<div id=\"footer\">\n"
      html_body += "<div class=\"container\">\n"
      html_body += "<p class=\"muted credit\">Proudly generated with <a href=\"http://dawnscanner.org\">Dawnscanner</a> &mdash; #{Time.now.strftime("%Y")} &mdash; engine v#{Dawn::VERSION} (#{Dawn::RELEASE})</p>\n"
      html_body += "</div>\n"
      html_body += "</div>\n"
      html_body += "</div>\n"
      html_body += "</div>\n"

      html_body += "</body>\n"
      html_body += "</html>"


      html = html_head + html_body

      unless @filename.nil?
        write(html)
      else
        write_html(output,  html)
      end
      true
    end
is_valid_format?(format) click to toggle source
# File lib/dawn/reporter.rb, line 63
def is_valid_format?(format)
  return false if format.nil?
  return true if (format == :console) || (format == :tabular) || (format == :json) || (format == :html) || (format == :csv)
  return false # otherwise
end
json_report() click to toggle source
# File lib/dawn/reporter.rb, line 236
def json_report
  result = {}
  return {:status=>"KO", :message=>"BUG at #{__FILE__}@#{__LINE__}: target is empty or engine is nil."}.to_json if @engine.target.empty? or @engine.nil?
  return {:status=>"KO", :message=>"#{target} doesn't exist"}.to_json if ! Dir.exist?(@engine.target)
  return {:status=>"KO", :message=>"no security checks applied"}.to_json unless @ret

  result[:status]="OK"
  result[:dawn_version] = Dawn::VERSION
  result[:dawn_status] = "Develoment version" if Dawn::RELEASE == "(development)"
  result[:scan_started] = @engine.scan_start
  result[:scan_duration] = "#{@engine.scan_time.round(3)} sec"
  result[:target]=@engine.target
  result[:mvc]=@engine.name unless @engine.name == "Gemfile.lock"
  result[:mvc]=@engine.force if @engine.name == "Gemfile.lock"
  result[:mvc_version]=@engine.get_mvc_version

  result[:applied_checks_count] = @engine.applied_checks
  result[:skipped_checks_count] = @engine.skipped_checks
  result[:vulnerabilities_count]=@engine.count_vulnerabilities

  result[:mitigated_issues_count] = @engine.mitigated_issues.count
  result[:reflected_xss_count] = @engine.reflected_xss.count
  result[:vulnerabilities]=[]
  @engine.vulnerabilities.each do |v|
    result[:vulnerabilities] << {
      :name => v[:name],
      :cve_link => v[:cve_link],
      :severity => v[:severity],
      :cvss_score => v[:cvss_score],
      :message => v[:message],
      :remediation => v[:remediation]
    }
  end
  result[:mitigated_vuln] = @engine.mitigated_issues
  result[:reflected_xss] = []
  @engine.reflected_xss.each do |r|
    result[:reflected_xss] << "request parameter \"#{r[:sink_source]}\""
  end

  write(result.to_json)
  true
end
write(output) click to toggle source
# File lib/dawn/reporter.rb, line 24
def write(output)

  puts output if @filename.nil?

  unless @filename.nil?
    # $logger.warn "I will use codesake.css, bootstrap.min.css and bootstrap.js stored in ./support/ directory" if @format == :html
    File.open(@filename, "w") do |f|
      f.puts output
    end
    $logger.info "#{@filename} created (#{output.length} bytes)"
  end
end
write_html(path, content) click to toggle source
# File lib/dawn/reporter.rb, line 37
def write_html(path, content)
  css_path = File.join(path, 'css')
  js_path = File.join(path, 'js')
  support_path = File.join(File.dirname(__FILE__), '..', '..', 'support')

  FileUtils.mkdir_p(File.join(path, 'css'))
  FileUtils.mkdir_p(File.join(path, 'js'))

  FileUtils.cp(File.join(support_path, 'bootstrap.js'), js_path)
  FileUtils.cp(File.join(support_path, 'bootstrap.min.css'), css_path)
  FileUtils.cp(File.join(support_path, 'codesake.css'), css_path)

  File.open(File.join(path, 'report.html'), "w") do |f|
    f.puts content
  end
  $logger.info "#{File.join(path, 'report.html')} created (#{File.stat(File.join(path, 'report.html')).size} bytes)"

end
write_table(path, content) click to toggle source
# File lib/dawn/reporter.rb, line 56
def write_table(path, content)
  File.open(path, "w") do |f|
    f.puts content
  end
  $logger.info "#{path} created (#{File.stat(path).size} bytes)"
end