pwn 0.5.709 → 0.5.710
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/documentation/Installation.md +1 -1
- data/etc/default_skills/exploitdev/SKILL.md +38 -0
- data/etc/default_skills/pwn/plugins/aflplusplus/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/credential_attack/SKILL.md +7 -0
- data/etc/default_skills/pwn/plugins/credential_attack/references/urls.md +3 -0
- data/etc/default_skills/pwn/plugins/exploit_db/SKILL.md +49 -0
- data/etc/default_skills/pwn/plugins/exploit_dev/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/frida/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/fuzz/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/gdb/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/k8s/SKILL.md +51 -0
- data/etc/default_skills/pwn/plugins/nuclei/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/{doctor → preflight_checker}/SKILL.md +13 -11
- data/etc/default_skills/pwn/plugins/process_tube/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/recon/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/recon/references/urls.md +1 -0
- data/etc/default_skills/pwn/plugins/semgrep/SKILL.md +48 -0
- data/etc/default_skills/pwn/plugins/volatility/SKILL.md +1 -0
- data/lib/pwn/ai/agent/prompt_builder.rb +1 -1
- data/lib/pwn/ai/agent/tools/sessions.rb +8 -1
- data/lib/pwn/ffi/stdio.rb +3 -1
- data/lib/pwn/plugins/aflplusplus.rb +21 -1
- data/lib/pwn/plugins/android.rb +9 -3
- data/lib/pwn/plugins/credential_attack.rb +62 -3
- data/lib/pwn/plugins/exploit_db.rb +69 -0
- data/lib/pwn/plugins/exploit_dev.rb +69 -3
- data/lib/pwn/plugins/frida.rb +60 -9
- data/lib/pwn/plugins/fuzz.rb +33 -0
- data/lib/pwn/plugins/gdb.rb +25 -5
- data/lib/pwn/plugins/jobs.rb +1 -0
- data/lib/pwn/plugins/k8s.rb +73 -0
- data/lib/pwn/plugins/nuclei.rb +36 -3
- data/lib/pwn/plugins/packet.rb +1 -1
- data/lib/pwn/plugins/{doctor.rb → preflight_checker.rb} +22 -5
- data/lib/pwn/plugins/process_tube.rb +20 -1
- data/lib/pwn/plugins/radare2.rb +1 -1
- data/lib/pwn/plugins/recon.rb +39 -4
- data/lib/pwn/plugins/semgrep.rb +61 -0
- data/lib/pwn/plugins/sock.rb +1 -0
- data/lib/pwn/plugins/sqlmap.rb +1 -1
- data/lib/pwn/plugins/volatility.rb +21 -2
- data/lib/pwn/plugins.rb +4 -1
- data/lib/pwn/setup.rb +13 -1
- data/lib/pwn/version.rb +1 -1
- data/spec/integration/help_contract_spec.rb +2 -0
- data/spec/lib/pwn/plugins/exploit_db_spec.rb +13 -0
- data/spec/lib/pwn/plugins/k8s_spec.rb +13 -0
- data/spec/lib/pwn/plugins/{doctor_spec.rb → preflight_checker_spec.rb} +1 -1
- data/spec/lib/pwn/plugins/semgrep_spec.rb +13 -0
- data/spec/lib/pwn/req_queue_spec.rb +168 -0
- data/third_party/pwn_rdoc.jsonl +43 -9
- metadata +20 -8
data/lib/pwn/plugins/radare2.rb
CHANGED
|
@@ -19,7 +19,7 @@ module PWN
|
|
|
19
19
|
# path: 'required - filesystem path to the binary r2 should open'
|
|
20
20
|
# )
|
|
21
21
|
public_class_method def self.open(opts = {})
|
|
22
|
-
PWN::Plugins::
|
|
22
|
+
PWN::Plugins::PreflightChecker.require_bin!(name: 'r2')
|
|
23
23
|
path = opts[:path].to_s
|
|
24
24
|
raise 'ERROR: path is required' if path.empty?
|
|
25
25
|
raise "ERROR: binary not found: #{path}" unless File.file?(path)
|
data/lib/pwn/plugins/recon.rb
CHANGED
|
@@ -21,16 +21,20 @@ module PWN
|
|
|
21
21
|
raise 'ERROR: domain is required' if domain.empty?
|
|
22
22
|
|
|
23
23
|
names = []
|
|
24
|
-
if PWN::Plugins::
|
|
24
|
+
if PWN::Plugins::PreflightChecker.bin?(name: 'subfinder')
|
|
25
25
|
stdout, = Open3.capture3('subfinder', '-silent', '-d', domain)
|
|
26
26
|
names.concat(stdout.lines.map(&:strip).reject(&:empty?))
|
|
27
27
|
end
|
|
28
|
+
if PWN::Plugins::PreflightChecker.bin?(name: 'amass')
|
|
29
|
+
stdout, = Open3.capture3('amass', 'enum', '-passive', '-d', domain, '-nocolor')
|
|
30
|
+
names.concat(stdout.lines.map(&:strip).reject(&:empty?))
|
|
31
|
+
end
|
|
28
32
|
names.concat(Array(crt_sh(domain: domain)))
|
|
29
33
|
normalize_names(names: names, domain: domain)
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
public_class_method def self.httpx(opts = {})
|
|
33
|
-
PWN::Plugins::
|
|
37
|
+
PWN::Plugins::PreflightChecker.require_bin!(name: 'httpx')
|
|
34
38
|
hosts = Array(opts[:hosts] || opts[:urls])
|
|
35
39
|
stdout, = Open3.capture3('httpx', '-silent', '-json', stdin_data: "#{hosts.join("\n")}\n")
|
|
36
40
|
stdout.each_line.filter_map do |ln|
|
|
@@ -41,8 +45,8 @@ module PWN
|
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
public_class_method def self.masscan(opts = {})
|
|
44
|
-
bin = %w[masscan naabu].find { |b| PWN::Plugins::
|
|
45
|
-
raise PWN::Plugins::
|
|
48
|
+
bin = %w[masscan naabu].find { |b| PWN::Plugins::PreflightChecker.bin?(name: b) }
|
|
49
|
+
raise PWN::Plugins::PreflightChecker::MissingBinary, 'ERROR: masscan/naabu missing' unless bin
|
|
46
50
|
|
|
47
51
|
target = opts[:target].to_s
|
|
48
52
|
ports = (opts[:ports] || '1-1024').to_s
|
|
@@ -75,6 +79,26 @@ module PWN
|
|
|
75
79
|
raise "ERROR: cert-transparency lookup failed for #{domain} (#{errors.join('; ')})"
|
|
76
80
|
end
|
|
77
81
|
|
|
82
|
+
public_class_method def self.amass(opts = {})
|
|
83
|
+
PWN::Plugins::PreflightChecker.require_bin!(name: 'amass')
|
|
84
|
+
domain = opts[:domain].to_s
|
|
85
|
+
raise 'ERROR: domain is required' if domain.empty?
|
|
86
|
+
|
|
87
|
+
stdout, = Open3.capture3('amass', 'enum', '-passive', '-d', domain, '-nocolor')
|
|
88
|
+
stdout.lines.map(&:strip).reject(&:empty?)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
public_class_method def self.passive_dns(opts = {})
|
|
92
|
+
domain = opts[:domain].to_s.downcase.strip
|
|
93
|
+
raise 'ERROR: domain is required' if domain.empty?
|
|
94
|
+
|
|
95
|
+
uri = URI("https://api.hackertarget.com/hostsearch/?q=#{URI.encode_www_form_component(domain)}")
|
|
96
|
+
rows = http_json(url: uri.to_s, timeout: opts[:timeout] || 8)
|
|
97
|
+
Array(rows)
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
{ error: "#{e.class}: #{e.message}", domain: domain }
|
|
100
|
+
end
|
|
101
|
+
|
|
78
102
|
public_class_method def self.authors
|
|
79
103
|
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
80
104
|
end
|
|
@@ -107,6 +131,17 @@ module PWN
|
|
|
107
131
|
timeout: 'optional - seconds to wait before giving up'
|
|
108
132
|
)
|
|
109
133
|
|
|
134
|
+
# Passive amass enum (requires amass).
|
|
135
|
+
#{self}.amass(
|
|
136
|
+
domain: 'required - FQDN to query (e.g. example.com)'
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Passive DNS hostsearch for a domain (hackertarget).
|
|
140
|
+
#{self}.passive_dns(
|
|
141
|
+
domain: 'required - FQDN to query (e.g. example.com)',
|
|
142
|
+
timeout: 'optional - seconds to wait before giving up'
|
|
143
|
+
)
|
|
144
|
+
|
|
110
145
|
# Print the AUTHOR(S) string for this module.
|
|
111
146
|
#{self}.authors
|
|
112
147
|
"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'open3'
|
|
5
|
+
|
|
6
|
+
module PWN
|
|
7
|
+
module Plugins
|
|
8
|
+
# semgrep wrapper feeding SAST-shaped findings.
|
|
9
|
+
module Semgrep
|
|
10
|
+
public_class_method def self.required_bins
|
|
11
|
+
%w[semgrep]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
public_class_method def self.scan(opts = {})
|
|
15
|
+
PWN::Plugins::PreflightChecker.require_bin!(name: 'semgrep')
|
|
16
|
+
target = opts[:path] || opts[:target] || '.'
|
|
17
|
+
cmd = ['semgrep', '--json', '--quiet']
|
|
18
|
+
cmd += ['--config', opts[:config].to_s] if opts[:config]
|
|
19
|
+
cmd << target.to_s
|
|
20
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
|
21
|
+
parsed = begin
|
|
22
|
+
JSON.parse(stdout)
|
|
23
|
+
rescue JSON::ParserError
|
|
24
|
+
{}
|
|
25
|
+
end
|
|
26
|
+
findings = Array(parsed['results']).map do |r|
|
|
27
|
+
{
|
|
28
|
+
title: r['check_id'],
|
|
29
|
+
severity: r.dig('extra', 'severity'),
|
|
30
|
+
path: r['path'],
|
|
31
|
+
line: r.dig('start', 'line'),
|
|
32
|
+
message: r.dig('extra', 'message')
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
{ findings: findings, stderr: stderr, exit: status.exitstatus }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
public_class_method def self.authors
|
|
39
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
public_class_method def self.help
|
|
43
|
+
puts "USAGE:
|
|
44
|
+
# List host binaries this module expects to be installed.
|
|
45
|
+
#{self}.required_bins
|
|
46
|
+
|
|
47
|
+
# Run semgrep --json and map results into finding hashes.
|
|
48
|
+
#{self}.scan(
|
|
49
|
+
path: 'optional - directory or file to scan (defaults to .)',
|
|
50
|
+
target: 'optional - alias for path',
|
|
51
|
+
config: 'optional - semgrep --config value (e.g. auto or p/owasp-top-ten)'
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Print the AUTHOR(S) string for this module.
|
|
55
|
+
#{self}.authors
|
|
56
|
+
"
|
|
57
|
+
constants.sort
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/pwn/plugins/sock.rb
CHANGED
data/lib/pwn/plugins/sqlmap.rb
CHANGED
|
@@ -11,8 +11,8 @@ module PWN
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
public_class_method def self.run(opts = {})
|
|
14
|
-
bin = %w[vol volatility3 vol3].find { |b| PWN::Plugins::
|
|
15
|
-
raise PWN::Plugins::
|
|
14
|
+
bin = %w[vol volatility3 vol3].find { |b| PWN::Plugins::PreflightChecker.bin?(name: b) }
|
|
15
|
+
raise PWN::Plugins::PreflightChecker::MissingBinary, 'ERROR: volatility3 (vol) missing' unless bin
|
|
16
16
|
|
|
17
17
|
memory = opts[:memory] || opts[:file]
|
|
18
18
|
plugin = opts[:plugin] || 'windows.pslist'
|
|
@@ -22,6 +22,16 @@ module PWN
|
|
|
22
22
|
{ stdout: stdout, stderr: stderr, exit: status.exitstatus }
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
public_class_method def self.yara(opts = {})
|
|
26
|
+
PWN::Plugins::PreflightChecker.require_bin!(name: 'yara')
|
|
27
|
+
rules = opts[:rules] || opts[:rule]
|
|
28
|
+
target = opts[:memory] || opts[:file] || opts[:path]
|
|
29
|
+
raise 'ERROR: rules and memory/file are required' if rules.to_s.empty? || target.to_s.empty?
|
|
30
|
+
|
|
31
|
+
stdout, stderr, status = Open3.capture3('yara', rules.to_s, target.to_s)
|
|
32
|
+
{ stdout: stdout, stderr: stderr, exit: status.exitstatus }
|
|
33
|
+
end
|
|
34
|
+
|
|
25
35
|
public_class_method def self.authors
|
|
26
36
|
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
27
37
|
end
|
|
@@ -38,6 +48,15 @@ module PWN
|
|
|
38
48
|
plugin: 'required - plugin value consumed by #run (defaults to windows.pslist)'
|
|
39
49
|
)
|
|
40
50
|
|
|
51
|
+
# Scan a memory image with a yara rules file.
|
|
52
|
+
#{self}.yara(
|
|
53
|
+
rules: 'required - path to a .yar rules file',
|
|
54
|
+
rule: 'optional - alias for rules',
|
|
55
|
+
memory: 'required - memory image path',
|
|
56
|
+
file: 'optional - alias for memory',
|
|
57
|
+
path: 'optional - alias for memory'
|
|
58
|
+
)
|
|
59
|
+
|
|
41
60
|
# Print the AUTHOR(S) string for this module.
|
|
42
61
|
#{self}.authors
|
|
43
62
|
"
|
data/lib/pwn/plugins.rb
CHANGED
|
@@ -20,7 +20,7 @@ module PWN
|
|
|
20
20
|
autoload :Char, 'pwn/plugins/char'
|
|
21
21
|
autoload :CreditCard, 'pwn/plugins/credit_card'
|
|
22
22
|
autoload :CredentialAttack, 'pwn/plugins/credential_attack'
|
|
23
|
-
autoload :
|
|
23
|
+
autoload :PreflightChecker, 'pwn/plugins/preflight_checker'
|
|
24
24
|
autoload :PWNLogger, 'pwn/plugins/pwn_logger'
|
|
25
25
|
autoload :DAOLDAP, 'pwn/plugins/dao_ldap'
|
|
26
26
|
autoload :DAOMongo, 'pwn/plugins/dao_mongo'
|
|
@@ -29,6 +29,7 @@ module PWN
|
|
|
29
29
|
autoload :DefectDojo, 'pwn/plugins/defect_dojo'
|
|
30
30
|
autoload :DetectOS, 'pwn/plugins/detect_os'
|
|
31
31
|
autoload :EIN, 'pwn/plugins/ein'
|
|
32
|
+
autoload :ExploitDB, 'pwn/plugins/exploit_db'
|
|
32
33
|
autoload :FileFu, 'pwn/plugins/file_fu'
|
|
33
34
|
autoload :Fuzz, 'pwn/plugins/fuzz'
|
|
34
35
|
autoload :Frida, 'pwn/plugins/frida'
|
|
@@ -42,6 +43,7 @@ module PWN
|
|
|
42
43
|
autoload :IPInfo, 'pwn/plugins/ip_info'
|
|
43
44
|
autoload :IRC, 'pwn/plugins/irc'
|
|
44
45
|
autoload :Jenkins, 'pwn/plugins/jenkins'
|
|
46
|
+
autoload :K8s, 'pwn/plugins/k8s'
|
|
45
47
|
autoload :JiraDataCenter, 'pwn/plugins/jira_data_center'
|
|
46
48
|
autoload :Jobs, 'pwn/plugins/jobs'
|
|
47
49
|
autoload :JSONPathify, 'pwn/plugins/json_pathify'
|
|
@@ -68,6 +70,7 @@ module PWN
|
|
|
68
70
|
autoload :Recon, 'pwn/plugins/recon'
|
|
69
71
|
autoload :REPL, 'pwn/plugins/repl'
|
|
70
72
|
autoload :ScannableCodes, 'pwn/plugins/scannable_codes'
|
|
73
|
+
autoload :Semgrep, 'pwn/plugins/semgrep'
|
|
71
74
|
autoload :Serial, 'pwn/plugins/serial'
|
|
72
75
|
autoload :Shodan, 'pwn/plugins/shodan'
|
|
73
76
|
autoload :SlackClient, 'pwn/plugins/slack_client'
|
data/lib/pwn/setup.rb
CHANGED
|
@@ -257,6 +257,18 @@ module PWN
|
|
|
257
257
|
apt: %w[], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
258
258
|
plugins: %w[PWN::Plugins::ExploitDev]
|
|
259
259
|
},
|
|
260
|
+
'pwndbg' => {
|
|
261
|
+
apt: %w[pwndbg], dnf: %w[], pacman: %w[pwndbg], brew: %w[], port: %w[],
|
|
262
|
+
plugins: %w[PWN::Plugins::GDB]
|
|
263
|
+
},
|
|
264
|
+
'ropper' => {
|
|
265
|
+
apt: %w[], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
266
|
+
plugins: %w[PWN::Plugins::ExploitDev]
|
|
267
|
+
},
|
|
268
|
+
'rizin' => {
|
|
269
|
+
apt: %w[rizin], dnf: %w[], pacman: %w[rizin], brew: %w[], port: %w[],
|
|
270
|
+
plugins: %w[PWN::Plugins::Radare2]
|
|
271
|
+
},
|
|
260
272
|
'qemu-x86_64' => {
|
|
261
273
|
apt: %w[qemu-user-static], dnf: %w[qemu-user-static], pacman: %w[qemu-user-static],
|
|
262
274
|
brew: %w[], port: %w[],
|
|
@@ -344,7 +356,7 @@ module PWN
|
|
|
344
356
|
re: {
|
|
345
357
|
desc: 'Radare2 · GDB · ExploitDev · Frida · Android RE tier',
|
|
346
358
|
gems: %w[],
|
|
347
|
-
bins: %w[gdb r2 checksec ROPgadget one_gadget qemu-x86_64 yara frida apktool jadx]
|
|
359
|
+
bins: %w[gdb r2 checksec ROPgadget one_gadget qemu-x86_64 yara frida apktool jadx pwndbg ropper rizin]
|
|
348
360
|
},
|
|
349
361
|
full: {
|
|
350
362
|
desc: 'everything above',
|
data/lib/pwn/version.rb
CHANGED
|
@@ -75,6 +75,8 @@ RSpec.describe 'PWN:: modules — .help / .authors behavioural contract', :aggre
|
|
|
75
75
|
|
|
76
76
|
# .help does not raise, completes in <2 s, and produces SOMETHING
|
|
77
77
|
# (stdout, a String return, or a namespace-index Array).
|
|
78
|
+
# PWN::FFI::Stdio attaches libc puts; help must not write fd 1 behind
|
|
79
|
+
# $stdout (Kernel.puts in that module). Still swallow $stdout here.
|
|
78
80
|
out = nil
|
|
79
81
|
expect do
|
|
80
82
|
Timeout.timeout(2) { out = capture_out { mod.help } }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe PWN::Plugins::ExploitDB do
|
|
6
|
+
it 'should display information for authors' do
|
|
7
|
+
expect(described_class).to respond_to :authors
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'should display information for existing help method' do
|
|
11
|
+
expect(described_class).to respond_to :help
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe PWN::Plugins::K8s do
|
|
6
|
+
it 'should display information for authors' do
|
|
7
|
+
expect(described_class).to respond_to :authors
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'should display information for existing help method' do
|
|
11
|
+
expect(described_class).to respond_to :help
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe PWN::Plugins::Semgrep do
|
|
6
|
+
it 'should display information for authors' do
|
|
7
|
+
expect(described_class).to respond_to :authors
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'should display information for existing help method' do
|
|
11
|
+
expect(described_class).to respond_to :help
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'socket'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
|
|
7
|
+
describe 'pwn_debug REQ queue remaining surface' do
|
|
8
|
+
describe PWN::Plugins::ExploitDev do
|
|
9
|
+
it 'catalogs per-arch execve_bin_sh shellcode as opcodes' do
|
|
10
|
+
sc = described_class.shellcode(arch: 'x86_64', kind: 'execve_bin_sh')
|
|
11
|
+
expect(sc.to_s).not_to be_empty
|
|
12
|
+
expect(sc.to_s).not_to eq("\x90")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'one_gadget returns a structured miss when the binary is absent' do
|
|
16
|
+
allow(PWN::Plugins::PreflightChecker).to receive(:bin?).and_call_original
|
|
17
|
+
allow(PWN::Plugins::PreflightChecker).to receive(:bin?).with(name: 'one_gadget').and_return(false)
|
|
18
|
+
row = described_class.one_gadget(path: '/lib/x86_64-linux-gnu/libc.so.6')
|
|
19
|
+
expect(row).to be_a(Hash)
|
|
20
|
+
expect(row[:error].to_s).to match(/one_gadget/i)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'libc_offsets extracts named symbols from an ELF' do
|
|
24
|
+
path = %w[/bin/ls /usr/bin/ls /bin/true /usr/bin/true].find { |p| File.file?(p) }
|
|
25
|
+
skip 'no ELF on host' unless path
|
|
26
|
+
|
|
27
|
+
row = described_class.libc_offsets(path: path)
|
|
28
|
+
expect(row).to include(:path)
|
|
29
|
+
expect(row[:symbols]).to be_a(Hash)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe PWN::Plugins::GDB do
|
|
34
|
+
it 'core raises without a core path' do
|
|
35
|
+
expect { described_class.core(core: '') }.to raise_error(/core is required/)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'breakpoints includes break commands in the gdb batch' do
|
|
39
|
+
allow(Open3).to receive(:capture3).and_return(['ok', '', instance_double(Process::Status, exitstatus: 0)])
|
|
40
|
+
described_class.breakpoints(binary: '/bin/true', breakpoints: ['main', '*0x401000'])
|
|
41
|
+
expect(Open3).to have_received(:capture3) do |*argv|
|
|
42
|
+
joined = argv.flatten.join(' ')
|
|
43
|
+
expect(joined).to include('break main')
|
|
44
|
+
expect(joined).to include('break *0x401000')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe PWN::Plugins::ProcessTube do
|
|
50
|
+
it 'connect uses the same write_line/recvuntil API over TCP' do
|
|
51
|
+
server = TCPServer.new('127.0.0.1', 0)
|
|
52
|
+
port = server.addr[1]
|
|
53
|
+
thr = Thread.new do
|
|
54
|
+
sock = server.accept
|
|
55
|
+
sock.write("banner\n")
|
|
56
|
+
sock.flush
|
|
57
|
+
line = sock.gets
|
|
58
|
+
sock.write("ack #{line}")
|
|
59
|
+
sock.close
|
|
60
|
+
end
|
|
61
|
+
tube = described_class.connect(host: '127.0.0.1', port: port)
|
|
62
|
+
described_class.recvuntil(id: tube[:id], until: "\n", timeout: 2)
|
|
63
|
+
described_class.write_line(id: tube[:id], line: 'hi')
|
|
64
|
+
out = described_class.recvuntil(id: tube[:id], until: 'hi', timeout: 2)
|
|
65
|
+
expect(out).to include('ack')
|
|
66
|
+
ensure
|
|
67
|
+
described_class.close(id: tube[:id]) if tube
|
|
68
|
+
thr&.kill
|
|
69
|
+
server&.close
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe PWN::Setup do
|
|
74
|
+
it 're profile includes pwndbg, ropper, and rizin' do
|
|
75
|
+
bins = PWN::Setup::PROFILES[:re][:bins]
|
|
76
|
+
expect(bins).to include('pwndbg', 'ropper', 'rizin')
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe PWN::Plugins::PreflightChecker do
|
|
81
|
+
it 'check includes missing_services' do
|
|
82
|
+
row = described_class.check.find { |r| r[:plugin].to_s.include?('Packet') }
|
|
83
|
+
expect(row).to include(:missing_services)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'service? is false for a missing unix socket' do
|
|
87
|
+
expect(described_class.service?(name: 'no-such-pwn-svc', path: '/tmp/pwn-no-such.sock')).to be false
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe PWN::Plugins::Sock do
|
|
92
|
+
it 'protocol :raw demands CAP_NET_RAW' do
|
|
93
|
+
allow(PWN::Plugins::PreflightChecker).to receive(:cap_net_raw?).and_return(false)
|
|
94
|
+
expect do
|
|
95
|
+
described_class.connect(target: '127.0.0.1', port: 9, protocol: :raw)
|
|
96
|
+
end.to raise_error(PWN::Plugins::PreflightChecker::MissingCapability)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe PWN::Plugins::Nuclei do
|
|
101
|
+
it 'to_findings maps JSONL rows into report-shaped hashes' do
|
|
102
|
+
rows = [{ 'info' => { 'name' => 'xss', 'severity' => 'high' }, 'matched-at' => 'https://x/' }]
|
|
103
|
+
out = described_class.to_findings(rows: rows)
|
|
104
|
+
expect(out.first).to include(:title, :severity, :url)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe PWN::Plugins::Fuzz do
|
|
109
|
+
it 'http mutates a request with dictionary payloads' do
|
|
110
|
+
reqs = described_class.http(
|
|
111
|
+
request: "GET /FUZZ HTTP/1.1\r\nHost: x\r\n\r\n",
|
|
112
|
+
dictionary: %w[admin ../]
|
|
113
|
+
)
|
|
114
|
+
expect(reqs.length).to eq(2)
|
|
115
|
+
expect(reqs.first).to include('admin')
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'file_format mutates a seed with dictionary blobs' do
|
|
119
|
+
Dir.mktmpdir do |dir|
|
|
120
|
+
seed = File.join(dir, 'seed.bin')
|
|
121
|
+
File.binwrite(seed, 'AAAA')
|
|
122
|
+
out = described_class.file_format(path: seed, dictionary: ["\xff\xff"])
|
|
123
|
+
expect(out).to be_an(Array)
|
|
124
|
+
expect(out.first).to be_a(String)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
describe PWN::Plugins::ArtifactRegistry do
|
|
130
|
+
it 'lists artifacts for a session id' do
|
|
131
|
+
Dir.mktmpdir do |dir|
|
|
132
|
+
stub_const('PWN::Plugins::ArtifactRegistry::ROOT', dir)
|
|
133
|
+
src = File.join(dir, 'loot.txt')
|
|
134
|
+
File.write(src, 'x')
|
|
135
|
+
described_class.register(session_id: 'sess1', path: src, kind: 'loot')
|
|
136
|
+
rows = described_class.list(session_id: 'sess1')
|
|
137
|
+
expect(rows.first[:kind]).to eq('loot')
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
describe PWN::Plugins::Frida do
|
|
143
|
+
it 'ssl_pinning_script returns injectable javascript' do
|
|
144
|
+
js = described_class.ssl_pinning_script
|
|
145
|
+
expect(js).to match(/SSL_CTX|TrustManager|pinning/i)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe PWN::Plugins::Semgrep do
|
|
150
|
+
it 'responds to scan' do
|
|
151
|
+
expect(described_class).to respond_to(:scan)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe PWN::Plugins::ExploitDB do
|
|
156
|
+
it 'responds to search and cve_for_cpe' do
|
|
157
|
+
expect(described_class).to respond_to(:search)
|
|
158
|
+
expect(described_class).to respond_to(:cve_for_cpe)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe PWN::Plugins::K8s do
|
|
163
|
+
it 'responds to trivy and kube_hunter' do
|
|
164
|
+
expect(described_class).to respond_to(:trivy)
|
|
165
|
+
expect(described_class).to respond_to(:kube_hunter)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|