pwn 0.5.721 → 0.5.723
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/bin/pwn_setup +5 -5
- data/documentation/AI-Integration.md +34 -1
- data/documentation/Policy-Benchmark.md +151 -0
- data/documentation/Reinforcement-Learning.md +27 -1
- data/etc/default_skills/pwn/ai/agent/engagement/SKILL.md +1 -0
- data/etc/default_skills/pwn/ai/agent/learning/SKILL.md +2 -0
- data/etc/default_skills/pwn/ai/agent/metrics/SKILL.md +4 -0
- data/etc/default_skills/pwn/ai/agent/mistakes/SKILL.md +2 -0
- data/etc/default_skills/pwn/ai/agent/policy/SKILL.md +1 -0
- data/etc/default_skills/pwn/ai/agent/reward/SKILL.md +2 -0
- data/etc/default_skills/pwn/ai/agent/swarm/SKILL.md +6 -0
- data/etc/default_skills/pwn/ai/agent/tools/capabilities/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/context/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/context/SKILL.md +50 -0
- data/etc/default_skills/pwn/ai/http_retry/SKILL.md +7 -0
- data/etc/default_skills/pwn/ai/http_retry/references/urls.md +4 -0
- data/etc/default_skills/pwn/ai/open_ai/SKILL.md +1 -0
- data/etc/default_skills/pwn/ai/open_ai/references/urls.md +1 -0
- data/etc/default_skills/pwn/plugins/exploit_dev/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/findings/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/gdb/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/gdbmi/SKILL.md +55 -0
- data/etc/default_skills/pwn/plugins/ghidra_headless/SKILL.md +49 -0
- data/etc/default_skills/pwn/plugins/jobs/SKILL.md +6 -0
- data/etc/default_skills/pwn/plugins/packet/SKILL.md +3 -0
- data/etc/default_skills/pwn/plugins/preflight_checker/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/radare2/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/transparent_browser/SKILL.md +3 -0
- data/etc/default_skills/pwn/reports/engagement/SKILL.md +3 -2
- data/lib/pwn/ai/agent/curriculum.rb +37 -45
- data/lib/pwn/ai/agent/dispatch.rb +9 -0
- data/lib/pwn/ai/agent/engagement.rb +59 -0
- data/lib/pwn/ai/agent/learning.rb +111 -44
- data/lib/pwn/ai/agent/loop.rb +50 -8
- data/lib/pwn/ai/agent/metrics.rb +70 -2
- data/lib/pwn/ai/agent/mistakes.rb +67 -1
- data/lib/pwn/ai/agent/policy.rb +142 -16
- data/lib/pwn/ai/agent/registry.rb +1 -1
- data/lib/pwn/ai/agent/reward.rb +153 -53
- data/lib/pwn/ai/agent/swarm.rb +235 -35
- data/lib/pwn/ai/agent/tool_guard.rb +13 -1
- data/lib/pwn/ai/agent/tools/artifacts.rb +50 -3
- data/lib/pwn/ai/agent/tools/capabilities.rb +19 -0
- data/lib/pwn/ai/agent/tools/context.rb +38 -0
- data/lib/pwn/ai/agent/tools/finding_record.rb +18 -0
- data/lib/pwn/ai/agent/tools/fuzz_campaign.rb +10 -1
- data/lib/pwn/ai/agent/tools/job_run.rb +32 -0
- data/lib/pwn/ai/agent/tools/learning.rb +5 -6
- data/lib/pwn/ai/agent/tools/metrics.rb +16 -0
- data/lib/pwn/ai/agent/tools/pty_session.rb +4 -4
- data/lib/pwn/ai/agent/tools/ruby_eval.rb +6 -5
- data/lib/pwn/ai/agent/tools/shell.rb +10 -1
- data/lib/pwn/ai/agent/tools/skills.rb +30 -0
- data/lib/pwn/ai/agent/tools/swarm.rb +8 -2
- data/lib/pwn/ai/context.rb +193 -0
- data/lib/pwn/ai/http_retry.rb +53 -7
- data/lib/pwn/ai/open_ai.rb +302 -45
- data/lib/pwn/ai.rb +1 -0
- data/lib/pwn/migrate.rb +10 -1
- data/lib/pwn/plugins/artifact_registry.rb +40 -17
- data/lib/pwn/plugins/binary_parser.rb +4 -1
- data/lib/pwn/plugins/exploit_dev.rb +15 -0
- data/lib/pwn/plugins/findings.rb +48 -8
- data/lib/pwn/plugins/gdb.rb +17 -0
- data/lib/pwn/plugins/gdbmi.rb +128 -0
- data/lib/pwn/plugins/ghidra_headless.rb +104 -0
- data/lib/pwn/plugins/jobs.rb +72 -0
- data/lib/pwn/plugins/packet.rb +51 -0
- data/lib/pwn/plugins/preflight_checker.rb +29 -0
- data/lib/pwn/plugins/process_tube.rb +24 -7
- data/lib/pwn/plugins/radare2.rb +14 -2
- data/lib/pwn/plugins/repl.rb +6 -1
- data/lib/pwn/plugins/transparent_browser.rb +64 -0
- data/lib/pwn/plugins.rb +2 -0
- data/lib/pwn/reports/engagement.rb +19 -0
- data/lib/pwn/sessions.rb +3 -1
- data/lib/pwn/version.rb +1 -1
- data/scripts/benchmark_policy.rb +276 -0
- data/spec/documentation/installation_md_spec.rb +18 -4
- data/spec/integration/reinforced_feedback_loop_spec.rb +20 -13
- data/spec/lib/pwn/ai/agent/curriculum_spec.rb +267 -0
- data/spec/lib/pwn/ai/agent/engagement_spec.rb +12 -0
- data/spec/lib/pwn/ai/agent/learning_spec.rb +81 -3
- data/spec/lib/pwn/ai/agent/loop_spec.rb +51 -0
- data/spec/lib/pwn/ai/agent/metrics_spec.rb +44 -0
- data/spec/lib/pwn/ai/agent/mistakes_spec.rb +10 -0
- data/spec/lib/pwn/ai/agent/policy_spec.rb +103 -0
- data/spec/lib/pwn/ai/agent/registry_spec.rb +62 -0
- data/spec/lib/pwn/ai/agent/reward_spec.rb +175 -12
- data/spec/lib/pwn/ai/agent/swarm_spec.rb +121 -1
- data/spec/lib/pwn/ai/agent/tool_guard_spec.rb +6 -0
- data/spec/lib/pwn/ai/agent/tools/capabilities_spec.rb +14 -0
- data/spec/lib/pwn/ai/agent/tools/context_spec.rb +14 -0
- data/spec/lib/pwn/ai/agent/tools/job_run_spec.rb +2 -0
- data/spec/lib/pwn/ai/agent/tools/learning_spec.rb +25 -0
- data/spec/lib/pwn/ai/context_spec.rb +48 -0
- data/spec/lib/pwn/ai/http_retry_spec.rb +27 -0
- data/spec/lib/pwn/ai/open_ai_oauth_transport_spec.rb +245 -0
- data/spec/lib/pwn/ai/open_ai_spec.rb +150 -0
- data/spec/lib/pwn/migrate_spec.rb +24 -0
- data/spec/lib/pwn/plugins/artifact_registry_spec.rb +19 -0
- data/spec/lib/pwn/plugins/findings_spec.rb +2 -0
- data/spec/lib/pwn/plugins/gdbmi_spec.rb +17 -0
- data/spec/lib/pwn/plugins/ghidra_headless_spec.rb +17 -0
- data/third_party/pwn_rdoc.jsonl +86 -2
- metadata +24 -5
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pwn-plugins-ghidraheadless
|
|
3
|
+
description: Drive PWN::Plugins::GhidraHeadless from pwn_eval.
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: [pwn, pwn_eval]
|
|
6
|
+
metadata:
|
|
7
|
+
bundled: true
|
|
8
|
+
generated: true
|
|
9
|
+
module: PWN::Plugins::GhidraHeadless
|
|
10
|
+
source: pwn/plugins/ghidra_headless.rb
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# PWN::Plugins::GhidraHeadless
|
|
14
|
+
|
|
15
|
+
analyzeHeadless wrapper that exports decompiled C plus the symbol table as JSON, cached by binary SHA-256.
|
|
16
|
+
|
|
17
|
+
## When to use
|
|
18
|
+
|
|
19
|
+
Call `PWN::Plugins::GhidraHeadless` from `pwn_eval` when the task needs this module.
|
|
20
|
+
Do not reimplement it in shell.
|
|
21
|
+
|
|
22
|
+
## Methodologies
|
|
23
|
+
|
|
24
|
+
Generated from `pwn/plugins/ghidra_headless.rb`. Prefer the public class methods below.
|
|
25
|
+
Class methods take `(opts = {})` and read `opts`.
|
|
26
|
+
|
|
27
|
+
## How to call
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
PWN::Plugins::GhidraHeadless.help
|
|
31
|
+
PWN::Plugins::GhidraHeadless.required_bins(opts)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Public methods
|
|
35
|
+
|
|
36
|
+
- `required_bins`
|
|
37
|
+
- `analyze`
|
|
38
|
+
- `decompile`
|
|
39
|
+
- `authors`
|
|
40
|
+
- `help`
|
|
41
|
+
|
|
42
|
+
## Source
|
|
43
|
+
|
|
44
|
+
`pwn/plugins/ghidra_headless.rb`
|
|
45
|
+
|
|
46
|
+
## Verification
|
|
47
|
+
|
|
48
|
+
`PWN::Plugins::GhidraHeadless.respond_to?(:required_bins)` after the
|
|
49
|
+
module is loaded. Read the source for parameter names.
|
|
@@ -28,11 +28,12 @@ Class methods take `(opts = {})` and read `opts`.
|
|
|
28
28
|
|
|
29
29
|
```ruby
|
|
30
30
|
PWN::Reports::Engagement.help
|
|
31
|
-
PWN::Reports::Engagement.
|
|
31
|
+
PWN::Reports::Engagement.render(opts)
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## Public methods
|
|
35
35
|
|
|
36
|
+
- `render`
|
|
36
37
|
- `generate`
|
|
37
38
|
- `authors`
|
|
38
39
|
- `help`
|
|
@@ -43,5 +44,5 @@ PWN::Reports::Engagement.generate(opts)
|
|
|
43
44
|
|
|
44
45
|
## Verification
|
|
45
46
|
|
|
46
|
-
`PWN::Reports::Engagement.respond_to?(:
|
|
47
|
+
`PWN::Reports::Engagement.respond_to?(:render)` after the
|
|
47
48
|
module is loaded. Read the source for parameter names.
|
|
@@ -104,11 +104,12 @@ module PWN
|
|
|
104
104
|
|
|
105
105
|
prompts = generate_reproducers(mistake: m, count: [per, 2].max)
|
|
106
106
|
runs = dry_run ? [] : prompts.map { |p| self_play(prompt: p, tag: "practice:#{m[:signature]}") }
|
|
107
|
-
solved = runs.select { |r| r[:
|
|
108
|
-
|
|
107
|
+
solved = runs.select { |r| r[:success] == true && !r[:training_score].nil? }
|
|
108
|
+
scored = runs.reject { |r| r[:training_score].nil? }
|
|
109
|
+
mean = scored.empty? ? nil : (scored.sum { |r| r[:training_score].to_f } / scored.length)
|
|
109
110
|
resolved = false
|
|
110
111
|
# 2.4 — auto-resolve only with N≥2 holdout successes + store trace
|
|
111
|
-
# P23 — auto-resolve only with N≥2
|
|
112
|
+
# P23 — auto-resolve only with N≥2 trusted holdout successes AND a
|
|
112
113
|
# real tool trace (not empty-final luck). Budget fingerprints
|
|
113
114
|
# additionally require mean holdout ≥0.7 and short-horizon tags.
|
|
114
115
|
if solved.length >= 2 && defined?(Mistakes)
|
|
@@ -130,7 +131,7 @@ module PWN
|
|
|
130
131
|
bump_cooldown!(cooldown: cool, signature: m[:signature], mean: mean) unless dry_run
|
|
131
132
|
results << {
|
|
132
133
|
signature: m[:signature], tool: m[:tool], prompts: prompts,
|
|
133
|
-
runs: runs
|
|
134
|
+
runs: runs,
|
|
134
135
|
resolved: false, mean_score: mean.round(3),
|
|
135
136
|
reason: 'holdouts_ok_but_trace_weak'
|
|
136
137
|
}
|
|
@@ -177,14 +178,14 @@ module PWN
|
|
|
177
178
|
end
|
|
178
179
|
resolved = true
|
|
179
180
|
cool.delete(m[:signature].to_s)
|
|
180
|
-
elsif !dry_run
|
|
181
|
+
elsif !dry_run && !mean.nil?
|
|
181
182
|
# P1 — track zero-progress nights; park after COOLDOWN_FAIL_NIGHTS
|
|
182
183
|
bump_cooldown!(cooldown: cool, signature: m[:signature], mean: mean)
|
|
183
184
|
end
|
|
184
185
|
results << {
|
|
185
186
|
signature: m[:signature], tool: m[:tool], prompts: prompts,
|
|
186
|
-
runs: runs
|
|
187
|
-
resolved: resolved, mean_score: mean
|
|
187
|
+
runs: runs,
|
|
188
|
+
resolved: resolved, mean_score: mean&.round(3)
|
|
188
189
|
}
|
|
189
190
|
end
|
|
190
191
|
end
|
|
@@ -264,7 +265,7 @@ module PWN
|
|
|
264
265
|
|
|
265
266
|
if commit && defined?(Learning)
|
|
266
267
|
prior = Learning.outcomes(limit: 500).find do |o|
|
|
267
|
-
o[:session_id].to_s == sid.to_s && o[:
|
|
268
|
+
o[:session_id].to_s == sid.to_s && !o[:training_score].nil? && Array(o[:tags]).include?('offline_judge')
|
|
268
269
|
end
|
|
269
270
|
next if prior
|
|
270
271
|
end
|
|
@@ -278,38 +279,31 @@ module PWN
|
|
|
278
279
|
next if req.strip.empty? || fin.strip.empty?
|
|
279
280
|
|
|
280
281
|
v = Reward.judge(request: req, final: fin, session_id: sid, commit: commit)
|
|
281
|
-
|
|
282
|
+
known = !v[:training_score].nil?
|
|
283
|
+
Reward.prm(request: req, session_id: sid) if do_prm && commit && known
|
|
282
284
|
# P7/W3 — offline path must also fill calibration so the controller
|
|
283
285
|
# (force plan_first/critic at n≥8) actually becomes reachable under
|
|
284
286
|
# :failure_only local introspect. Pull p(success)= out of any PLAN.
|
|
285
|
-
if commit
|
|
287
|
+
if commit && known
|
|
286
288
|
plan = t.find { |e| e[:role].to_s == 'assistant' && e[:content].to_s.start_with?('PLAN:') }
|
|
287
289
|
pred = plan && plan[:content].to_s[/p\(success\)\s*=\s*([01](?:\.\d+)?)/i, 1]
|
|
288
290
|
if pred
|
|
289
291
|
eng = (PWN::Env.dig(:ai, :active) if defined?(PWN::Env))
|
|
290
|
-
calibrate(predicted: pred.to_f, actual: v[:
|
|
292
|
+
calibrate(predicted: pred.to_f, actual: v[:success] ? 1.0 : 0.0, engine: eng)
|
|
291
293
|
end
|
|
292
294
|
end
|
|
293
295
|
if commit && defined?(Learning)
|
|
294
|
-
|
|
295
|
-
sc = v[:score].to_f
|
|
296
|
-
verd = if defined?(Learning) && Learning.respond_to?(:verdict_for_score, true)
|
|
297
|
-
Learning.send(:verdict_for_score, score: sc).to_s
|
|
298
|
-
elsif sc >= 0.6 then 'solved'
|
|
299
|
-
elsif sc >= 0.3 then 'partial'
|
|
300
|
-
else 'wrong'
|
|
301
|
-
end
|
|
296
|
+
verd = v[:verdict].to_s
|
|
302
297
|
Learning.note_outcome(
|
|
303
298
|
task: req[0, 120],
|
|
304
|
-
|
|
305
|
-
score:
|
|
306
|
-
details: "offline_judge #{verd}(#{sc.round(2)}) #{v[:rationale]}",
|
|
299
|
+
outcome: v,
|
|
300
|
+
details: "offline_judge #{verd}(#{v[:score]}) #{v[:rationale]}",
|
|
307
301
|
session_id: sid,
|
|
308
302
|
tags: ['offline_judge', 'auto', verd],
|
|
309
303
|
judge_source: v[:source]
|
|
310
304
|
)
|
|
311
305
|
end
|
|
312
|
-
scored <<
|
|
306
|
+
scored << v.merge(session_id: sid)
|
|
313
307
|
end
|
|
314
308
|
|
|
315
309
|
mean = scored.empty? ? nil : (scored.sum { |r| r[:score].to_f } / scored.length).round(3)
|
|
@@ -1059,9 +1053,11 @@ module PWN
|
|
|
1059
1053
|
rescue StandardError
|
|
1060
1054
|
nil
|
|
1061
1055
|
end
|
|
1062
|
-
|
|
1056
|
+
v.merge(session_id: sid, prompt: opts[:prompt], final: final, trace: trace)
|
|
1063
1057
|
rescue StandardError => e
|
|
1064
|
-
{
|
|
1058
|
+
Reward.resolve_outcome(outcome: { source: :error, error: e.message }).merge(
|
|
1059
|
+
session_id: sid, prompt: opts[:prompt], final: final, trace: trace
|
|
1060
|
+
)
|
|
1065
1061
|
end
|
|
1066
1062
|
|
|
1067
1063
|
private_class_method def self.score_branch(opts = {})
|
|
@@ -1204,7 +1200,8 @@ module PWN
|
|
|
1204
1200
|
smoke_ok = cand_smoke[:resolved].to_i >= base_smoke[:resolved].to_i &&
|
|
1205
1201
|
cand_smoke[:mean_score].to_f + 0.05 >= base_smoke[:mean_score].to_f
|
|
1206
1202
|
|
|
1207
|
-
|
|
1203
|
+
outcomes_known = [baseline, candid, base_smoke, cand_smoke].all? { |r| r[:unknown]&.zero? }
|
|
1204
|
+
promote = outcomes_known && resolved_win && mean_win && smoke_ok
|
|
1208
1205
|
{
|
|
1209
1206
|
baseline: opts[:baseline],
|
|
1210
1207
|
candidate: opts[:candidate],
|
|
@@ -1223,6 +1220,7 @@ module PWN
|
|
|
1223
1220
|
resolved_win: resolved_win,
|
|
1224
1221
|
mean_win: mean_win,
|
|
1225
1222
|
smoke_ok: smoke_ok,
|
|
1223
|
+
outcomes_known: outcomes_known,
|
|
1226
1224
|
promote: promote,
|
|
1227
1225
|
evalset_size: evalset.length,
|
|
1228
1226
|
gate_version: 2
|
|
@@ -1270,34 +1268,28 @@ module PWN
|
|
|
1270
1268
|
|
|
1271
1269
|
private_class_method def self.replay_on_detailed(opts = {})
|
|
1272
1270
|
tag = opts[:tag].to_s
|
|
1273
|
-
return { resolved: 0, mean_score: 0.0, scores: [] } if tag.empty?
|
|
1271
|
+
return { resolved: 0, mean_score: 0.0, scores: [], outcomes: [], unknown: Array(opts[:evalset]).length } if tag.empty?
|
|
1274
1272
|
|
|
1275
|
-
|
|
1273
|
+
outcomes = []
|
|
1276
1274
|
with_ollama_model(tag: tag) do
|
|
1277
1275
|
Array(opts[:evalset]).each do |e|
|
|
1278
|
-
|
|
1279
|
-
scores << r[:score].to_f
|
|
1276
|
+
outcomes << self_play(prompt: e[:prompt], tag: "gate:#{tag}")
|
|
1280
1277
|
end
|
|
1281
1278
|
end
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1279
|
+
known = outcomes.reject { |r| r[:training_score].nil? }
|
|
1280
|
+
resolved = known.count { |r| r[:success] == true }
|
|
1281
|
+
mean = known.empty? ? 0.0 : (known.sum { |r| r[:training_score] } / known.length)
|
|
1282
|
+
{
|
|
1283
|
+
resolved: resolved, mean_score: mean.round(3),
|
|
1284
|
+
scores: outcomes.map { |r| r[:score] }, outcomes: outcomes,
|
|
1285
|
+
unknown: outcomes.length - known.length
|
|
1286
|
+
}
|
|
1285
1287
|
rescue StandardError
|
|
1286
|
-
{ resolved: 0, mean_score: 0.0, scores: [] }
|
|
1288
|
+
{ resolved: 0, mean_score: 0.0, scores: [], outcomes: outcomes || [], unknown: Array(opts[:evalset]).length }
|
|
1287
1289
|
end
|
|
1288
1290
|
|
|
1289
1291
|
private_class_method def self.replay_on(opts = {})
|
|
1290
|
-
tag
|
|
1291
|
-
return 0 if tag.empty?
|
|
1292
|
-
|
|
1293
|
-
with_ollama_model(tag: tag) do
|
|
1294
|
-
opts[:evalset].count do |e|
|
|
1295
|
-
r = self_play(prompt: e[:prompt], tag: "gate:#{tag}")
|
|
1296
|
-
r[:score].to_f >= 0.7
|
|
1297
|
-
end
|
|
1298
|
-
end
|
|
1299
|
-
rescue StandardError
|
|
1300
|
-
0
|
|
1292
|
+
replay_on_detailed(tag: opts[:tag], evalset: opts[:evalset])[:resolved]
|
|
1301
1293
|
end
|
|
1302
1294
|
|
|
1303
1295
|
private_class_method def self.with_ollama_model(opts = {})
|
|
@@ -72,6 +72,15 @@ module PWN
|
|
|
72
72
|
error: 'refused: memory_remember/skills_update text copied from last tool output'
|
|
73
73
|
)
|
|
74
74
|
end
|
|
75
|
+
if blob.match?(/open_sockraw|sockraw/) && defined?(PWN::Plugins::PreflightChecker) &&
|
|
76
|
+
PWN::Plugins::PreflightChecker.respond_to?(:cap_net_raw?) && !PWN::Plugins::PreflightChecker.cap_net_raw?
|
|
77
|
+
return JSON.generate(
|
|
78
|
+
success: false,
|
|
79
|
+
error: 'capability missing CAP_NET_RAW',
|
|
80
|
+
substitute: 'PWN::Plugins::Packet.tcp_connect_scan',
|
|
81
|
+
code: 'CAP_DENY'
|
|
82
|
+
)
|
|
83
|
+
end
|
|
75
84
|
result = entry.handler.call(args)
|
|
76
85
|
result = ToolGuard.quarantine_output(text: result) if defined?(ToolGuard) && result.is_a?(String) && ToolGuard.respond_to?(:quarantine_output)
|
|
77
86
|
note_taint(text: result)
|
|
@@ -58,6 +58,32 @@ module PWN
|
|
|
58
58
|
token = (opts[:host] || opts[:ip] || opts[:target] || opts[:url]).to_s
|
|
59
59
|
return true if token.empty?
|
|
60
60
|
|
|
61
|
+
roe = load_roe
|
|
62
|
+
host = token
|
|
63
|
+
begin
|
|
64
|
+
host = URI.parse(token).host || token if token.include?('://')
|
|
65
|
+
rescue StandardError
|
|
66
|
+
host = token
|
|
67
|
+
end
|
|
68
|
+
host = host.sub(%r{\Ahttps?://}i, '').split('/').first.to_s.split(':').first
|
|
69
|
+
if roe[:present]
|
|
70
|
+
deny = Array(roe[:targets_deny])
|
|
71
|
+
return false if deny.any? { |ex| host == ex.to_s || host.end_with?(".#{ex}") || host.include?(ex.to_s) }
|
|
72
|
+
|
|
73
|
+
allow = Array(roe[:targets_allow])
|
|
74
|
+
unless allow.empty?
|
|
75
|
+
ok = allow.any? do |a|
|
|
76
|
+
a = a.to_s
|
|
77
|
+
host == a || host.end_with?(".#{a}") || begin
|
|
78
|
+
IPAddr.new(a).include?(IPAddr.new(host))
|
|
79
|
+
rescue StandardError
|
|
80
|
+
false
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
return false unless ok
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
61
87
|
row = status
|
|
62
88
|
return true if row[:active].nil? || row[:missing]
|
|
63
89
|
|
|
@@ -90,6 +116,16 @@ module PWN
|
|
|
90
116
|
public_class_method def self.deny_if_out_of_scope(opts = {})
|
|
91
117
|
args = opts[:args] || opts[:command] || opts[:text]
|
|
92
118
|
blob = args.is_a?(Hash) ? args.inspect : args.to_s
|
|
119
|
+
roe = load_roe
|
|
120
|
+
tech_hit = Array(roe[:techniques_deny]).find { |t| blob.downcase.include?(t.to_s.downcase) }
|
|
121
|
+
if tech_hit
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: "roe_deny: technique #{tech_hit}",
|
|
125
|
+
code: 'ROE_DENY',
|
|
126
|
+
violating: [tech_hit]
|
|
127
|
+
}
|
|
128
|
+
end
|
|
93
129
|
tokens = blob.scan(/(?:\d{1,3}\.){3}\d{1,3}|[A-Za-z0-9.-]+\.[A-Za-z]{2,}/).uniq
|
|
94
130
|
bad = tokens.reject { |tok| in_scope?(host: tok) }
|
|
95
131
|
return nil if bad.empty?
|
|
@@ -102,6 +138,24 @@ module PWN
|
|
|
102
138
|
}
|
|
103
139
|
end
|
|
104
140
|
|
|
141
|
+
public_class_method def self.load_roe(opts = {})
|
|
142
|
+
path = (opts[:path] || File.join(Dir.home, '.pwn', 'roe.yaml')).to_s
|
|
143
|
+
return { present: false, targets_allow: [], targets_deny: [], techniques_deny: [] } unless File.file?(path)
|
|
144
|
+
|
|
145
|
+
require 'yaml'
|
|
146
|
+
raw = YAML.safe_load_file(path, permitted_classes: [], symbolize_names: true) || {}
|
|
147
|
+
raw = {} unless raw.is_a?(Hash)
|
|
148
|
+
{
|
|
149
|
+
present: true,
|
|
150
|
+
targets_allow: Array(raw[:targets_allow] || raw['targets_allow']),
|
|
151
|
+
targets_deny: Array(raw[:targets_deny] || raw['targets_deny']),
|
|
152
|
+
techniques_deny: Array(raw[:techniques_deny] || raw['techniques_deny']),
|
|
153
|
+
time_windows: raw[:time_windows] || raw['time_windows']
|
|
154
|
+
}
|
|
155
|
+
rescue StandardError
|
|
156
|
+
{ present: false, targets_allow: [], targets_deny: [], techniques_deny: [] }
|
|
157
|
+
end
|
|
158
|
+
|
|
105
159
|
public_class_method def self.authors
|
|
106
160
|
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
107
161
|
end
|
|
@@ -149,6 +203,11 @@ module PWN
|
|
|
149
203
|
text: 'optional - free-form blob to scan'
|
|
150
204
|
)
|
|
151
205
|
|
|
206
|
+
# Load the RoE policy file when present for allow and deny lists.
|
|
207
|
+
#{self}.load_roe(
|
|
208
|
+
path: 'optional - filesystem path of roe.yaml (defaults to ~/.pwn/roe.yaml)'
|
|
209
|
+
)
|
|
210
|
+
|
|
152
211
|
# Print the AUTHOR(S) string for this module.
|
|
153
212
|
#{self}.authors
|
|
154
213
|
"
|