Module: PWN::Reports::Markdown

Defined in:
lib/pwn/reports/markdown.rb

Overview

Generic Markdown report writer for pentest / findings payloads.

Class Method Summary collapse

Class Method Details

.authors ⇒ Object



74
75
76
# File 'lib/pwn/reports/markdown.rb', line 74

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <[email protected]>\n"
end

.generate(opts = {}) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/pwn/reports/markdown.rb', line 7

public_class_method def self.generate(opts = {})
  out = PWN::Reports.resolve_path(opts.merge(ext: 'md'))
  payload = PWN::Reports.package_evidence(payload: PWN::Reports.report_payload(opts), path: out)
  lines = ["# #{escape(text: payload[:title])}", '']
  lines += ['## Executive summary', '', escape(text: payload[:executive_summary]), ''] unless payload[:executive_summary].to_s.empty?
  lines += ['## Ranked priorities', '']
  Array(payload[:priorities]).each_with_index do |priority, index|
    lines << "#{index + 1}. **#{escape(text: priority[:combined_severity])}** — #{escape(text: priority[:title])} (#{escape(text: priority[:kind])}). #{escape(text: priority[:rationale])}"
  end
  lines += ['', '## Attack chains', '']
  payload[:attack_chains].each do |chain|
    lines += ["### #{escape(text: chain[:title])}", '',
              "Directed path: #{escape(text: chain[:finding_ids].join(' -> '))}", '',
              "Combined severity: **#{escape(text: chain[:combined_severity])}**; Assessment: #{escape(text: chain[:assessment_status])}", '',
              escape(text: chain[:rationale]), '', '#### Directed links', '']
    Array(chain[:links]).each { |link| lines << "- #{escape(text: "#{link[:from]} -> #{link[:to]}")}" }
    lines += ['', '#### PoC reproduction steps', '']
    Array(chain[:reproduction_steps]).each_with_index { |step, index| lines << "#{index + 1}. #{escape(text: step)}" }
    lines += ['', '#### Evidence', '']
    Array(chain[:evidence_artifacts]).each do |artifact|
      artifact = artifact.transform_keys(&:to_s)
      label = escape(text: artifact['label'])
      lines << (artifact['attachment'] ? "[#{label}](#{artifact['attachment']})" : label)
      lines << "- Kind: #{escape(text: artifact['kind'])}; Handle: #{escape(text: artifact['handle'])}; SHA-256: #{escape(text: artifact['sha256'])}; Size: #{escape(text: artifact['size'])} bytes"
    end
    lines << ''
  end
  lines += ['## Findings', '']
  if payload[:findings].empty?
    lines << '_No findings._'
  else
    payload[:findings].each do |row|
      lines << "### #{escape(text: row['id'].to_s.empty? ? row['title'] : "#{row['id']}: #{row['title']}")}"
      lines << ''
      row.each do |key, val|
        next if %w[id title reproduction_steps severity_justification poc evidence_artifacts poc_export].include?(key.to_s)

        lines << "- **#{escape(text: key)}**: #{escape(text: val)}"
      end
      lines += ['', '#### Reproduction steps', '']
      steps = Array(row['reproduction_steps'])
      lines += steps.empty? ? ['Not supplied'] : steps.each_with_index.map { |step, index| "#{index + 1}. #{escape(text: step)}" }
      justification = row['severity_justification'].to_s
      lines += ['', '#### Severity justification', '', justification.empty? ? 'Not supplied' : escape(text: justification)]
      code = PWN::Reports.poc_preview(text: row['poc'])
      fence = '`' * [3, (code.scan(/`+/).map(&:length).max || 0) + 1].max
      lines += ['', '#### PoC command/code', '', fence, code.empty? ? 'Not supplied' : code, fence]
      lines += ['', '#### Evidence', '']
      (Array(row['evidence_artifacts']) + [row['poc_export']].compact).each do |artifact|
        label = escape(text: artifact['label'])
        link = "[#{label}](#{artifact['attachment']})"
        lines << "#{'!' if artifact['inline_image']}#{link}"
        lines << "- Kind: #{escape(text: artifact['kind'])}; Handle: #{escape(text: artifact['handle'])}; SHA-256: #{artifact['sha256']}; Size: #{artifact['size']} bytes"
      end
      lines << ''
    end
  end

  File.write(out, "#{lines.join("\n").rstrip}\n")
  out
end

.help ⇒ Object



78
79
80
81
82
83
84
85
86
87
# File 'lib/pwn/reports/markdown.rb', line 78

public_class_method def self.help
  puts "USAGE:
    # Run generate and return its result
    #{self}.generate

    # Print the AUTHOR(S) string for this module.
    #{self}.authors
  "
  constants.sort
end