pwn 0.5.784 → 0.5.787
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 +6 -6
- data/README.md +37 -0
- data/bin/pwn +2 -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 +69 -15
- data/documentation/Configuration.md +10 -6
- data/documentation/Contributing.md +1 -1
- data/documentation/Cron.md +2 -2
- data/documentation/Diagrams.md +2 -2
- data/documentation/General-PWN-Usage.md +4 -3
- data/documentation/Harness-Evaluation.md +1 -1
- data/documentation/Home.md +6 -5
- data/documentation/How-PWN-Works.md +6 -5
- data/documentation/Installation.md +12 -9
- data/documentation/Mission-Cancellation.md +134 -0
- data/documentation/Operability.md +18 -0
- data/documentation/Persistence.md +7 -5
- data/documentation/Plugin-Degradation.md +14 -0
- data/documentation/Plugins.md +3 -2
- data/documentation/Policy-Benchmark.md +32 -33
- 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 +185 -0
- data/documentation/YubiKey.md +218 -0
- data/documentation/diagrams/cron-scheduling.svg +130 -130
- data/documentation/diagrams/dot/cron-scheduling.dot +3 -3
- data/documentation/diagrams/dot/persistence-filesystem.dot +5 -3
- data/documentation/diagrams/dot/plugin-ecosystem.dot +2 -2
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +1 -1
- data/documentation/diagrams/dot/reinforcement-learning.dot +2 -2
- data/documentation/diagrams/persistence-filesystem.svg +172 -142
- data/documentation/diagrams/plugin-ecosystem.svg +64 -64
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +1 -1
- data/documentation/diagrams/reinforcement-learning.svg +89 -89
- data/documentation/pwn-ai-Agent.md +10 -4
- data/etc/default_cron/session_cleanup.rb +43 -27
- 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/learning.rb +14 -18
- 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_evaluation.rb +334 -36
- 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 +4 -0
- 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 +8 -1
- data/lib/pwn/ai/server_tools.rb +231 -0
- data/lib/pwn/ai.rb +1 -0
- data/lib/pwn/backups.rb +349 -0
- data/lib/pwn/config.rb +70 -39
- data/lib/pwn/migrate.rb +85 -280
- 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/google_workspace.rb +4 -21
- data/lib/pwn/plugins/jobs.rb +1 -1
- data/lib/pwn/plugins/log.rb +3 -1
- data/lib/pwn/plugins/preflight_checker.rb +34 -9
- data/lib/pwn/plugins/recon.rb +4 -1
- data/lib/pwn/plugins/repl/ai.rb +293 -26
- data/lib/pwn/plugins/repl/ai_console.rb +268 -88
- data/lib/pwn/plugins/repl/ai_swarm.rb +177 -58
- data/lib/pwn/plugins/repl/mesh.rb +14 -19
- data/lib/pwn/plugins/repl/vault.rb +4 -15
- data/lib/pwn/plugins/repl.rb +9 -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/setup.rb +114 -24
- 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/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/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/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_evaluation_spec.rb +10 -23
- 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 +8 -16
- 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 +1 -1
- data/spec/lib/pwn/ai/server_tools_spec.rb +39 -0
- data/spec/lib/pwn/backups_spec.rb +129 -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/migrate_spec.rb +29 -81
- 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_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 +110 -75
- data/spec/lib/pwn/plugins/repl/ai_legacy_containment_spec.rb +200 -0
- data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +19 -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_mesh_layout_spec.rb +2 -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_yubikey_spec.rb +61 -0
- data/spec/lib/pwn/session_cleanup_default_spec.rb +7 -5
- data/spec/lib/pwn/session_cleanup_payload_spec.rb +11 -11
- data/spec/lib/pwn/session_cleanup_recovery_spec.rb +34 -20
- data/spec/lib/pwn/setup_native_yubikey_spec.rb +66 -0
- 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/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 +605 -19
- metadata +129 -16
- data/scripts/benchmark_policy.rb +0 -376
- data/scripts/build_gate.rb +0 -58
- data/spec/integration/build_gate_spec.rb +0 -73
data/lib/pwn/plugins.rb
CHANGED
|
@@ -111,6 +111,7 @@ module PWN
|
|
|
111
111
|
autoload :Volatility, 'pwn/plugins/volatility'
|
|
112
112
|
autoload :Vsphere, 'pwn/plugins/vsphere'
|
|
113
113
|
autoload :XXD, 'pwn/plugins/xxd'
|
|
114
|
+
autoload :YubiKey, 'pwn/plugins/yubikey'
|
|
114
115
|
autoload :Zaproxy, 'pwn/plugins/zaproxy'
|
|
115
116
|
|
|
116
117
|
# Display a List of Every PWN::Plugins Module
|
data/lib/pwn/redaction.rb
CHANGED
|
@@ -4,7 +4,7 @@ require 'digest'
|
|
|
4
4
|
require 'json'
|
|
5
5
|
|
|
6
6
|
module PWN
|
|
7
|
-
# Write-boundary redaction;
|
|
7
|
+
# Write-boundary redaction; redact never opens a credential store. Capture optionally stores ordinary secrets. Hardware/MFA fields use constant masks, never digest markers or loot capture.
|
|
8
8
|
module Redaction
|
|
9
9
|
PATTERNS = {
|
|
10
10
|
pem: /-----BEGIN [A-Z ]*PRIVATE KEY-----.*?(?:-----END [A-Z ]*PRIVATE KEY-----|\z)/m,
|
|
@@ -17,6 +17,15 @@ module PWN
|
|
|
17
17
|
cookie: /\bSet-Cookie:\s*(?!\[REDACTED:)[^\r\n]+/i
|
|
18
18
|
}.freeze
|
|
19
19
|
SECRET_FIELD = /\A(?:password|passwd|secret|api[_-]?key|authorization|proxy-authorization|access[_-]?token|refresh[_-]?token|private[_-]?key|client[_-]?secret)\z/i
|
|
20
|
+
CONSTANT_MASK = '[REDACTED]'
|
|
21
|
+
MASKED_NAME = /(?:
|
|
22
|
+
(?:(?:new|current|old|admin|user|fido2?|piv|openpgp)[_-])?(?:pin|puk) |
|
|
23
|
+
access[_-]?code | management[_-]?(?:key|password) | oath[_-]?secret | static[_-]?password |
|
|
24
|
+
scp(?:03|11)?(?:[_-](?:key|keys|enc|mac|dek)(?:[_-]key)?)? |
|
|
25
|
+
(?:mfa|otp|totp|hotp)(?:[_-](?:code|token|secret))? | (?:recovery|verification)[_-]?code
|
|
26
|
+
)/ix
|
|
27
|
+
MASKED_FIELD = /\A#{MASKED_NAME}\z/
|
|
28
|
+
MASKED_TEXT = /(?<![\w-])((?:--)?["']?#{MASKED_NAME}["']?\s*(?:=>|[:=])\s*)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\[REDACTED(?::[^\]]+)?\]|[^\s,;}\])]+)/
|
|
20
29
|
|
|
21
30
|
public_class_method def self.redact(opts = {})
|
|
22
31
|
value = opts[:value]
|
|
@@ -46,7 +55,9 @@ module PWN
|
|
|
46
55
|
case value
|
|
47
56
|
when Hash
|
|
48
57
|
value.to_h do |key, item|
|
|
49
|
-
clean = if key.to_s.match?(
|
|
58
|
+
clean = if key.to_s.match?(MASKED_FIELD) && !item.nil?
|
|
59
|
+
CONSTANT_MASK
|
|
60
|
+
elsif key.to_s.match?(SECRET_FIELD) && !item.nil?
|
|
50
61
|
token(kind: key.to_s.downcase, value: item.to_s)
|
|
51
62
|
else
|
|
52
63
|
redact_value(value: item)
|
|
@@ -55,15 +66,58 @@ module PWN
|
|
|
55
66
|
end
|
|
56
67
|
when Array then value.map { |item| redact_value(value: item) }
|
|
57
68
|
when String
|
|
58
|
-
PATTERNS.reduce(value
|
|
59
|
-
text.split(/(\[REDACTED
|
|
60
|
-
part.start_with?('[REDACTED
|
|
69
|
+
PATTERNS.reduce(mask_fields(text: value)) do |text, (kind, regex)|
|
|
70
|
+
text.split(/(\[REDACTED(?::[^:\]]+:[0-9a-f]{8})?\])/).map do |part|
|
|
71
|
+
part.start_with?('[REDACTED') ? part : part.gsub(regex) { |match| token(kind: kind, value: match) }
|
|
61
72
|
end.join
|
|
62
73
|
end
|
|
63
74
|
else value
|
|
64
75
|
end
|
|
65
76
|
end
|
|
66
77
|
|
|
78
|
+
private_class_method def self.mask_fields(opts = {})
|
|
79
|
+
text = opts[:text]
|
|
80
|
+
clean = +''
|
|
81
|
+
offset = 0
|
|
82
|
+
while (match = MASKED_TEXT.match(text, offset))
|
|
83
|
+
clean << text[offset...match.begin(0)] << match[1] << CONSTANT_MASK
|
|
84
|
+
offset = masked_value_end(text: text, start: match.end(1), finish: match.end(0))
|
|
85
|
+
end
|
|
86
|
+
clean << text[offset..]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Iterative scan prevents partial masks of serialized arrays/hashes of keys.
|
|
90
|
+
private_class_method def self.masked_value_end(opts = {})
|
|
91
|
+
text = opts[:text]
|
|
92
|
+
start = opts[:start]
|
|
93
|
+
pairs = { '[' => ']', '{' => '}' }
|
|
94
|
+
return opts[:finish] unless pairs.key?(text[start]) || ['"', "'"].include?(text[start])
|
|
95
|
+
|
|
96
|
+
stack = []
|
|
97
|
+
quote = nil
|
|
98
|
+
escaped = false
|
|
99
|
+
text[start..].each_char.with_index(start) do |char, index|
|
|
100
|
+
if quote
|
|
101
|
+
if escaped
|
|
102
|
+
escaped = false
|
|
103
|
+
elsif char == '\\'
|
|
104
|
+
escaped = true
|
|
105
|
+
elsif char == quote
|
|
106
|
+
quote = nil
|
|
107
|
+
return index + 1 if stack.empty?
|
|
108
|
+
end
|
|
109
|
+
elsif ['"', "'"].include?(char)
|
|
110
|
+
quote = char
|
|
111
|
+
elsif pairs.key?(char)
|
|
112
|
+
stack << pairs[char]
|
|
113
|
+
elsif pairs.value?(char)
|
|
114
|
+
return text.length unless stack.pop == char
|
|
115
|
+
return index + 1 if stack.empty?
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
text.length
|
|
119
|
+
end
|
|
120
|
+
|
|
67
121
|
public_class_method def self.token(opts = {})
|
|
68
122
|
value = opts[:value].to_s
|
|
69
123
|
return value if value.match?(/\A\[REDACTED:[^:]+:[0-9a-f]{8}\]\z/)
|
|
@@ -75,19 +129,21 @@ module PWN
|
|
|
75
129
|
value = opts[:value]
|
|
76
130
|
return { redacted: redact(value: value), refs: [] } unless value.is_a?(String)
|
|
77
131
|
|
|
78
|
-
text = value
|
|
132
|
+
text = mask_fields(text: value)
|
|
79
133
|
refs = []
|
|
80
134
|
redacted = PATTERNS.reduce(text.dup) do |acc, (kind, regex)|
|
|
81
135
|
acc.gsub(regex) do |match|
|
|
136
|
+
next match if match.match?(/\A[\w-]+\s*[:=]\s*\[REDACTED\]\z/)
|
|
137
|
+
|
|
82
138
|
tok = token(kind: kind, value: match)
|
|
83
139
|
refs << { kind: kind, token: tok }
|
|
84
|
-
PWN::Plugins::Vault.store(label: tok, secret: match, engagement: opts[:engagement]) if opts[:vault] != false && defined?(PWN::Plugins::Vault)
|
|
140
|
+
PWN::Plugins::Vault.store(label: tok, secret: match, engagement: opts[:engagement], yubikey: opts[:yubikey]) if opts[:vault] != false && defined?(PWN::Plugins::Vault)
|
|
85
141
|
tok
|
|
86
142
|
rescue StandardError
|
|
87
143
|
tok
|
|
88
144
|
end
|
|
89
145
|
end
|
|
90
|
-
redacted = capture_entropy(text: redacted, refs: refs, vault: opts[:vault], engagement: opts[:engagement]) if opts[:entropy] == true
|
|
146
|
+
redacted = capture_entropy(text: redacted, refs: refs, vault: opts[:vault], engagement: opts[:engagement], yubikey: opts[:yubikey]) if opts[:entropy] == true
|
|
91
147
|
{ redacted: redacted, refs: refs }
|
|
92
148
|
end
|
|
93
149
|
|
|
@@ -100,10 +156,10 @@ module PWN
|
|
|
100
156
|
|
|
101
157
|
tok = token(kind: 'entropy', value: match)
|
|
102
158
|
refs << { kind: 'entropy', token: tok }
|
|
103
|
-
PWN::Plugins::Vault.store(label: tok, secret: match, engagement: opts[:engagement]) if opts[:vault] != false && defined?(PWN::Plugins::Vault)
|
|
159
|
+
PWN::Plugins::Vault.store(label: tok, secret: match, engagement: opts[:engagement], yubikey: opts[:yubikey]) if opts[:vault] != false && defined?(PWN::Plugins::Vault)
|
|
104
160
|
tok
|
|
105
161
|
rescue StandardError
|
|
106
|
-
match
|
|
162
|
+
tok || token(kind: 'entropy', value: match)
|
|
107
163
|
end
|
|
108
164
|
end
|
|
109
165
|
|
|
@@ -122,7 +178,7 @@ module PWN
|
|
|
122
178
|
|
|
123
179
|
public_class_method def self.help
|
|
124
180
|
puts "USAGE:
|
|
125
|
-
# Redact
|
|
181
|
+
# Redact before persistence; hardware/MFA fields use constant masks. Reject cycles/depth above 100 with JSON::NestingError.
|
|
126
182
|
#{self}.redact(
|
|
127
183
|
value: 'required - string or nested JSON-compatible data'
|
|
128
184
|
)
|
|
@@ -131,10 +187,11 @@ module PWN
|
|
|
131
187
|
kind: 'required - secret type label',
|
|
132
188
|
value: 'required - sensitive value to hash'
|
|
133
189
|
)
|
|
134
|
-
# Capture secrets into Vault labels
|
|
190
|
+
# Capture ordinary secrets into Vault labels; mask hardware/MFA fields without storing them.
|
|
135
191
|
#{self}.capture(
|
|
136
192
|
value: 'required - string that may contain credentials',
|
|
137
193
|
vault: 'optional - false skips Vault.store',
|
|
194
|
+
yubikey: 'optional - selector Hash for hardware-encrypted captured loot; no software fallback on failure',
|
|
138
195
|
engagement: 'optional - engagement name stored with the vault label',
|
|
139
196
|
entropy: 'optional - true also captures high-entropy tokens'
|
|
140
197
|
)
|
data/lib/pwn/session_trace.rb
CHANGED
|
@@ -91,6 +91,10 @@ module PWN
|
|
|
91
91
|
timeout = Float(opts.fetch(:timeout, 30))
|
|
92
92
|
raise ArgumentError, 'Invalid rerun deadline' unless timeout.positive? && timeout <= 300
|
|
93
93
|
|
|
94
|
+
# A user-namespace caller that is not uid 0 may still hold capabilities.
|
|
95
|
+
# Bubblewrap rejects those. Uid 0 can already create the nested map;
|
|
96
|
+
# dropping every capability there makes the uid-map setup fail.
|
|
97
|
+
argv.unshift('setpriv', '--ambient-caps=-all', '--inh-caps=-all', '--bounding-set=-all', '--') if PWN::AI::Agent::MissionNamespace.prepared? && !Process.uid.zero?
|
|
94
98
|
Open3.popen3(*argv, unsetenv_others: true, pgroup: true) do |stdin, stdout, stderr, wait|
|
|
95
99
|
readers = [Thread.new { stdout.read }, Thread.new { stderr.read }]
|
|
96
100
|
begin
|
data/lib/pwn/setup.rb
CHANGED
|
@@ -129,10 +129,44 @@ module PWN
|
|
|
129
129
|
}
|
|
130
130
|
}.freeze
|
|
131
131
|
|
|
132
|
+
# Optional runtime libraries, not executables or Ruby build dependencies.
|
|
133
|
+
# macOS provides PC/SC as a system framework. Linux PC/SC also needs a
|
|
134
|
+
# daemon and CCID reader driver for actual I/O; doctor only loads libraries.
|
|
135
|
+
NATIVE_LIBRARIES = {
|
|
136
|
+
'hidapi' => {
|
|
137
|
+
apt: %w[libhidapi-hidraw0], dnf: %w[hidapi], pacman: %w[hidapi],
|
|
138
|
+
brew: %w[hidapi], port: %w[hidapi],
|
|
139
|
+
libraries: {
|
|
140
|
+
unix: %w[libhidapi-hidraw.so.0 libhidapi-hidraw.so libhidapi-libusb.so.0 libhidapi-libusb.so libhidapi.so],
|
|
141
|
+
macos: %w[libhidapi.dylib /opt/homebrew/lib/libhidapi.dylib /usr/local/lib/libhidapi.dylib /opt/local/lib/libhidapi.dylib],
|
|
142
|
+
windows: %w[hidapi.dll]
|
|
143
|
+
},
|
|
144
|
+
plugins: %w[PWN::Plugins::YubiKey]
|
|
145
|
+
},
|
|
146
|
+
'pcsc' => {
|
|
147
|
+
apt: %w[libpcsclite1 pcscd libccid], dnf: %w[pcsc-lite-libs pcsc-lite pcsc-lite-ccid],
|
|
148
|
+
pacman: %w[pcsclite ccid], brew: %w[], port: %w[],
|
|
149
|
+
libraries: {
|
|
150
|
+
unix: %w[libpcsclite.so.1 libpcsclite.so],
|
|
151
|
+
macos: %w[/System/Library/Frameworks/PCSC.framework/PCSC],
|
|
152
|
+
windows: %w[winscard.dll]
|
|
153
|
+
},
|
|
154
|
+
plugins: %w[PWN::Plugins::YubiKey]
|
|
155
|
+
}
|
|
156
|
+
}.freeze
|
|
157
|
+
|
|
132
158
|
# External binaries PWN wraps → OS packages that provide them.
|
|
133
159
|
# (Extrospection.probe_toolchain already knows how to *detect* these;
|
|
134
160
|
# this table knows how to *install* them.)
|
|
135
161
|
TOOLCHAIN = {
|
|
162
|
+
'setpriv' => {
|
|
163
|
+
apt: %w[util-linux], dnf: %w[util-linux], pacman: %w[util-linux], brew: %w[], port: %w[],
|
|
164
|
+
plugins: %w[PWN::SessionTrace]
|
|
165
|
+
},
|
|
166
|
+
'unshare' => {
|
|
167
|
+
apt: %w[util-linux], dnf: %w[util-linux], pacman: %w[util-linux], brew: %w[], port: %w[],
|
|
168
|
+
plugins: %w[PWN::AI::Agent::MissionNamespace]
|
|
169
|
+
},
|
|
136
170
|
'nmap' => {
|
|
137
171
|
apt: %w[nmap], dnf: %w[nmap], pacman: %w[nmap], brew: %w[nmap], port: %w[nmap],
|
|
138
172
|
plugins: %w[PWN::Plugins::NmapIt]
|
|
@@ -408,7 +442,7 @@ module PWN
|
|
|
408
442
|
ai: {
|
|
409
443
|
desc: 'verify at least one AI engine key/oauth in ~/.pwn/pwn.yaml',
|
|
410
444
|
gems: %w[],
|
|
411
|
-
bins: %w[jq]
|
|
445
|
+
bins: %w[jq unshare setpriv]
|
|
412
446
|
},
|
|
413
447
|
web: {
|
|
414
448
|
desc: 'TransparentBrowser · BurpSuite · Zaproxy · extro_verify · extro_watch',
|
|
@@ -446,9 +480,10 @@ module PWN
|
|
|
446
480
|
bins: %w[msfconsole sqlmap]
|
|
447
481
|
},
|
|
448
482
|
hardware: {
|
|
449
|
-
desc: 'Serial · BusPirate · Android · BareSIP · extro_serial · extro_telecomm',
|
|
483
|
+
desc: 'Serial · BusPirate · Android · BareSIP · YubiKey · extro_serial · extro_telecomm',
|
|
450
484
|
gems: %w[libusb],
|
|
451
|
-
bins: %w[adb baresip]
|
|
485
|
+
bins: %w[adb baresip],
|
|
486
|
+
libs: %w[hidapi pcsc]
|
|
452
487
|
},
|
|
453
488
|
re: {
|
|
454
489
|
desc: 'Radare2 · GDB · ExploitDev · Frida · Android RE tier',
|
|
@@ -458,7 +493,8 @@ module PWN
|
|
|
458
493
|
full: {
|
|
459
494
|
desc: 'everything above',
|
|
460
495
|
gems: NATIVE_GEMS.keys,
|
|
461
|
-
bins: TOOLCHAIN.keys
|
|
496
|
+
bins: TOOLCHAIN.keys,
|
|
497
|
+
libs: NATIVE_LIBRARIES.keys
|
|
462
498
|
}
|
|
463
499
|
}.freeze
|
|
464
500
|
|
|
@@ -572,15 +608,23 @@ module PWN
|
|
|
572
608
|
io.puts
|
|
573
609
|
|
|
574
610
|
# ~/.pwn -----------------------------------------------------------
|
|
575
|
-
pwn_root
|
|
576
|
-
|
|
577
|
-
|
|
611
|
+
pwn_root = File.join(Dir.home, '.pwn')
|
|
612
|
+
driver = defined?(PWN::Env) && PWN::Env.is_a?(Hash) ? PWN::Env[:driver_opts] : nil
|
|
613
|
+
driver = {} unless driver.is_a?(Hash)
|
|
614
|
+
pwn_yaml = driver[:pwn_env_path] || File.join(pwn_root, 'pwn.yaml')
|
|
615
|
+
pwn_dec = driver[:pwn_dec_path] || "#{pwn_yaml}.decryptor"
|
|
578
616
|
root_ok = File.directory?(pwn_root)
|
|
579
617
|
root_note = root_ok ? "(#{Dir.children(pwn_root).length} entries)" : '(will be created on first `pwn` launch)'
|
|
580
618
|
io.puts "#{'~/.pwn/'.ljust(26)} #{root_ok ? OK : MISS} #{root_note}"
|
|
581
619
|
yaml_ok = File.file?(pwn_yaml)
|
|
582
620
|
dec_state = File.file?(pwn_dec) ? 'present' : 'MISSING'
|
|
583
|
-
yaml_note = yaml_ok
|
|
621
|
+
yaml_note = if !yaml_ok
|
|
622
|
+
'(run `pwn` once, then `pwn-vault` to configure)'
|
|
623
|
+
elsif PWN::Plugins::Vault.hardware_encrypted?(file: pwn_yaml)
|
|
624
|
+
'(YubiKey encrypted; no software decryptor required)'
|
|
625
|
+
else
|
|
626
|
+
"(encrypted, decryptor #{dec_state})"
|
|
627
|
+
end
|
|
584
628
|
io.puts "#{'~/.pwn/pwn.yaml'.ljust(26)} #{yaml_ok ? OK : MISS} #{yaml_note}"
|
|
585
629
|
engine = ai_active_engine
|
|
586
630
|
key_ok = ai_key_configured?(engine: engine)
|
|
@@ -603,6 +647,20 @@ module PWN
|
|
|
603
647
|
end
|
|
604
648
|
io.puts
|
|
605
649
|
|
|
650
|
+
# Runtime libraries ------------------------------------------------
|
|
651
|
+
io.puts 'Native libraries (host loading only; no device discovery)'
|
|
652
|
+
lib_missing = []
|
|
653
|
+
NATIVE_LIBRARIES.each do |name, meta|
|
|
654
|
+
ok = native_library_available?(name: name, os: os)
|
|
655
|
+
lib_missing << name unless ok
|
|
656
|
+
packages = Array(meta[pm[:key]])
|
|
657
|
+
hint = packages.empty? ? 'native OS library; see hardware profile' : packages.join(' ')
|
|
658
|
+
need = ok ? '' : "(needs: #{hint})"
|
|
659
|
+
io.puts " #{name.ljust(14)} #{ok ? OK : MISS} #{need} → #{Array(meta[:plugins]).join(', ')}"
|
|
660
|
+
end
|
|
661
|
+
io.puts ' Library presence does not verify device permissions, PC/SC service, reader or token readiness.'
|
|
662
|
+
io.puts
|
|
663
|
+
|
|
606
664
|
# Toolchain --------------------------------------------------------
|
|
607
665
|
io.puts "#{'External toolchain'.ljust(46)} used by"
|
|
608
666
|
bin_missing = []
|
|
@@ -621,36 +679,41 @@ module PWN
|
|
|
621
679
|
io.puts
|
|
622
680
|
end
|
|
623
681
|
|
|
624
|
-
total = NATIVE_GEMS.size + TOOLCHAIN.size
|
|
625
|
-
missing = gem_missing.size + bin_missing.size
|
|
682
|
+
total = NATIVE_GEMS.size + TOOLCHAIN.size + NATIVE_LIBRARIES.size
|
|
683
|
+
missing = gem_missing.size + bin_missing.size + lib_missing.size
|
|
626
684
|
io.puts "#{total - missing} / #{total} capabilities usable · #{missing} degraded"
|
|
627
685
|
io.puts
|
|
628
686
|
unless missing.zero?
|
|
629
|
-
io.puts 'Run `pwn setup --deps` to install missing OS headers/tools, or'
|
|
687
|
+
io.puts 'Run `pwn setup --deps` to install missing OS headers/tools/libraries, or'
|
|
630
688
|
io.puts ' `pwn setup --profile <name>` for a subset. See `pwn setup --list-profiles`.'
|
|
631
689
|
end
|
|
632
690
|
|
|
633
691
|
# When a profile is given (`pwn setup --check --profile core`), :ok is
|
|
634
|
-
# gated ONLY on that profile's gems/bins so a partially-provisioned host
|
|
692
|
+
# gated ONLY on that profile's gems/bins/libs so a partially-provisioned host
|
|
635
693
|
# can still pass its own doctor gate. `packer/provisioners/pwn.sh` and
|
|
636
694
|
# spec/integration/fresh_install_spec.rb rely on this — a `core` leg of
|
|
637
695
|
# install-matrix must not fail because gqrx/burpsuite/etc. are absent.
|
|
638
696
|
gate_gems = gem_missing
|
|
639
697
|
gate_bins = bin_missing
|
|
698
|
+
gate_libs = lib_missing
|
|
640
699
|
if profile && PROFILES.key?(profile)
|
|
641
700
|
prof = PROFILES[profile]
|
|
642
701
|
gate_gems = gem_missing & Array(prof[:gems])
|
|
643
702
|
gate_bins = bin_missing & Array(prof[:bins])
|
|
703
|
+
gate_libs = lib_missing & Array(prof[:libs])
|
|
644
704
|
io.puts
|
|
705
|
+
library_note = ", libs: #{gate_libs.empty? ? '—' : gate_libs.join(' ')}" unless Array(prof[:libs]).empty?
|
|
645
706
|
io.puts "Profile '#{profile}' gate: " \
|
|
646
|
-
"#{gate_gems.empty? && gate_bins.empty? ? OK : MISS} " \
|
|
707
|
+
"#{gate_gems.empty? && gate_bins.empty? && gate_libs.empty? ? OK : MISS} " \
|
|
647
708
|
"(gems: #{gate_gems.empty? ? '—' : gate_gems.join(' ')}, " \
|
|
648
|
-
"bins: #{gate_bins.empty? ? '—' : gate_bins.join(' ')}
|
|
709
|
+
"bins: #{gate_bins.empty? ? '—' : gate_bins.join(' ')}" \
|
|
710
|
+
"#{library_note})"
|
|
649
711
|
end
|
|
650
712
|
|
|
651
|
-
{ ok: gate_gems.empty? && gate_bins.empty? && Array(migrate && migrate[:incompatible]).empty?,
|
|
713
|
+
{ ok: gate_gems.empty? && gate_bins.empty? && gate_libs.empty? && Array(migrate && migrate[:incompatible]).empty?,
|
|
652
714
|
native_gems_missing: gem_missing, toolchain_missing: bin_missing,
|
|
653
|
-
state: migrate, pkg_manager: pm[:key], os: os, arch: arch
|
|
715
|
+
state: migrate, pkg_manager: pm[:key], os: os, arch: arch,
|
|
716
|
+
native_libraries_missing: lib_missing }
|
|
654
717
|
rescue StandardError => e
|
|
655
718
|
raise e
|
|
656
719
|
end
|
|
@@ -690,6 +753,7 @@ module PWN
|
|
|
690
753
|
bins = Array(prof[:bins])
|
|
691
754
|
os_pkgs = []
|
|
692
755
|
gems.each { |g| os_pkgs.concat(Array(NATIVE_GEMS.dig(g, pm[:key]))) }
|
|
756
|
+
Array(prof[:libs]).each { |lib| os_pkgs.concat(Array(NATIVE_LIBRARIES.dig(lib, pm[:key]))) }
|
|
693
757
|
bins.each { |b| os_pkgs.concat(packages_for(bin: b, distro: distro, version: version, pm_key: pm[:key])) }
|
|
694
758
|
os_pkgs = os_pkgs.reject(&:empty?).uniq
|
|
695
759
|
alts = alt_install_cmds(bins: bins, pm_key: pm[:key], distro: distro, version: version)
|
|
@@ -765,7 +829,7 @@ module PWN
|
|
|
765
829
|
# third_party/terminator/pwn_shift_enter.py to
|
|
766
830
|
# ~/.config/terminator/plugins/ and enables the PWNShiftEnter
|
|
767
831
|
# plugin in ~/.config/terminator/config — WITH confirmation and
|
|
768
|
-
# a
|
|
832
|
+
# a shared recovery backup. This is the only VTE emulator we can fix at
|
|
769
833
|
# the GTK layer without patching libvte itself.
|
|
770
834
|
#
|
|
771
835
|
# Everything here is host-config mutation, which is why it lives in
|
|
@@ -831,7 +895,7 @@ module PWN
|
|
|
831
895
|
io.puts
|
|
832
896
|
io.puts " write #{plugin_dst}"
|
|
833
897
|
io.puts " edit #{cfg_path} (add PWNShiftEnter to enabled_plugins)"
|
|
834
|
-
io.puts
|
|
898
|
+
io.puts ' backup ~/.pwn/backup (shared seven-generation store)'
|
|
835
899
|
io.puts
|
|
836
900
|
|
|
837
901
|
unless yes
|
|
@@ -858,7 +922,7 @@ module PWN
|
|
|
858
922
|
if cfg.include?('PWNShiftEnter')
|
|
859
923
|
io.puts " #{OK} PWNShiftEnter already enabled in #{cfg_path}"
|
|
860
924
|
else
|
|
861
|
-
|
|
925
|
+
|
|
862
926
|
new_cfg =
|
|
863
927
|
if cfg =~ /^\s*enabled_plugins\s*=\s*(.*)$/
|
|
864
928
|
cfg.sub(/^(\s*enabled_plugins\s*=\s*)(.*)$/) do
|
|
@@ -871,8 +935,11 @@ module PWN
|
|
|
871
935
|
else
|
|
872
936
|
"[global_config]\n enabled_plugins = PWNShiftEnter\n#{cfg}"
|
|
873
937
|
end
|
|
874
|
-
|
|
875
|
-
|
|
938
|
+
PWN::Backups.transaction do
|
|
939
|
+
backup = PWN::Backups.capture(path: cfg_path, label: 'terminator')
|
|
940
|
+
File.write(cfg_path, new_cfg)
|
|
941
|
+
io.puts " #{OK} enabled PWNShiftEnter in #{cfg_path} (backup: #{backup})"
|
|
942
|
+
end
|
|
876
943
|
end
|
|
877
944
|
else
|
|
878
945
|
FileUtils.mkdir_p(File.dirname(cfg_path))
|
|
@@ -900,6 +967,7 @@ module PWN
|
|
|
900
967
|
io.puts " #{name.to_s.ljust(10)} #{meta[:desc]}"
|
|
901
968
|
io.puts " #{''.ljust(10)} gems: #{meta[:gems].join(' ')}" unless meta[:gems].empty?
|
|
902
969
|
io.puts " #{''.ljust(10)} bins: #{meta[:bins].join(' ')}" unless meta[:bins].empty?
|
|
970
|
+
io.puts " #{''.ljust(10)} libs: #{meta[:libs].join(' ')}" unless Array(meta[:libs]).empty?
|
|
903
971
|
io.puts
|
|
904
972
|
end
|
|
905
973
|
PROFILES.keys
|
|
@@ -998,6 +1066,28 @@ module PWN
|
|
|
998
1066
|
''
|
|
999
1067
|
end
|
|
1000
1068
|
|
|
1069
|
+
private_class_method def self.native_library_available?(opts = {})
|
|
1070
|
+
require 'ffi'
|
|
1071
|
+
os = opts[:os].to_s
|
|
1072
|
+
platform = if os.match?(/darwin|osx|macos/i)
|
|
1073
|
+
:macos
|
|
1074
|
+
elsif os.match?(/mswin|mingw|cygwin|windows/i)
|
|
1075
|
+
:windows
|
|
1076
|
+
else
|
|
1077
|
+
:unix
|
|
1078
|
+
end
|
|
1079
|
+
libraries = NATIVE_LIBRARIES.fetch(opts[:name]).fetch(:libraries).fetch(platform)
|
|
1080
|
+
flags = ::FFI::DynamicLibrary::RTLD_LAZY | ::FFI::DynamicLibrary::RTLD_LOCAL
|
|
1081
|
+
libraries.any? do |library|
|
|
1082
|
+
::FFI::DynamicLibrary.open(library, flags)
|
|
1083
|
+
true
|
|
1084
|
+
rescue LoadError
|
|
1085
|
+
false
|
|
1086
|
+
end
|
|
1087
|
+
rescue LoadError
|
|
1088
|
+
false
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1001
1091
|
private_class_method def self.gem_loadable?(opts = {})
|
|
1002
1092
|
name = opts[:name].to_s
|
|
1003
1093
|
Gem::Specification.find_by_name(name)
|
|
@@ -1137,13 +1227,13 @@ module PWN
|
|
|
1137
1227
|
pm_key: 'optional - package-manager family (:apt :dnf :pacman :brew :port :pkg)'
|
|
1138
1228
|
)
|
|
1139
1229
|
|
|
1140
|
-
#
|
|
1230
|
+
# Check gems, binaries and native host libraries; native_libraries_missing reports HIDAPI/PCSC without device discovery.
|
|
1141
1231
|
#{self}.check(
|
|
1142
1232
|
io: 'optional - IO to write the report to (default $stdout)',
|
|
1143
|
-
profile: 'optional - profile
|
|
1233
|
+
profile: 'optional - gate only this profile; core never requires hardware libraries'
|
|
1144
1234
|
)
|
|
1145
1235
|
|
|
1146
|
-
# Install OS packages
|
|
1236
|
+
# Install OS packages and gems; hardware/full add HIDAPI and PC/SC, not a Python or executable YubiKey backend.
|
|
1147
1237
|
#{self}.deps(
|
|
1148
1238
|
profile: 'optional - one of PROFILES.keys (default :full)',
|
|
1149
1239
|
yes: 'optional - non-interactive; assume yes to prompts (default false)',
|
data/lib/pwn/version.rb
CHANGED
|
@@ -5,6 +5,8 @@ require 'yaml'
|
|
|
5
5
|
module PWN
|
|
6
6
|
module WWW
|
|
7
7
|
# This plugin supports app.cobalt.io actions.
|
|
8
|
+
# YubiKey OATH MFA requires explicit confirm: true because HOTP advances counters
|
|
9
|
+
# and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
|
|
8
10
|
module AppCobaltIO
|
|
9
11
|
# Supported Method Parameters::
|
|
10
12
|
# browser_obj = PWN::WWW::AppCobaltIO.open(
|
|
@@ -28,10 +30,13 @@ module PWN
|
|
|
28
30
|
# browser_obj: 'required - browser_obj returned from #open method',
|
|
29
31
|
# username: 'required - username',
|
|
30
32
|
# password: 'optional - passwd (will prompt if blank)',
|
|
31
|
-
# mfa: 'optional - if true prompt for mfa token (defaults to false)'
|
|
33
|
+
# mfa: 'optional - if true prompt for mfa token (defaults to false)',
|
|
34
|
+
# yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
|
|
32
35
|
# )
|
|
33
36
|
|
|
34
37
|
public_class_method def self.login(opts = {})
|
|
38
|
+
PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
|
|
39
|
+
yubikey = opts[:yubikey]
|
|
35
40
|
browser_obj = opts[:browser_obj]
|
|
36
41
|
username = opts[:username].to_s.scrub.strip.chomp
|
|
37
42
|
password = opts[:password]
|
|
@@ -54,9 +59,9 @@ module PWN
|
|
|
54
59
|
# name: 'commit' doesn't work
|
|
55
60
|
browser.button(index: 6).click! # no name or id in button element
|
|
56
61
|
|
|
57
|
-
if mfa
|
|
62
|
+
if mfa || !yubikey.nil?
|
|
58
63
|
until browser.url == 'https://app.cobalt.io/dashboard'
|
|
59
|
-
browser.text_field(id: 'code').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
|
|
64
|
+
browser.text_field(id: 'code').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey))
|
|
60
65
|
browser.button(name: 'commit').click!
|
|
61
66
|
sleep 3
|
|
62
67
|
end
|
|
@@ -117,12 +122,18 @@ module PWN
|
|
|
117
122
|
proxy: 'optional - scheme://proxy_host:port || tor'
|
|
118
123
|
)
|
|
119
124
|
|
|
120
|
-
#
|
|
125
|
+
# Log in with manual MFA or explicitly selected YubiKey OATH.
|
|
126
|
+
# YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
|
|
127
|
+
# Configure the service for an authenticator challenge, not SMS.
|
|
128
|
+
# Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
|
|
129
|
+
# Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
|
|
130
|
+
# Reject conflicting explicit tokens before any login action.
|
|
121
131
|
#{self}.login(
|
|
122
132
|
browser_obj: 'required - browser_obj returned from #open method',
|
|
123
133
|
username: 'required - username value consumed by #login',
|
|
124
134
|
password: 'optional - passwd (will prompt if blank)',
|
|
125
|
-
mfa: 'optional - if true prompt for mfa token (defaults to false)'
|
|
135
|
+
mfa: 'optional - if true prompt for mfa token (defaults to false)',
|
|
136
|
+
yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
|
|
126
137
|
)
|
|
127
138
|
|
|
128
139
|
# Run logout and return its result
|
data/lib/pwn/www/bug_crowd.rb
CHANGED
|
@@ -5,6 +5,8 @@ require 'yaml'
|
|
|
5
5
|
module PWN
|
|
6
6
|
module WWW
|
|
7
7
|
# This plugin supports bugcrowd.com actions.
|
|
8
|
+
# YubiKey OATH MFA requires explicit confirm: true because HOTP advances counters
|
|
9
|
+
# and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
|
|
8
10
|
module BugCrowd
|
|
9
11
|
# Supported Method Parameters::
|
|
10
12
|
# browser_obj = PWN::WWW::BugCrowd.open(
|
|
@@ -28,10 +30,13 @@ module PWN
|
|
|
28
30
|
# browser_obj: 'required - browser_obj returned from #open method',
|
|
29
31
|
# username: 'required - username',
|
|
30
32
|
# password: 'optional - passwd (will prompt if blank)',
|
|
31
|
-
# mfa: 'optional - if true prompt for mfa token (defaults to false)'
|
|
33
|
+
# mfa: 'optional - if true prompt for mfa token (defaults to false)',
|
|
34
|
+
# yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
|
|
32
35
|
# )
|
|
33
36
|
|
|
34
37
|
public_class_method def self.login(opts = {})
|
|
38
|
+
PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
|
|
39
|
+
yubikey = opts[:yubikey]
|
|
35
40
|
browser_obj = opts[:browser_obj]
|
|
36
41
|
username = opts[:username].to_s.scrub.strip.chomp
|
|
37
42
|
password = opts[:password]
|
|
@@ -51,9 +56,9 @@ module PWN
|
|
|
51
56
|
browser.text_field(id: 'user_password').wait_until(&:present?).set(password)
|
|
52
57
|
browser.button(name: 'button').click!
|
|
53
58
|
|
|
54
|
-
if mfa
|
|
59
|
+
if mfa || !yubikey.nil?
|
|
55
60
|
until browser.url == 'https://bugcrowd.com/programs'
|
|
56
|
-
browser.text_field(name: 'otp_attempt').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
|
|
61
|
+
browser.text_field(name: 'otp_attempt').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey))
|
|
57
62
|
browser.button(name: 'commit').click!
|
|
58
63
|
sleep 3
|
|
59
64
|
end
|
|
@@ -114,12 +119,18 @@ module PWN
|
|
|
114
119
|
proxy: 'optional - scheme://proxy_host:port || tor'
|
|
115
120
|
)
|
|
116
121
|
|
|
117
|
-
#
|
|
122
|
+
# Log in with manual MFA or explicitly selected YubiKey OATH.
|
|
123
|
+
# YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
|
|
124
|
+
# Configure the service for an authenticator challenge, not SMS.
|
|
125
|
+
# Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
|
|
126
|
+
# Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
|
|
127
|
+
# Reject conflicting explicit tokens before any login action.
|
|
118
128
|
#{self}.login(
|
|
119
129
|
browser_obj: 'required - browser_obj returned from #open method',
|
|
120
130
|
username: 'required - username value consumed by #login',
|
|
121
131
|
password: 'optional - passwd (will prompt if blank)',
|
|
122
|
-
mfa: 'optional - if true prompt for mfa token (defaults to false)'
|
|
132
|
+
mfa: 'optional - if true prompt for mfa token (defaults to false)',
|
|
133
|
+
yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
|
|
123
134
|
)
|
|
124
135
|
|
|
125
136
|
# Run logout and return its result
|
data/lib/pwn/www/coinbase_pro.rb
CHANGED
|
@@ -5,6 +5,8 @@ require 'yaml'
|
|
|
5
5
|
module PWN
|
|
6
6
|
module WWW
|
|
7
7
|
# This plugin supports tradingview.com actions.
|
|
8
|
+
# YubiKey OATH MFA requires explicit confirm: true because HOTP advances counters
|
|
9
|
+
# and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
|
|
8
10
|
module CoinbasePro
|
|
9
11
|
# Supported Method Parameters::
|
|
10
12
|
# browser_obj = PWN::WWW::CoinbasePro.open(
|
|
@@ -28,10 +30,13 @@ module PWN
|
|
|
28
30
|
# browser_obj: 'required - browser_obj returned from #open method',
|
|
29
31
|
# username: 'required - username',
|
|
30
32
|
# password: 'optional - passwd (will prompt if blank)',
|
|
31
|
-
# mfa: 'optional - if true prompt for mfa token (defaults to false)'
|
|
33
|
+
# mfa: 'optional - if true prompt for mfa token (defaults to false)',
|
|
34
|
+
# yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
|
|
32
35
|
# )
|
|
33
36
|
|
|
34
37
|
public_class_method def self.login(opts = {})
|
|
38
|
+
PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
|
|
39
|
+
yubikey = opts[:yubikey]
|
|
35
40
|
browser_obj = opts[:browser_obj]
|
|
36
41
|
username = opts[:username].to_s.scrub.strip.chomp
|
|
37
42
|
password = opts[:password]
|
|
@@ -52,9 +57,9 @@ module PWN
|
|
|
52
57
|
browser.text_field(name: 'password').wait_until(&:present?).set(password)
|
|
53
58
|
browser.button(text: 'Sign In').click!
|
|
54
59
|
|
|
55
|
-
if mfa
|
|
60
|
+
if mfa || !yubikey.nil?
|
|
56
61
|
until browser.url.include?('https://pro.coinbase.com')
|
|
57
|
-
browser.text_field(name: 'token').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
|
|
62
|
+
browser.text_field(name: 'token').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey))
|
|
58
63
|
browser.button(text: 'Verify').click!
|
|
59
64
|
sleep 3
|
|
60
65
|
end
|
|
@@ -114,12 +119,18 @@ module PWN
|
|
|
114
119
|
proxy: 'optional - scheme://proxy_host:port || tor'
|
|
115
120
|
)
|
|
116
121
|
|
|
117
|
-
#
|
|
122
|
+
# Log in with manual MFA or explicitly selected YubiKey OATH.
|
|
123
|
+
# YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
|
|
124
|
+
# Configure the service for an authenticator challenge, not SMS.
|
|
125
|
+
# Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
|
|
126
|
+
# Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
|
|
127
|
+
# Reject conflicting explicit tokens before any login action.
|
|
118
128
|
#{self}.login(
|
|
119
129
|
browser_obj: 'required - browser_obj returned from #open method',
|
|
120
130
|
username: 'required - username value consumed by #login',
|
|
121
131
|
password: 'optional - passwd (will prompt if blank)',
|
|
122
|
-
mfa: 'optional - if true prompt for mfa token (defaults to false)'
|
|
132
|
+
mfa: 'optional - if true prompt for mfa token (defaults to false)',
|
|
133
|
+
yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
|
|
123
134
|
)
|
|
124
135
|
|
|
125
136
|
# Run logout and return its result
|