pwn 0.5.783 → 0.5.785
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/.ruby-version +1 -1
- data/Gemfile +8 -8
- data/README.md +37 -0
- data/Rakefile +19 -1
- data/bin/pwn +2 -1
- data/bin/pwn_setup +1 -1
- data/bin/pwn_web_cache_deception +35 -7
- data/build_gem.sh +1 -1
- data/documentation/AI-Integration.md +7 -5
- data/documentation/Agent-Tool-Registry.md +2 -2
- data/documentation/Backup-Retention.md +82 -0
- data/documentation/Configuration.md +10 -6
- data/documentation/Contributing.md +1 -1
- data/documentation/Cron.md +64 -35
- data/documentation/Diagrams.md +2 -2
- data/documentation/General-PWN-Usage.md +4 -3
- data/documentation/Home.md +6 -5
- data/documentation/How-PWN-Works.md +7 -6
- data/documentation/Installation.md +254 -10
- data/documentation/Mission-Cancellation.md +134 -0
- data/documentation/Operability.md +17 -0
- data/documentation/Persistence.md +8 -6
- data/documentation/Plugin-Degradation.md +12 -0
- data/documentation/Plugins.md +3 -2
- data/documentation/Reinforcement-Learning.md +5 -6
- data/documentation/Runtime-Source-Protection.md +162 -0
- data/documentation/Transparent-Browser.md +11 -0
- data/documentation/Troubleshooting.md +3 -2
- data/documentation/What-is-PWN.md +2 -2
- data/documentation/YubiKey-Vaults.md +183 -0
- data/documentation/YubiKey.md +339 -0
- data/documentation/diagrams/cron-scheduling.svg +130 -128
- data/documentation/diagrams/dot/cron-scheduling.dot +5 -5
- data/documentation/diagrams/dot/persistence-filesystem.dot +5 -3
- data/documentation/diagrams/dot/plugin-ecosystem.dot +2 -2
- data/documentation/diagrams/persistence-filesystem.svg +172 -142
- data/documentation/diagrams/plugin-ecosystem.svg +64 -64
- data/documentation/pwn-ai-Agent.md +10 -4
- data/etc/default_cron/session_cleanup.rb +1245 -22
- data/etc/default_skills/pwn/ai/agent/mission_namespace/SKILL.md +21 -0
- data/etc/default_skills/pwn/ai/agent/tools/browser_act/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/loot_query/SKILL.md +27 -26
- data/etc/default_skills/pwn/ai/agent/tools/recon_run/SKILL.md +25 -15
- data/etc/default_skills/pwn/ai/agent/tools/workspace_tool/SKILL.md +24 -0
- data/etc/default_skills/pwn/ai/server_tools/SKILL.md +25 -0
- data/etc/default_skills/pwn/backups/SKILL.md +54 -0
- data/etc/default_skills/pwn/plugins/recon/SKILL.md +12 -1
- data/etc/default_skills/pwn/plugins/yubikey/SKILL.md +157 -0
- data/etc/default_skills/pwn/plugins/yubikey/fido/SKILL.md +189 -0
- data/etc/default_skills/pwn/plugins/yubikey/hsmauth/SKILL.md +83 -0
- data/etc/default_skills/pwn/plugins/yubikey/management/SKILL.md +80 -0
- data/etc/default_skills/pwn/plugins/yubikey/oath/SKILL.md +115 -0
- data/etc/default_skills/pwn/plugins/yubikey/openpgp/SKILL.md +144 -0
- data/etc/default_skills/pwn/plugins/yubikey/otp/SKILL.md +97 -0
- data/etc/default_skills/pwn/plugins/yubikey/piv/SKILL.md +110 -0
- data/etc/default_skills/pwn/plugins/yubikey/protocol/SKILL.md +21 -0
- data/etc/default_skills/pwn/plugins/yubikey/security_domain/SKILL.md +75 -0
- data/etc/default_skills/pwn/plugins/yubikey/security_domain/scp/SKILL.md +46 -0
- data/etc/default_skills/pwn/plugins/yubikey/transport/SKILL.md +98 -0
- data/etc/default_skills/pwn/redaction/SKILL.md +13 -1
- data/lib/pwn/ai/agent/dispatch.rb +26 -15
- data/lib/pwn/ai/agent/extrospection.rb +6 -7
- data/lib/pwn/ai/agent/learning.rb +62 -25
- data/lib/pwn/ai/agent/loop.rb +17 -1
- data/lib/pwn/ai/agent/mission_namespace.rb +331 -0
- data/lib/pwn/ai/agent/policy.rb +5 -0
- data/lib/pwn/ai/agent/policy_evaluation.rb +8 -8
- data/lib/pwn/ai/agent/prompt_builder.rb +13 -2
- data/lib/pwn/ai/agent/request_runtime.rb +122 -18
- data/lib/pwn/ai/agent/reward.rb +5 -3
- data/lib/pwn/ai/agent/skill_consolidation.rb +3 -2
- data/lib/pwn/ai/agent/skill_review.rb +9 -5
- data/lib/pwn/ai/agent/swarm.rb +35 -16
- data/lib/pwn/ai/agent/tools/browser_act.rb +32 -0
- data/lib/pwn/ai/agent/tools/loot_query.rb +11 -1
- data/lib/pwn/ai/agent/tools/recon_run.rb +11 -1
- data/lib/pwn/ai/agent/tools/workspace_tool.rb +29 -0
- data/lib/pwn/ai/agent/turn_finalizer.rb +6 -2
- data/lib/pwn/ai/agent.rb +1 -0
- data/lib/pwn/ai/anthropic.rb +8 -33
- data/lib/pwn/ai/cli.rb +34 -5
- data/lib/pwn/ai/gemini.rb +1 -0
- data/lib/pwn/ai/grok.rb +39 -28
- data/lib/pwn/ai/ollama.rb +4 -2
- data/lib/pwn/ai/open_ai.rb +11 -32
- data/lib/pwn/ai/open_web_ui.rb +62 -4
- data/lib/pwn/ai/router.rb +53 -3
- data/lib/pwn/ai/server_tools.rb +231 -0
- data/lib/pwn/ai.rb +1 -0
- data/lib/pwn/backups.rb +347 -0
- data/lib/pwn/config.rb +70 -39
- data/lib/pwn/cron.rb +120 -49
- data/lib/pwn/migrate.rb +150 -68
- data/lib/pwn/module_skills.rb +7 -0
- data/lib/pwn/plugins/ai_sandbox.rb +538 -1
- data/lib/pwn/plugins/authentication_helper.rb +74 -4
- data/lib/pwn/plugins/basic_auth.rb +2 -1
- data/lib/pwn/plugins/binary_parser.rb +5 -4
- data/lib/pwn/plugins/ghidra.rb +4 -3
- data/lib/pwn/plugins/google_workspace.rb +4 -21
- data/lib/pwn/plugins/jobs.rb +1 -1
- data/lib/pwn/plugins/log.rb +21 -3
- data/lib/pwn/plugins/preflight_checker.rb +34 -9
- data/lib/pwn/plugins/recon.rb +4 -1
- data/lib/pwn/plugins/repl/ai.rb +305 -27
- data/lib/pwn/plugins/repl/ai_console.rb +273 -89
- data/lib/pwn/plugins/repl/ai_swarm.rb +181 -62
- data/lib/pwn/plugins/repl/mesh.rb +3 -16
- data/lib/pwn/plugins/repl/vault.rb +4 -15
- data/lib/pwn/plugins/repl.rb +1 -216
- data/lib/pwn/plugins/sandbox/driver.rb +4 -0
- data/lib/pwn/plugins/transparent_browser.rb +215 -0
- data/lib/pwn/plugins/vault.rb +412 -135
- data/lib/pwn/plugins/yubikey/fido.rb +1288 -0
- data/lib/pwn/plugins/yubikey/hsmauth.rb +439 -0
- data/lib/pwn/plugins/yubikey/management.rb +686 -0
- data/lib/pwn/plugins/yubikey/oath.rb +766 -0
- data/lib/pwn/plugins/yubikey/openpgp.rb +1063 -0
- data/lib/pwn/plugins/yubikey/otp.rb +897 -0
- data/lib/pwn/plugins/yubikey/piv.rb +1315 -0
- data/lib/pwn/plugins/yubikey/protocol.rb +341 -0
- data/lib/pwn/plugins/yubikey/security_domain/scp.rb +482 -0
- data/lib/pwn/plugins/yubikey/security_domain.rb +464 -0
- data/lib/pwn/plugins/yubikey/transport.rb +1017 -0
- data/lib/pwn/plugins/yubikey.rb +844 -0
- data/lib/pwn/plugins.rb +1 -0
- data/lib/pwn/redaction.rb +69 -12
- data/lib/pwn/session_trace.rb +4 -0
- data/lib/pwn/sessions.rb +58 -16
- data/lib/pwn/setup.rb +123 -41
- data/lib/pwn/version.rb +1 -1
- data/lib/pwn/www/app_cobalt_io.rb +16 -5
- data/lib/pwn/www/bug_crowd.rb +16 -5
- data/lib/pwn/www/coinbase_pro.rb +16 -5
- data/lib/pwn/www/github.rb +30 -14
- data/lib/pwn/www/paypal.rb +15 -4
- data/lib/pwn/www/synack.rb +16 -5
- data/lib/pwn/www/twitter.rb +17 -6
- data/lib/pwn.rb +1 -0
- data/scripts/build_gate.rb +58 -0
- data/spec/documentation/installation_md_spec.rb +1 -1
- data/spec/fixtures/yubikey/apdu_frames.json +162 -0
- data/spec/integration/authentication_helper_oath_spec.rb +117 -0
- data/spec/integration/build_gate_spec.rb +73 -0
- data/spec/integration/controller_spill_spec.rb +48 -0
- data/spec/integration/harness_process_spec.rb +97 -0
- data/spec/integration/mission_cancel_spec.rb +79 -0
- data/spec/integration/mission_curses_pty_spec.rb +44 -0
- data/spec/integration/persistence_roundtrip_spec.rb +1 -7
- data/spec/integration/readme_security_spec.rb +47 -0
- data/spec/integration/web_cache_deception_yubikey_spec.rb +159 -0
- data/spec/lib/pwn/ai/agent/dispatch_spec.rb +6 -3
- data/spec/lib/pwn/ai/agent/dispatch_vault_safety_spec.rb +86 -0
- data/spec/lib/pwn/ai/agent/injection_guard_spec.rb +2 -0
- data/spec/lib/pwn/ai/agent/learning_spec.rb +4 -4
- data/spec/lib/pwn/ai/agent/loop_artifacts_spec.rb +5 -0
- data/spec/lib/pwn/ai/agent/loop_spec.rb +104 -29
- data/spec/lib/pwn/ai/agent/mission_namespace_spec.rb +138 -0
- data/spec/lib/pwn/ai/agent/policy_spec.rb +27 -0
- data/spec/lib/pwn/ai/agent/request_runtime_spec.rb +48 -0
- data/spec/lib/pwn/ai/agent/shell_search_effect_spec.rb +44 -0
- data/spec/lib/pwn/ai/agent/solve_spec.rb +6 -4
- data/spec/lib/pwn/ai/agent/tools/browser_act_spec.rb +44 -0
- data/spec/lib/pwn/ai/agent/tools/loot_query_yubikey_spec.rb +55 -0
- data/spec/lib/pwn/ai/agent/tools/recon_run_yubikey_spec.rb +43 -0
- data/spec/lib/pwn/ai/agent/tools/workspace_tool_spec.rb +20 -0
- data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +7 -0
- data/spec/lib/pwn/ai/anthropic_vault_enrollment_spec.rb +37 -0
- data/spec/lib/pwn/ai/cli_spec.rb +6 -9
- data/spec/lib/pwn/ai/oauth_vault_persistence_spec.rb +195 -0
- data/spec/lib/pwn/ai/open_ai_spec.rb +2 -2
- data/spec/lib/pwn/ai/open_web_ui_workspace_spec.rb +36 -0
- data/spec/lib/pwn/ai/router_spec.rb +31 -3
- data/spec/lib/pwn/ai/server_tools_spec.rb +39 -0
- data/spec/lib/pwn/backups_spec.rb +114 -0
- data/spec/lib/pwn/config_redaction_yubikey_spec.rb +28 -0
- data/spec/lib/pwn/config_yubikey_spec.rb +92 -0
- data/spec/lib/pwn/cron_spec.rb +113 -6
- data/spec/lib/pwn/migrate_spec.rb +108 -5
- data/spec/lib/pwn/migrate_yubikey_spec.rb +128 -0
- data/spec/lib/pwn/module_skills_spec.rb +8 -0
- data/spec/lib/pwn/plugins/ai_sandbox_spec.rb +230 -0
- data/spec/lib/pwn/plugins/authentication_helper_native_yubikey_spec.rb +40 -0
- data/spec/lib/pwn/plugins/authentication_helper_yubikey_spec.rb +90 -0
- data/spec/lib/pwn/plugins/capability_broker/daemon_spec.rb +1 -1
- data/spec/lib/pwn/plugins/google_workspace_spec.rb +17 -35
- data/spec/lib/pwn/plugins/log_spec.rb +7 -0
- data/spec/lib/pwn/plugins/log_yubikey_spec.rb +47 -0
- data/spec/lib/pwn/plugins/preflight_checker_native_yubikey_spec.rb +19 -0
- data/spec/lib/pwn/plugins/preflight_checker_yubikey_spec.rb +55 -0
- data/spec/lib/pwn/plugins/recon_yubikey_spec.rb +43 -0
- data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +125 -78
- data/spec/lib/pwn/plugins/repl/ai_legacy_containment_spec.rb +200 -0
- data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +30 -16
- data/spec/lib/pwn/plugins/repl/ai_vault_edit_yubikey_spec.rb +103 -0
- data/spec/lib/pwn/plugins/repl/ai_yubikey_spec.rb +48 -0
- data/spec/lib/pwn/plugins/repl/mesh_vault_persistence_spec.rb +149 -0
- data/spec/lib/pwn/plugins/repl/vault_yaml_serialization_spec.rb +11 -0
- data/spec/lib/pwn/plugins/repl/vault_yubikey_spec.rb +39 -0
- data/spec/lib/pwn/plugins/repl_spec.rb +56 -5
- data/spec/lib/pwn/plugins/transparent_browser_jev_spec.rb +46 -0
- data/spec/lib/pwn/plugins/vault_native_yubikey_spec.rb +29 -0
- data/spec/lib/pwn/plugins/vault_publication_safety_spec.rb +258 -0
- data/spec/lib/pwn/plugins/vault_store_safety_spec.rb +241 -0
- data/spec/lib/pwn/plugins/vault_yubikey_safety_spec.rb +168 -0
- data/spec/lib/pwn/plugins/vault_yubikey_spec.rb +128 -0
- data/spec/lib/pwn/plugins/vault_yubikey_vector_spec.rb +45 -0
- data/spec/lib/pwn/plugins/yubikey/fido_spec.rb +854 -0
- data/spec/lib/pwn/plugins/yubikey/hsmauth_spec.rb +235 -0
- data/spec/lib/pwn/plugins/yubikey/management_spec.rb +277 -0
- data/spec/lib/pwn/plugins/yubikey/oath_spec.rb +349 -0
- data/spec/lib/pwn/plugins/yubikey/openpgp_spec.rb +810 -0
- data/spec/lib/pwn/plugins/yubikey/otp_spec.rb +337 -0
- data/spec/lib/pwn/plugins/yubikey/piv_spec.rb +646 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_secure_spec.rb +28 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_spec.rb +149 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_vectors_spec.rb +18 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain/scp_spec.rb +256 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain/support.rb +153 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain_spec.rb +296 -0
- data/spec/lib/pwn/plugins/yubikey/transport_spec.rb +760 -0
- data/spec/lib/pwn/plugins/yubikey_security_spec.rb +260 -0
- data/spec/lib/pwn/plugins/yubikey_spec.rb +209 -0
- data/spec/lib/pwn/plugins/yubikey_terminal_spec.rb +147 -0
- data/spec/lib/pwn/redaction_spec.rb +13 -10
- data/spec/lib/pwn/redaction_yubikey_spec.rb +61 -0
- data/spec/lib/pwn/session_cleanup_concurrency_spec.rb +201 -0
- data/spec/lib/pwn/session_cleanup_default_spec.rb +167 -2
- data/spec/lib/pwn/session_cleanup_legacy_spec.rb +101 -0
- data/spec/lib/pwn/session_cleanup_payload_spec.rb +335 -2
- data/spec/lib/pwn/session_cleanup_recovery_spec.rb +403 -0
- data/spec/lib/pwn/sessions_spec.rb +112 -0
- data/spec/lib/pwn/setup_native_yubikey_spec.rb +66 -0
- data/spec/lib/pwn/setup_spec.rb +3 -1
- data/spec/lib/pwn/setup_vault_yubikey_spec.rb +61 -0
- data/spec/lib/pwn/setup_yubikey_spec.rb +40 -0
- data/spec/lib/pwn/www/login_yubikey_spec.rb +273 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/gate_failure_formatter.rb +14 -0
- data/spec/support/harness/cli_faults.rb +13 -9
- data/spec/support/harness/controller_spill.rb +26 -0
- data/spec/support/harness/controller_state.rb +54 -0
- data/spec/support/harness/evaluate.rb +12 -1
- data/spec/support/harness/experiments.rb +1 -1
- data/spec/support/harness/fake_provider.rb +22 -6
- data/spec/support/harness/fault_preload.rb +3 -1
- data/spec/support/harness/mission_console.rb +54 -0
- data/spec/support/harness/mission_curses_pty.rb +41 -0
- data/spec/support/harness/mission_dispatch.rb +30 -0
- data/spec/support/harness/mission_fixture_preload.rb +24 -0
- data/spec/support/harness/mission_job_lifetime.rb +25 -0
- data/spec/support/harness/mission_legacy.rb +25 -0
- data/spec/support/harness/mission_normal_exit.rb +34 -0
- data/spec/support/harness/mission_swarm.rb +23 -0
- data/spec/support/harness/mission_unsupported.rb +29 -0
- data/spec/support/harness/package_acceptance.rb +33 -5
- data/spec/support/harness/session_acceptance.rb +4 -4
- data/spec/support/harness/swarm_acceptance.rb +1 -1
- data/spec/support/harness/swarm_control.rb +44 -0
- data/spec/support/mission_namespace.rb +14 -0
- data/spec/support/mission_pipe_queue.rb +33 -0
- data/third_party/pwn_rdoc.jsonl +599 -15
- metadata +140 -17
|
@@ -17,7 +17,7 @@ module PWN
|
|
|
17
17
|
basic_user = opts[:username]
|
|
18
18
|
basic_pass = opts[:password]
|
|
19
19
|
if (basic_user.nil? || basic_pass.nil?) && !opts[:host].to_s.empty?
|
|
20
|
-
hit = Array(PWN::Plugins::Vault.offer(host: opts[:host], service: opts[:service], engagement: opts[:engagement] || opts[:engagement_id])).first
|
|
20
|
+
hit = Array(PWN::Plugins::Vault.offer(host: opts[:host], service: opts[:service], engagement: opts[:engagement] || opts[:engagement_id], yubikey: opts[:yubikey])).first
|
|
21
21
|
if hit
|
|
22
22
|
basic_user = hit[:username] if basic_user.nil?
|
|
23
23
|
basic_pass = hit[:secret] if basic_pass.nil?
|
|
@@ -63,6 +63,7 @@ module PWN
|
|
|
63
63
|
password: 'optional - password; omitted values are filled from Vault.offer',
|
|
64
64
|
host: 'optional - hostname used to offer recon loot when username or password is omitted',
|
|
65
65
|
service: 'optional - service name such as http or ssh used to match loot',
|
|
66
|
+
yubikey: 'optional - YubiKey selector Hash forwarded to hardware-protected Vault loot',
|
|
66
67
|
engagement: 'optional - engagement id scoping the loot file (defaults to default)',
|
|
67
68
|
engagement_id: 'optional - alias for engagement'
|
|
68
69
|
)
|
|
@@ -101,16 +101,17 @@ module PWN
|
|
|
101
101
|
sha = Digest::SHA256.file(path).hexdigest
|
|
102
102
|
sid = (opts[:session_id] || 'triage').to_s
|
|
103
103
|
cache_dir = File.join(Dir.home, '.pwn', 'cache', 'triage')
|
|
104
|
-
FileUtils.mkdir_p(cache_dir)
|
|
105
104
|
cache = File.join(cache_dir, "#{sha}.json")
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
require File.expand_path('../../../etc/default_cron/session_cleanup', __dir__)
|
|
106
|
+
cached_bytes = PWN::SessionCleanup.cache(path: cache)
|
|
107
|
+
if cached_bytes
|
|
108
|
+
cached = JSON.parse(cached_bytes, symbolize_names: true)
|
|
108
109
|
spilled = persist_triage(body: cached, session_id: sid)
|
|
109
110
|
return cached.merge(cached: true, handle: spilled[:handle], artifact: spilled)
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
body = inspect_binary(opts.merge(path: path, sha256: sha))
|
|
113
|
-
|
|
114
|
+
PWN::SessionCleanup.cache(path: cache, bytes: JSON.pretty_generate(body))
|
|
114
115
|
facts_dir = File.join(Dir.home, '.pwn', 'engagements', 'default', 'binaries', sha)
|
|
115
116
|
FileUtils.mkdir_p(facts_dir)
|
|
116
117
|
File.write(File.join(facts_dir, 'facts.json'), JSON.pretty_generate(body))
|
data/lib/pwn/plugins/ghidra.rb
CHANGED
|
@@ -37,13 +37,14 @@ module PWN
|
|
|
37
37
|
|
|
38
38
|
sha = Digest::SHA256.file(path).hexdigest if File.file?(path)
|
|
39
39
|
cache = File.join(Dir.home, '.pwn', 'cache', 'decompile', "#{sha}.json") if sha
|
|
40
|
-
|
|
40
|
+
require File.expand_path('../../../etc/default_cron/session_cleanup', __dir__)
|
|
41
|
+
cached = PWN::SessionCleanup.cache(path: cache) if cache
|
|
42
|
+
return JSON.parse(cached, symbolize_names: true).merge(cached: true) if cached
|
|
41
43
|
|
|
42
44
|
row = analyze(opts)
|
|
43
45
|
fn = opts[:function].to_s
|
|
44
46
|
row[:function] = fn unless fn.empty?
|
|
45
|
-
|
|
46
|
-
File.write(cache, JSON.generate(row)) if cache
|
|
47
|
+
PWN::SessionCleanup.cache(path: cache, bytes: JSON.generate(row)) if cache
|
|
47
48
|
row
|
|
48
49
|
end
|
|
49
50
|
|
|
@@ -397,25 +397,13 @@ module PWN
|
|
|
397
397
|
dec_path = dec_path.to_s.strip
|
|
398
398
|
dec_path = "#{env_path}.decryptor" if dec_path.empty?
|
|
399
399
|
|
|
400
|
-
unless File.exist?(env_path)
|
|
400
|
+
unless File.exist?(env_path)
|
|
401
401
|
puts '[*] INFO: Google Workspace OAuth tokens updated in this session only; ' \
|
|
402
|
-
"persistence to #{env_path} skipped (missing
|
|
402
|
+
"persistence to #{env_path} skipped (missing vault)."
|
|
403
403
|
return false
|
|
404
404
|
end
|
|
405
405
|
|
|
406
|
-
|
|
407
|
-
key = decryptor.is_a?(Hash) ? decryptor[:key] : nil
|
|
408
|
-
iv = decryptor.is_a?(Hash) ? decryptor[:iv] : nil
|
|
409
|
-
unless real_config_value?(value: key) && real_config_value?(value: iv)
|
|
410
|
-
puts '[*] INFO: Google Workspace OAuth tokens updated in this session only; ' \
|
|
411
|
-
'decryptor has no usable key/iv.'
|
|
412
|
-
return false
|
|
413
|
-
end
|
|
414
|
-
|
|
415
|
-
PWN::Plugins::Vault.decrypt(file: env_path, key: key, iv: iv)
|
|
416
|
-
begin
|
|
417
|
-
vault = YAML.load_file(env_path, symbolize_names: true)
|
|
418
|
-
vault = {} unless vault.is_a?(Hash)
|
|
406
|
+
PWN::Plugins::Vault.update(file: env_path, decryptor_file: dec_path) do |vault|
|
|
419
407
|
vault[:plugins] = {} unless vault[:plugins].is_a?(Hash)
|
|
420
408
|
vault[:plugins][:google_workspace] = {} unless vault[:plugins][:google_workspace].is_a?(Hash)
|
|
421
409
|
slot = vault[:plugins][:google_workspace][:oauth]
|
|
@@ -423,15 +411,10 @@ module PWN
|
|
|
423
411
|
%i[bearer_token refresh_token expires_at client_id client_secret redirect_uri scope].each do |k|
|
|
424
412
|
slot[k] = oauth[k] if oauth.key?(k) && !oauth[k].nil?
|
|
425
413
|
end
|
|
426
|
-
yaml_env = YAML.dump(vault).gsub(/^(\s*):/, '\1')
|
|
427
|
-
File.write(env_path, yaml_env)
|
|
428
|
-
File.chmod(0o600, env_path)
|
|
429
|
-
ensure
|
|
430
|
-
PWN::Plugins::Vault.encrypt(file: env_path, key: key, iv: iv)
|
|
431
414
|
end
|
|
432
415
|
true
|
|
433
416
|
rescue StandardError => e
|
|
434
|
-
warn "[!] Google Workspace OAuth vault persistence failed (session tokens still updated): #{e.class}
|
|
417
|
+
warn "[!] Google Workspace OAuth vault persistence failed (session tokens still updated): #{e.class}"
|
|
435
418
|
false
|
|
436
419
|
end
|
|
437
420
|
|
data/lib/pwn/plugins/jobs.rb
CHANGED
|
@@ -26,7 +26,7 @@ module PWN
|
|
|
26
26
|
|
|
27
27
|
root = File.expand_path(JOBS_DIR)
|
|
28
28
|
FileUtils.mkdir_p(root, mode: 0o700)
|
|
29
|
-
File.chmod(0o700, root)
|
|
29
|
+
File.chmod(0o700, root) unless (File.stat(root).mode & 0o777) == 0o700
|
|
30
30
|
runtime = Float(opts[:max_runtime] || 0)
|
|
31
31
|
raise ArgumentError, 'max_runtime must be finite and nonnegative' unless runtime.finite? && runtime >= 0
|
|
32
32
|
|
data/lib/pwn/plugins/log.rb
CHANGED
|
@@ -27,7 +27,7 @@ module PWN
|
|
|
27
27
|
PWN::AI::Agent::Loop
|
|
28
28
|
PWN::AI::Agent::Dispatch
|
|
29
29
|
].freeze
|
|
30
|
-
SECRET_KEY_RX = /password|passwd|secret|token|api[_-]?key|authorization|bearer|cookie|session[_-]?id|private[_-]?key|decryptor|credential|ssh[_-]?key|client[_-]?secret|refresh[_-]?token|access[_-]?token|id[_-]?token|vault|csrf/i
|
|
30
|
+
SECRET_KEY_RX = Regexp.union(PWN::Redaction::MASKED_FIELD, /password|passwd|secret|token|api[_-]?key|authorization|bearer|cookie|session[_-]?id|private[_-]?key|decryptor|credential|ssh[_-]?key|client[_-]?secret|refresh[_-]?token|access[_-]?token|id[_-]?token|vault|csrf/i)
|
|
31
31
|
SECRET_VALUE_RX = %r{
|
|
32
32
|
-----BEGIN\ [A-Z ]*PRIVATE\ KEY----- |
|
|
33
33
|
Bearer\s+[A-Za-z0-9\-._~+/]+=* |
|
|
@@ -60,8 +60,9 @@ module PWN
|
|
|
60
60
|
log_file_path = "/tmp/pwn-ai-#{session}.json" if level == :learning
|
|
61
61
|
log_file = File.open(log_file_path, 'w')
|
|
62
62
|
else
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
require 'stringio'
|
|
64
|
+
log_file_path = File.join(Dir.home, '.pwn', 'logs', 'pwn.log')
|
|
65
|
+
log_file = StringIO.new
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
# Leave 10 "old" log files where
|
|
@@ -124,6 +125,10 @@ module PWN
|
|
|
124
125
|
end
|
|
125
126
|
|
|
126
127
|
logger.add(logger.level, PWN::Redaction.redact(value: log_event), PWN::Redaction.redact(value: which_self))
|
|
128
|
+
unless level == :learning
|
|
129
|
+
require File.expand_path('../../../etc/default_cron/session_cleanup', __dir__)
|
|
130
|
+
PWN::SessionCleanup.append_log(path: log_file_path, bytes: log_file.string)
|
|
131
|
+
end
|
|
127
132
|
logger.close
|
|
128
133
|
rescue Interrupt
|
|
129
134
|
note_interrupt!(where: 'CTRL+C', which_self: self) if debug_enabled?
|
|
@@ -424,9 +429,18 @@ module PWN
|
|
|
424
429
|
unless @debug_file.nil? || @debug_file.closed?
|
|
425
430
|
@debug_file.puts(PWN::Redaction.token(kind: 'stream', value: @stderr_redaction_buffer)) unless @stderr_redaction_buffer.to_s.empty?
|
|
426
431
|
@debug_file.close
|
|
432
|
+
@debug_lease&.close
|
|
433
|
+
@debug_lease = nil
|
|
434
|
+
if @debug_path.to_s.start_with?("#{File.join(Dir.home, '.pwn', 'logs')}/")
|
|
435
|
+
require File.expand_path('../../../etc/default_cron/session_cleanup', __dir__)
|
|
436
|
+
PWN::SessionCleanup.register_disposable(path: @debug_path, category: 'logs')
|
|
437
|
+
end
|
|
427
438
|
end
|
|
428
439
|
rescue StandardError
|
|
429
440
|
nil
|
|
441
|
+
ensure
|
|
442
|
+
@debug_lease&.close
|
|
443
|
+
@debug_lease = nil
|
|
430
444
|
end
|
|
431
445
|
@debug_file = nil
|
|
432
446
|
@stderr_redaction_buffer = ''
|
|
@@ -439,6 +453,8 @@ module PWN
|
|
|
439
453
|
|
|
440
454
|
close_debug_file!
|
|
441
455
|
FileUtils.mkdir_p(File.dirname(path))
|
|
456
|
+
@debug_lease = File.open("#{path}.lease", File::RDWR | File::CREAT | File::NOFOLLOW, 0o600)
|
|
457
|
+
@debug_lease.flock(File::LOCK_EX)
|
|
442
458
|
io = File.open(path, 'a', 0o600)
|
|
443
459
|
io.chmod(0o600)
|
|
444
460
|
io.sync = true
|
|
@@ -628,11 +644,13 @@ module PWN
|
|
|
628
644
|
private_class_method def self.inspect_debug(opts = {})
|
|
629
645
|
val = opts[:value]
|
|
630
646
|
key = opts[:key]
|
|
647
|
+
return PWN::Redaction::CONSTANT_MASK if key.to_s.match?(PWN::Redaction::MASKED_FIELD)
|
|
631
648
|
return PWN::Redaction.token(kind: 'credential', value: val.to_s) if secret_key?(key: key)
|
|
632
649
|
return PWN::Redaction.token(kind: 'opaque', value: val) if opaque_secret?(key: key, value: val)
|
|
633
650
|
return format_debug_hash(hash: val) if val.is_a?(Hash)
|
|
634
651
|
return sanitize_debug_text(text: val) if val.is_a?(String) && val.match?(SECRET_VALUE_RX)
|
|
635
652
|
|
|
653
|
+
val = PWN::Redaction.redact(value: val) if val.is_a?(Array)
|
|
636
654
|
raw = sanitize_debug_text(text: val.inspect)
|
|
637
655
|
raw = "#{raw[0, DEBUG_VALUE_MAX]}…" if raw.length > DEBUG_VALUE_MAX
|
|
638
656
|
raw
|
|
@@ -6,9 +6,9 @@ require 'time'
|
|
|
6
6
|
|
|
7
7
|
module PWN
|
|
8
8
|
module Plugins
|
|
9
|
-
# Plugin/binary/capability preflight.
|
|
10
|
-
#
|
|
11
|
-
# the whole plugin tree.
|
|
9
|
+
# Plugin/binary/capability preflight. Native probes load host libraries
|
|
10
|
+
# only: no device enumeration, authentication or I/O. HOST summaries list
|
|
11
|
+
# degraded modules without autoloading the whole plugin tree.
|
|
12
12
|
module PreflightChecker
|
|
13
13
|
class MissingBinary < StandardError; end
|
|
14
14
|
class MissingCapability < StandardError; end
|
|
@@ -32,6 +32,13 @@ module PWN
|
|
|
32
32
|
'PWN::Plugins::Semgrep' => { bins: %w[semgrep] },
|
|
33
33
|
'PWN::Plugins::ExploitDB' => { bins: %w[searchsploit] },
|
|
34
34
|
'PWN::Plugins::Recon' => { bins: %w[subfinder httpx] },
|
|
35
|
+
'PWN::Plugins::YubiKey' => {
|
|
36
|
+
bins: [],
|
|
37
|
+
native: [
|
|
38
|
+
{ name: 'FIDO native transport', probe: 'PWN::Plugins::YubiKey', options: { application: :fido } },
|
|
39
|
+
{ name: 'PC/SC native transport', probe: 'PWN::Plugins::YubiKey', options: { application: :oath } }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
35
42
|
'PWN::Plugins::CredentialAttack' => { bins: %w[hydra john hashcat] }
|
|
36
43
|
}.freeze
|
|
37
44
|
|
|
@@ -97,6 +104,11 @@ module PWN
|
|
|
97
104
|
PLUGIN_DEPS.each do |mod, dep|
|
|
98
105
|
missing = Array(dep[:bins]).reject { |b| bin?(name: b) }
|
|
99
106
|
caps[mod] = { bins: dep[:bins], missing: missing, ok: missing.empty?, caps: Array(dep[:caps]) }
|
|
107
|
+
next unless dep[:native]
|
|
108
|
+
|
|
109
|
+
missing_native = Array(dep[:native]).reject { |probe| native_available?(probe: probe) }.map { |probe| probe[:name] }
|
|
110
|
+
caps[mod][:missing_native] = missing_native
|
|
111
|
+
caps[mod][:ok] &&= missing_native.empty?
|
|
100
112
|
end
|
|
101
113
|
row = {
|
|
102
114
|
at: Time.now.utc.iso8601,
|
|
@@ -164,21 +176,25 @@ module PWN
|
|
|
164
176
|
bins = Array(dep[:bins])
|
|
165
177
|
caps = Array(dep[:caps])
|
|
166
178
|
services = Array(dep[:services])
|
|
179
|
+
native = Array(dep[:native])
|
|
167
180
|
missing_bins = bins.reject { |b| bin?(name: b) }
|
|
181
|
+
missing_native = native.reject { |probe| native_available?(probe: probe) }.map { |probe| probe[:name] }
|
|
168
182
|
missing_caps = []
|
|
169
183
|
if caps.include?('CAP_NET_RAW')
|
|
170
184
|
pkt_ok = defined?(PWN::Plugins::Packet) && PWN::Plugins::Packet.respond_to?(:health) && PWN::Plugins::Packet.health[:ok]
|
|
171
185
|
missing_caps << 'CAP_NET_RAW' unless pkt_ok || cap_net_raw?
|
|
172
186
|
end
|
|
173
187
|
missing_services = services.reject { |s| service?(name: s[:name] || s['name'], path: s[:path] || s['path']) }.map { |s| s[:name] || s['name'] }
|
|
174
|
-
status = if missing_bins.empty? && missing_caps.empty? && missing_services.empty?
|
|
188
|
+
status = if missing_bins.empty? && missing_caps.empty? && missing_services.empty? && missing_native.empty?
|
|
175
189
|
:ok
|
|
176
|
-
elsif bins.any? && missing_bins.length == bins.length
|
|
190
|
+
elsif (bins.any? && missing_bins.length == bins.length) || (native.any? && missing_native.length == native.length)
|
|
177
191
|
:dead
|
|
178
192
|
else
|
|
179
193
|
:degraded
|
|
180
194
|
end
|
|
181
|
-
{ plugin: plugin, status: status, missing_bins: missing_bins, missing_caps: missing_caps, missing_services: missing_services }
|
|
195
|
+
row = { plugin: plugin, status: status, missing_bins: missing_bins, missing_caps: missing_caps, missing_services: missing_services }
|
|
196
|
+
row[:missing_native] = missing_native if dep[:native]
|
|
197
|
+
row
|
|
182
198
|
end
|
|
183
199
|
end
|
|
184
200
|
|
|
@@ -189,7 +205,7 @@ module PWN
|
|
|
189
205
|
degraded = rows.select { |r| r[:status] == :degraded }
|
|
190
206
|
lines = ["HOST plugins ok=#{rows.count { |r| r[:status] == :ok }} degraded=#{degraded.length} dead=#{dead.length}"]
|
|
191
207
|
(degraded + dead).first(cap).each do |r|
|
|
192
|
-
miss = (Array(r[:missing_bins]) + Array(r[:missing_caps]) + Array(r[:missing_services])).join(',')
|
|
208
|
+
miss = (Array(r[:missing_bins]) + Array(r[:missing_caps]) + Array(r[:missing_services]) + Array(r[:missing_native])).join(',')
|
|
193
209
|
fb = Array(FALLBACKS[r[:plugin]]).join(',')
|
|
194
210
|
extra = fb.empty? ? '' : " fallback=#{fb}"
|
|
195
211
|
anchor = r[:plugin].to_s.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/\A-+|-+\z/, '')
|
|
@@ -198,6 +214,15 @@ module PWN
|
|
|
198
214
|
lines.join("\n")
|
|
199
215
|
end
|
|
200
216
|
|
|
217
|
+
# Only trusted declarations above may supply a host-only available? probe.
|
|
218
|
+
private_class_method def self.native_available?(opts = {})
|
|
219
|
+
probe = opts[:probe]
|
|
220
|
+
provider = probe.fetch(:probe).split('::').inject(Object) { |scope, name| scope.const_get(name, false) }
|
|
221
|
+
provider.available?(probe.fetch(:options, {})) == true
|
|
222
|
+
rescue LoadError, StandardError
|
|
223
|
+
false
|
|
224
|
+
end
|
|
225
|
+
|
|
201
226
|
public_class_method def self.authors
|
|
202
227
|
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
203
228
|
end
|
|
@@ -247,7 +272,7 @@ module PWN
|
|
|
247
272
|
path: 'required - unix socket path (e.g. /var/run/docker.sock)'
|
|
248
273
|
)
|
|
249
274
|
|
|
250
|
-
#
|
|
275
|
+
# Report dependencies; native rows add missing_native from host-only library probes, never device readiness.
|
|
251
276
|
#{self}.check(
|
|
252
277
|
names: 'optional - Array names value consumed by #check'
|
|
253
278
|
)
|
|
@@ -258,7 +283,7 @@ module PWN
|
|
|
258
283
|
limit: 'optional - limit value consumed by #host_summary'
|
|
259
284
|
)
|
|
260
285
|
|
|
261
|
-
# Refresh ~/.pwn/capabilities.json with bin/CAP_* presence.
|
|
286
|
+
# Refresh ~/.pwn/capabilities.json with bin/CAP_* and host-native-library presence; no device discovery.
|
|
262
287
|
#{self}.manifest(
|
|
263
288
|
refresh: 'optional - unused reserved refresh flag'
|
|
264
289
|
)
|
data/lib/pwn/plugins/recon.rb
CHANGED
|
@@ -138,7 +138,7 @@ module PWN
|
|
|
138
138
|
File.rename(temp, path)
|
|
139
139
|
end
|
|
140
140
|
result = JSON.parse(File.read(path), symbolize_names: true).merge(path: path)
|
|
141
|
-
result[:loot] = harvest_loot(assets: result[:assets], engagement_id: engagement)
|
|
141
|
+
result[:loot] = harvest_loot(assets: result[:assets], engagement_id: engagement, yubikey: opts[:yubikey])
|
|
142
142
|
if opts[:ingest]
|
|
143
143
|
begin
|
|
144
144
|
ingestor = opts[:ingestor] || PWN::AI::Context.method(:ingest)
|
|
@@ -285,6 +285,7 @@ module PWN
|
|
|
285
285
|
source: 'recon',
|
|
286
286
|
where: opts[:where] || "banner:#{obs[:port] || asset[:port]}",
|
|
287
287
|
engagement: eng,
|
|
288
|
+
yubikey: opts[:yubikey],
|
|
288
289
|
finding_id: opts[:finding_id]
|
|
289
290
|
)
|
|
290
291
|
end
|
|
@@ -354,6 +355,7 @@ module PWN
|
|
|
354
355
|
modules: 'optional - Array nmap/banner/tls/subfinder/nuclei; defaults nmap/banner',
|
|
355
356
|
ports: 'optional - Array of integer ports; defaults 80 and 443',
|
|
356
357
|
engagement_id: 'optional - simple identifier; defaults default',
|
|
358
|
+
yubikey: 'optional - hardware selector Hash for harvested loot; device failure raises without software fallback',
|
|
357
359
|
root: 'optional - engagements directory; defaults ~/.pwn/engagements',
|
|
358
360
|
timeout: 'optional - per-operation timeout seconds; defaults 15, maximum 300',
|
|
359
361
|
ingest: 'optional - invoke Context.ingest after persistence; defaults false',
|
|
@@ -366,6 +368,7 @@ module PWN
|
|
|
366
368
|
assets: 'required - Array of recon asset hashes with address and observations',
|
|
367
369
|
engagement_id: 'optional - engagement id scoping the loot file (defaults to default)',
|
|
368
370
|
engagement: 'optional - alias for engagement_id',
|
|
371
|
+
yubikey: 'optional - hardware selector Hash forwarded to Vault.ingest for captured secrets',
|
|
369
372
|
where: 'optional - provenance path overlay when observation port is missing',
|
|
370
373
|
finding_id: 'optional - finding id linked onto harvested secrets'
|
|
371
374
|
)
|