Module: PWN::Reports
- Defined in:
- lib/pwn/reports.rb,
lib/pwn/reports/csv.rb,
lib/pwn/reports/pdf.rb,
lib/pwn/reports/xml.rb,
lib/pwn/reports/fuzz.rb,
lib/pwn/reports/html.rb,
lib/pwn/reports/json.rb,
lib/pwn/reports/sast.rb,
lib/pwn/reports/phone.rb,
lib/pwn/reports/sarif.rb,
lib/pwn/reports/markdown.rb,
lib/pwn/reports/engagement.rb,
lib/pwn/reports/uri_buster.rb,
lib/pwn/reports/ai_red_team.rb,
lib/pwn/reports/html_footer.rb,
lib/pwn/reports/html_header.rb
Overview
Defined Under Namespace
Modules: AIRedTeam, CSV, Engagement, Fuzz, HTML, HTMLFooter, HTMLHeader, JSON, Markdown, PDF, Phone, SARIF, SAST, URIBuster, XML
Class Method Summary
collapse
Class Method Details
.attack_chains(opts = {}) ⇒ Object
Connected explicit references only; references never imply a severity boost.
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
# File 'lib/pwn/reports.rb', line 67
public_class_method def self.attack_chains(opts = {})
rows = Array(opts[:findings]).map { |row| stringify_keys(hash: row) }
by_id = rows.to_h { |row| [row['id'].to_s, row] }
adjacency = Hash.new { |hash, key| hash[key] = [] }
rows.each do |row|
id = row['id'].to_s
refs = Array(row['attack_chain_refs'] || row['chain_refs'] || row['chain_parent_id'])
refs.each do |ref|
ref = ref.to_s
next if id.empty? || ref == id || !by_id.key?(ref)
next unless row['engagement_id'].to_s == by_id[ref]['engagement_id'].to_s
adjacency[id] << ref
adjacency[ref] << id
end
end
seen = []
adjacency.keys.sort.filter_map do |id|
next if seen.include?(id)
group = []
pending = [id]
until pending.empty?
current = pending.shift
next if group.include?(current)
group << current
pending.concat(adjacency[current])
end
seen.concat(group)
ranks = %w[info low medium high critical]
severity = group.map { |key| by_id[key]['severity'].to_s }.max_by { |value| ranks.index(value) || -1 }
{ finding_ids: group.sort, combined_severity: severity,
rationale: 'Maximum recorded constituent severity. No automatic escalation; linking is not proof of combined exploitability.' }
end
end
|
.authors ⇒ Object
113
114
115
|
# File 'lib/pwn/reports.rb', line 113
public_class_method def self.authors
"AUTHOR(S):\n 0day Inc. <[email protected]>\n"
end
|
.help ⇒ Object
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
# File 'lib/pwn/reports.rb', line 117
public_class_method def self.help
puts "USAGE:
# Run resolve path and return its result
#{self}.resolve_path(
path: 'required - filesystem path to read or write',
ext: 'optional - ext value consumed by #resolve_path',
dir_path: 'optional - dir path value consumed by #resolve_path',
report_name: 'optional - report name value consumed by #resolve_path'
)
# Compose explicit same-engagement references; never invent severity escalation.
#{self}.attack_chains(findings: 'required - Array of finding hashes')
# Run report payload and return its result
#{self}.report_payload(
results_hash: 'optional - results hash value consumed by #report_payload',
title: 'optional - title value consumed by #report_payload',
executive_summary: 'optional - executive summary value consumed by #report_payload'
)
# Print the AUTHOR(S) string for this module.
#{self}.authors
"
constants.sort
end
|
.report_payload(opts = {}) ⇒ Object
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
# File 'lib/pwn/reports.rb', line 42
public_class_method def self.report_payload(opts = {})
raw = opts[:results_hash]
raw = {} unless raw.is_a?(Hash)
title = (
opts[:title] ||
raw[:title] || raw['title'] ||
raw[:report_name] || raw['report_name'] ||
'PWN Report'
).to_s
summary = (
opts[:executive_summary] ||
raw[:executive_summary] || raw['executive_summary']
).to_s
findings = raw[:findings] || raw['findings'] || raw[:data] || raw['data'] || []
findings = [] unless findings.is_a?(Array)
{
title: title,
executive_summary: summary,
findings: findings.map { |row| stringify_keys(hash: row) },
attack_chains: attack_chains(findings: findings),
raw: raw
}
end
|
.resolve_path(opts = {}) ⇒ Object
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
# File 'lib/pwn/reports.rb', line 26
public_class_method def self.resolve_path(opts = {})
path = opts[:path].to_s
ext = opts[:ext].to_s.sub(/\A\./, '')
unless path.empty?
FileUtils.mkdir_p(File.dirname(path)) unless File.dirname(path).to_s.empty? || File.dirname(path) == '.'
return path
end
dir = opts[:dir_path].to_s
dir = '.' if dir.empty?
FileUtils.mkdir_p(dir)
name = opts[:report_name].to_s
name = File.basename(Dir.pwd) if name.empty?
File.join(dir, "#{name}.#{ext}")
end
|