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/www/github.rb
CHANGED
|
@@ -5,6 +5,8 @@ require 'yaml'
|
|
|
5
5
|
module PWN
|
|
6
6
|
module WWW
|
|
7
7
|
# This plugin supports github.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 GitHub
|
|
9
11
|
# Supported Method Parameters::
|
|
10
12
|
# browser_obj = PWN::WWW::GitHub.open(
|
|
@@ -29,10 +31,12 @@ module PWN
|
|
|
29
31
|
# username: 'required - username',
|
|
30
32
|
# password: 'optional - passwd (will prompt if blank)',
|
|
31
33
|
# mfa: 'optional - MFA / TOTP token string, or true to prompt (defaults to false)',
|
|
32
|
-
# mfa_token: 'optional - MFA / TOTP token used to reach a post-authenticated state'
|
|
34
|
+
# mfa_token: 'optional - MFA / TOTP token used to reach a post-authenticated state',
|
|
35
|
+
# 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'
|
|
33
36
|
# )
|
|
34
37
|
|
|
35
38
|
public_class_method def self.login(opts = {})
|
|
39
|
+
PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
|
|
36
40
|
browser_obj = opts[:browser_obj]
|
|
37
41
|
username = opts[:username].to_s.scrub.strip.chomp
|
|
38
42
|
password = opts[:password]
|
|
@@ -45,6 +49,7 @@ module PWN
|
|
|
45
49
|
password = opts[:password].to_s.scrub.strip.chomp
|
|
46
50
|
end
|
|
47
51
|
mfa = opts[:mfa]
|
|
52
|
+
yubikey = opts[:yubikey]
|
|
48
53
|
mfa_token = opts[:mfa_token].to_s.scrub.strip.chomp
|
|
49
54
|
mfa_token = mfa.to_s.scrub.strip.chomp if mfa_token.empty? && mfa.is_a?(String)
|
|
50
55
|
|
|
@@ -54,7 +59,7 @@ module PWN
|
|
|
54
59
|
browser.text_field(name: 'password').wait_until(&:present?).set(password)
|
|
55
60
|
browser.input(name: 'commit').click!
|
|
56
61
|
|
|
57
|
-
if mfa || !mfa_token.empty?
|
|
62
|
+
if mfa || !mfa_token.empty? || !yubikey.nil?
|
|
58
63
|
until browser.url == 'https://github.com/' ||
|
|
59
64
|
browser.url.include?('https://github.com/dashboard') ||
|
|
60
65
|
(
|
|
@@ -63,23 +68,28 @@ module PWN
|
|
|
63
68
|
!browser.url.include?('/session')
|
|
64
69
|
)
|
|
65
70
|
|
|
71
|
+
mfa_field = if browser.text_field(name: 'app_otp').exist?
|
|
72
|
+
browser.text_field(name: 'app_otp')
|
|
73
|
+
elsif browser.text_field(id: 'app_totp').exist?
|
|
74
|
+
browser.text_field(id: 'app_totp')
|
|
75
|
+
elsif browser.text_field(name: 'otp').exist?
|
|
76
|
+
browser.text_field(name: 'otp')
|
|
77
|
+
else
|
|
78
|
+
raise ArgumentError, 'YubiKey OATH requires an authenticator challenge, not SMS' unless yubikey.nil?
|
|
79
|
+
|
|
80
|
+
browser.text_field(name: 'sms_otp')
|
|
81
|
+
end
|
|
82
|
+
mfa_field.wait_until(&:present?)
|
|
83
|
+
|
|
66
84
|
token = if mfa_token.empty?
|
|
67
|
-
PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token')
|
|
85
|
+
PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey)
|
|
68
86
|
else
|
|
69
87
|
consumed = mfa_token
|
|
70
88
|
mfa_token = ''
|
|
71
89
|
consumed
|
|
72
90
|
end
|
|
73
91
|
|
|
74
|
-
|
|
75
|
-
browser.text_field(name: 'app_otp').wait_until(&:present?).set(token)
|
|
76
|
-
elsif browser.text_field(id: 'app_totp').exist?
|
|
77
|
-
browser.text_field(id: 'app_totp').wait_until(&:present?).set(token)
|
|
78
|
-
elsif browser.text_field(name: 'otp').exist?
|
|
79
|
-
browser.text_field(name: 'otp').wait_until(&:present?).set(token)
|
|
80
|
-
else
|
|
81
|
-
browser.text_field(name: 'sms_otp').wait_until(&:present?).set(token)
|
|
82
|
-
end
|
|
92
|
+
mfa_field.set(token)
|
|
83
93
|
|
|
84
94
|
if browser.input(name: 'commit').exist?
|
|
85
95
|
browser.input(name: 'commit').click!
|
|
@@ -151,13 +161,19 @@ module PWN
|
|
|
151
161
|
proxy: 'optional - scheme://proxy_host:port || tor'
|
|
152
162
|
)
|
|
153
163
|
|
|
154
|
-
#
|
|
164
|
+
# Log in with manual MFA or explicitly selected YubiKey OATH.
|
|
165
|
+
# YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
|
|
166
|
+
# Configure the service for an authenticator challenge, not SMS.
|
|
167
|
+
# Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
|
|
168
|
+
# Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
|
|
169
|
+
# Reject conflicting explicit tokens before any login action.
|
|
155
170
|
#{self}.login(
|
|
156
171
|
browser_obj: 'required - browser_obj returned from #open method',
|
|
157
172
|
username: 'required - username value consumed by #login',
|
|
158
173
|
password: 'optional - passwd (will prompt if blank)',
|
|
159
174
|
mfa: 'optional - MFA / TOTP token string, or true to prompt (defaults to false)',
|
|
160
|
-
mfa_token: 'optional - MFA / TOTP token used to reach a post-authenticated state'
|
|
175
|
+
mfa_token: 'optional - MFA / TOTP token used to reach a post-authenticated state',
|
|
176
|
+
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'
|
|
161
177
|
)
|
|
162
178
|
|
|
163
179
|
# Run logout and return its result
|
data/lib/pwn/www/paypal.rb
CHANGED
|
@@ -5,6 +5,9 @@ require 'yaml'
|
|
|
5
5
|
module PWN
|
|
6
6
|
module WWW
|
|
7
7
|
# This plugin supports paypal.com actions.
|
|
8
|
+
# Its legacy login adapter selects SMS MFA; YubiKey OATH is unsupported even
|
|
9
|
+
# with confirm: true; it is rejected before login rather than submitting
|
|
10
|
+
# an authenticator code as SMS.
|
|
8
11
|
module Paypal
|
|
9
12
|
# Supported Method Parameters::
|
|
10
13
|
# browser_obj = PWN::WWW::Paypal.open(
|
|
@@ -86,10 +89,14 @@ module PWN
|
|
|
86
89
|
# browser_obj: 'required - browser_obj returned from #open method',
|
|
87
90
|
# username: 'required - username (email address)',
|
|
88
91
|
# password: 'optional - passwd (will prompt if blank)',
|
|
89
|
-
# mfa: 'optional - if true prompt for mfa token (defaults to false)'
|
|
92
|
+
# mfa: 'optional - if true prompt for mfa token (defaults to false)',
|
|
93
|
+
# yubikey: 'optional - rejected: exact OATH account Hash with :serial, :reader, :device_path, :connection, :timeout is unsupported even with confirm: true; this adapter is SMS-only; explicit mfa/mfa_token values conflict; omit to use manual SMS'
|
|
90
94
|
# )
|
|
91
95
|
|
|
92
96
|
public_class_method def self.login(opts = {})
|
|
97
|
+
PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
|
|
98
|
+
raise ArgumentError, 'PayPal YubiKey OATH login is unsupported by this SMS-only browser adapter' unless opts[:yubikey].nil?
|
|
99
|
+
|
|
93
100
|
browser_obj = opts[:browser_obj]
|
|
94
101
|
username = opts[:username].to_s.scrub.strip.chomp
|
|
95
102
|
password = opts[:password]
|
|
@@ -110,7 +117,7 @@ module PWN
|
|
|
110
117
|
browser.button(id: 'btnLogin').click!
|
|
111
118
|
|
|
112
119
|
if mfa
|
|
113
|
-
# Send code to SMS
|
|
120
|
+
# Send code to SMS; this legacy adapter does not select an OATH authenticator.
|
|
114
121
|
browser.button(id: 'btnSelectSoftToken').wait_until(&:present?).click!
|
|
115
122
|
until browser.url == 'https://www.paypal.com/myaccount/home'
|
|
116
123
|
browser.text_field(id: 'security-code').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
|
|
@@ -188,12 +195,16 @@ module PWN
|
|
|
188
195
|
mfa: 'optional - mfa value consumed by #signup'
|
|
189
196
|
)
|
|
190
197
|
|
|
191
|
-
#
|
|
198
|
+
# Log in using the existing manual SMS challenge.
|
|
199
|
+
# YubiKey OATH is unsupported even with confirm: true; this SMS-only browser adapter rejects it before login.
|
|
200
|
+
# Service-level authenticator availability does not establish a compatible adapter flow.
|
|
201
|
+
# Validate options and reject conflicting explicit tokens before any login action.
|
|
192
202
|
#{self}.login(
|
|
193
203
|
browser_obj: 'required - browser_obj returned from #open method',
|
|
194
204
|
username: 'required - username (email address)',
|
|
195
205
|
password: 'optional - passwd (will prompt if blank)',
|
|
196
|
-
mfa: 'optional - if true prompt for mfa token (defaults to false)'
|
|
206
|
+
mfa: 'optional - if true prompt for mfa token (defaults to false)',
|
|
207
|
+
yubikey: 'optional - rejected: exact OATH account Hash with :serial, :reader, :device_path, :connection, :timeout is unsupported even with confirm: true; this adapter is SMS-only; explicit mfa/mfa_token values conflict; omit to use manual SMS'
|
|
197
208
|
)
|
|
198
209
|
|
|
199
210
|
# Run logout and return its result
|
data/lib/pwn/www/synack.rb
CHANGED
|
@@ -5,6 +5,8 @@ require 'yaml'
|
|
|
5
5
|
module PWN
|
|
6
6
|
module WWW
|
|
7
7
|
# This plugin supports platform.synack.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 Synack
|
|
9
11
|
# Supported Method Parameters::
|
|
10
12
|
# browser_obj = PWN::WWW::Synack.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(name: 'password').wait_until(&:present?).set(password)
|
|
52
57
|
browser.button(class: 'btn').click!
|
|
53
58
|
|
|
54
|
-
if mfa
|
|
59
|
+
if mfa || !yubikey.nil?
|
|
55
60
|
until browser.url.include?('https://platform.synack.com')
|
|
56
|
-
browser.text_field(name: 'authy_token').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
|
|
61
|
+
browser.text_field(name: 'authy_token').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey))
|
|
57
62
|
browser.button(class: 'btn').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/twitter.rb
CHANGED
|
@@ -5,6 +5,8 @@ require 'yaml'
|
|
|
5
5
|
module PWN
|
|
6
6
|
module WWW
|
|
7
7
|
# This plugin supports twitter.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 Twitter
|
|
9
11
|
# Supported Method Parameters::
|
|
10
12
|
# browser_obj = PWN::WWW::Twitter.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]
|
|
@@ -45,17 +50,17 @@ module PWN
|
|
|
45
50
|
end
|
|
46
51
|
mfa = opts[:mfa]
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
browser.goto('https://twitter.com/login')
|
|
49
54
|
|
|
50
55
|
browser.text_field(index: 2).wait_until(&:present?).set(username)
|
|
51
56
|
browser.text_field(index: 3).wait_until(&:present?).set(password)
|
|
52
57
|
browser.button(index: 1).click!
|
|
53
58
|
|
|
54
|
-
if mfa
|
|
59
|
+
if mfa || !yubikey.nil?
|
|
55
60
|
until browser.url == 'https://twitter.com/' ||
|
|
56
61
|
browser.url == 'https://twitter.com/home'
|
|
57
62
|
|
|
58
|
-
browser.text_field(id: 'challenge_response').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
|
|
63
|
+
browser.text_field(id: 'challenge_response').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey))
|
|
59
64
|
browser.button(id: 'email_challenge_submit').click!
|
|
60
65
|
sleep 3
|
|
61
66
|
end
|
|
@@ -116,12 +121,18 @@ module PWN
|
|
|
116
121
|
proxy: 'optional - scheme://proxy_host:port || tor'
|
|
117
122
|
)
|
|
118
123
|
|
|
119
|
-
#
|
|
124
|
+
# Log in with manual MFA or explicitly selected YubiKey OATH.
|
|
125
|
+
# YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
|
|
126
|
+
# Configure the service for an authenticator challenge, not SMS.
|
|
127
|
+
# Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
|
|
128
|
+
# Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
|
|
129
|
+
# Reject conflicting explicit tokens before any login action.
|
|
120
130
|
#{self}.login(
|
|
121
131
|
browser_obj: 'required - browser_obj returned from #open method',
|
|
122
132
|
username: 'required - username value consumed by #login',
|
|
123
133
|
password: 'optional - passwd (will prompt if blank)',
|
|
124
|
-
mfa: 'optional - if true prompt for mfa token (defaults to false)'
|
|
134
|
+
mfa: 'optional - if true prompt for mfa token (defaults to false)',
|
|
135
|
+
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'
|
|
125
136
|
)
|
|
126
137
|
|
|
127
138
|
# Run logout and return its result
|
data/lib/pwn.rb
CHANGED
|
@@ -24,6 +24,7 @@ module PWN
|
|
|
24
24
|
autoload :Memory, 'pwn/memory'
|
|
25
25
|
autoload :MemoryIndex, 'pwn/memory_index'
|
|
26
26
|
autoload :Migrate, 'pwn/migrate'
|
|
27
|
+
autoload :Backups, 'pwn/backups'
|
|
27
28
|
autoload :ModuleSkills, 'pwn/module_skills'
|
|
28
29
|
autoload :Sessions, 'pwn/sessions'
|
|
29
30
|
autoload :SessionTrace, 'pwn/session_trace'
|
|
@@ -66,7 +66,7 @@ RSpec.describe 'documentation/Installation.md' do
|
|
|
66
66
|
it 'programmatic API section is truthful' do
|
|
67
67
|
expect(PWN::Setup).to respond_to(:check, :deps, :list_profiles, :pkg_manager, :migrate)
|
|
68
68
|
expect(PWN::Setup.check(io: StringIO.new).keys)
|
|
69
|
-
.to eq(%i[ok native_gems_missing toolchain_missing state pkg_manager os arch])
|
|
69
|
+
.to eq(%i[ok native_gems_missing toolchain_missing state pkg_manager os arch native_libraries_missing])
|
|
70
70
|
expect(PWN::Setup.pkg_manager.keys).to eq(%i[key install sudo])
|
|
71
71
|
expect(PWN::Setup.constants).to include(:NATIVE_GEMS, :TOOLCHAIN, :PROFILES)
|
|
72
72
|
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reference": "Yubico yubikey-manager 5.9.1 smartcard formatters; generated offline with synthetic bytes, not a hardware capture",
|
|
3
|
+
"source": "https://github.com/Yubico/yubikey-manager/blob/5.9.1/yubikit/core/smartcard/__init__.py",
|
|
4
|
+
"vectors": [
|
|
5
|
+
{
|
|
6
|
+
"name": "short-0-0",
|
|
7
|
+
"options": {
|
|
8
|
+
"cla": 0,
|
|
9
|
+
"ins": 202,
|
|
10
|
+
"p1": 1,
|
|
11
|
+
"p2": 2,
|
|
12
|
+
"data_hex": "",
|
|
13
|
+
"le": null,
|
|
14
|
+
"extended": false
|
|
15
|
+
},
|
|
16
|
+
"expected_hex": "00ca010200"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "short-0-1",
|
|
20
|
+
"options": {
|
|
21
|
+
"cla": 0,
|
|
22
|
+
"ins": 202,
|
|
23
|
+
"p1": 1,
|
|
24
|
+
"p2": 2,
|
|
25
|
+
"data_hex": "",
|
|
26
|
+
"le": 1,
|
|
27
|
+
"extended": false
|
|
28
|
+
},
|
|
29
|
+
"expected_hex": "00ca010201"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "short-1-0",
|
|
33
|
+
"options": {
|
|
34
|
+
"cla": 0,
|
|
35
|
+
"ins": 202,
|
|
36
|
+
"p1": 1,
|
|
37
|
+
"p2": 2,
|
|
38
|
+
"data_hex": "00",
|
|
39
|
+
"le": null,
|
|
40
|
+
"extended": false
|
|
41
|
+
},
|
|
42
|
+
"expected_hex": "00ca01020100"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "short-16-16",
|
|
46
|
+
"options": {
|
|
47
|
+
"cla": 0,
|
|
48
|
+
"ins": 202,
|
|
49
|
+
"p1": 1,
|
|
50
|
+
"p2": 2,
|
|
51
|
+
"data_hex": "000102030405060708090a0b0c0d0e0f",
|
|
52
|
+
"le": 16,
|
|
53
|
+
"extended": false
|
|
54
|
+
},
|
|
55
|
+
"expected_hex": "00ca010210000102030405060708090a0b0c0d0e0f10"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "short-255-0",
|
|
59
|
+
"options": {
|
|
60
|
+
"cla": 0,
|
|
61
|
+
"ins": 202,
|
|
62
|
+
"p1": 1,
|
|
63
|
+
"p2": 2,
|
|
64
|
+
"data_hex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe",
|
|
65
|
+
"le": null,
|
|
66
|
+
"extended": false
|
|
67
|
+
},
|
|
68
|
+
"expected_hex": "00ca0102ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "short-255-255",
|
|
72
|
+
"options": {
|
|
73
|
+
"cla": 0,
|
|
74
|
+
"ins": 202,
|
|
75
|
+
"p1": 1,
|
|
76
|
+
"p2": 2,
|
|
77
|
+
"data_hex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe",
|
|
78
|
+
"le": 255,
|
|
79
|
+
"extended": false
|
|
80
|
+
},
|
|
81
|
+
"expected_hex": "00ca0102ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "extended-0-0",
|
|
85
|
+
"options": {
|
|
86
|
+
"cla": 0,
|
|
87
|
+
"ins": 202,
|
|
88
|
+
"p1": 1,
|
|
89
|
+
"p2": 2,
|
|
90
|
+
"data_hex": "",
|
|
91
|
+
"le": null,
|
|
92
|
+
"extended": true
|
|
93
|
+
},
|
|
94
|
+
"expected_hex": "00ca0102"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "extended-0-256",
|
|
98
|
+
"options": {
|
|
99
|
+
"cla": 0,
|
|
100
|
+
"ins": 202,
|
|
101
|
+
"p1": 1,
|
|
102
|
+
"p2": 2,
|
|
103
|
+
"data_hex": "",
|
|
104
|
+
"le": 256,
|
|
105
|
+
"extended": true
|
|
106
|
+
},
|
|
107
|
+
"expected_hex": "00ca0102000100"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "extended-1-0",
|
|
111
|
+
"options": {
|
|
112
|
+
"cla": 0,
|
|
113
|
+
"ins": 202,
|
|
114
|
+
"p1": 1,
|
|
115
|
+
"p2": 2,
|
|
116
|
+
"data_hex": "00",
|
|
117
|
+
"le": null,
|
|
118
|
+
"extended": true
|
|
119
|
+
},
|
|
120
|
+
"expected_hex": "00ca010200000100"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "extended-255-256",
|
|
124
|
+
"options": {
|
|
125
|
+
"cla": 0,
|
|
126
|
+
"ins": 202,
|
|
127
|
+
"p1": 1,
|
|
128
|
+
"p2": 2,
|
|
129
|
+
"data_hex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe",
|
|
130
|
+
"le": 256,
|
|
131
|
+
"extended": true
|
|
132
|
+
},
|
|
133
|
+
"expected_hex": "00ca01020000ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe0100"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "extended-256-0",
|
|
137
|
+
"options": {
|
|
138
|
+
"cla": 0,
|
|
139
|
+
"ins": 202,
|
|
140
|
+
"p1": 1,
|
|
141
|
+
"p2": 2,
|
|
142
|
+
"data_hex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
|
|
143
|
+
"le": null,
|
|
144
|
+
"extended": true
|
|
145
|
+
},
|
|
146
|
+
"expected_hex": "00ca0102000100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "extended-512-1024",
|
|
150
|
+
"options": {
|
|
151
|
+
"cla": 0,
|
|
152
|
+
"ins": 202,
|
|
153
|
+
"p1": 1,
|
|
154
|
+
"p2": 2,
|
|
155
|
+
"data_hex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
|
|
156
|
+
"le": 1024,
|
|
157
|
+
"extended": true
|
|
158
|
+
},
|
|
159
|
+
"expected_hex": "00ca0102000200000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff0400"
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'tty-prompt'
|
|
5
|
+
|
|
6
|
+
RSpec.describe 'AuthenticationHelper native OATH integration' do
|
|
7
|
+
let(:account) { '-例:tester@example.invalid .+ ' }
|
|
8
|
+
let(:serial) { '00123456' }
|
|
9
|
+
let(:connection) { double('offline APDU connection', descriptor: { protocol: :apdu }, close: :closed) }
|
|
10
|
+
let(:yubikey) { { account: account, confirm: true, serial: serial, connection: connection, timeout: 5 }.freeze }
|
|
11
|
+
let(:select_apdu) { ['00a4040007a0000005272101'].pack('H*') }
|
|
12
|
+
let(:list_apdu) { ['00a1000000'].pack('H*') }
|
|
13
|
+
let(:calculate_apdu) do
|
|
14
|
+
body = [0x71, account.bytesize].pack('CC') + account.b + [0x74, 8].pack('CC') + [1].pack('Q>')
|
|
15
|
+
[0, 0xa2, 0, 1, body.bytesize].pack('C*') + body
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
before do
|
|
19
|
+
@ready = false
|
|
20
|
+
@frames = []
|
|
21
|
+
allow(Time).to receive(:now).and_return(Time.at(59))
|
|
22
|
+
allow(TTY::Prompt).to receive(:new).and_raise('unexpected interactive prompt')
|
|
23
|
+
expect(PWN::Plugins::YubiKey::Transport).not_to receive(:open)
|
|
24
|
+
expect(connection).not_to receive(:close)
|
|
25
|
+
allow(connection).to receive(:exchange) do |request|
|
|
26
|
+
expect(@ready).to be(true)
|
|
27
|
+
expect(request[:protocol]).to eq(:apdu)
|
|
28
|
+
@frames << request[:data]
|
|
29
|
+
case request[:data]
|
|
30
|
+
when select_apdu
|
|
31
|
+
[0x79, 3, 5, 7, 1, 0x71, 8].pack('C*') + 'fixture!'.b + "\x90\0".b
|
|
32
|
+
when list_apdu
|
|
33
|
+
[0x72, account.bytesize + 1, 0x21].pack('C*') + account.b + "\x90\0".b
|
|
34
|
+
when calculate_apdu
|
|
35
|
+
[0x76, 5, 8, 12_345].pack('CCCN') + "\x90\0".b
|
|
36
|
+
else
|
|
37
|
+
raise 'Unexpected APDU in offline OATH fixture'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'runs the real helper, root facade, OATH.code and Protocol against a fake APDU transport' do
|
|
43
|
+
@ready = true
|
|
44
|
+
options = { yubikey: yubikey }.freeze
|
|
45
|
+
expect do
|
|
46
|
+
expect(PWN::Plugins::AuthenticationHelper.mfa(options)).to eq('00012345')
|
|
47
|
+
end.not_to output.to_stdout
|
|
48
|
+
expect(@frames).to eq([select_apdu, list_apdu, calculate_apdu])
|
|
49
|
+
expect(options[:yubikey]).to equal(yubikey)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
[nil, false, 'true'].each do |confirm|
|
|
53
|
+
it "rejects unconfirmed OATH #{confirm.inspect} before any APDU or prompt" do
|
|
54
|
+
expect do
|
|
55
|
+
PWN::Plugins::AuthenticationHelper.mfa(yubikey: yubikey.merge(confirm: confirm).freeze)
|
|
56
|
+
end.to raise_error(ArgumentError, /confirm: true/)
|
|
57
|
+
expect(@frames).to be_empty
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'propagates exact-account errors without calculation, credential repair or manual fallback' do
|
|
62
|
+
@ready = true
|
|
63
|
+
expect do
|
|
64
|
+
PWN::Plugins::AuthenticationHelper.mfa(yubikey: yubikey.merge(account: 'missing@example.invalid'))
|
|
65
|
+
end.to raise_error(PWN::Plugins::YubiKey::ResponseError, /exact account/)
|
|
66
|
+
expect(@frames).to eq([select_apdu, list_apdu])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'through the real GitHub login and helper' do
|
|
70
|
+
let(:browser) { double('offline browser') }
|
|
71
|
+
let(:browser_obj) { { browser: browser } }
|
|
72
|
+
let(:credential_field) { double('offline credential field', set: nil, present?: true) }
|
|
73
|
+
let(:otp_field) { double('offline OATH field', present?: true, exist?: true) }
|
|
74
|
+
let(:submit) { double('offline submit', exist?: true) }
|
|
75
|
+
let(:login_opts) { { browser_obj: browser_obj, username: 'tester@example.invalid', password: 'fake-password', mfa: false, yubikey: yubikey } }
|
|
76
|
+
|
|
77
|
+
before do
|
|
78
|
+
@authenticated = false
|
|
79
|
+
@otp_written = false
|
|
80
|
+
allow(PWN::WWW::GitHub).to receive(:sleep)
|
|
81
|
+
allow(browser).to receive(:goto)
|
|
82
|
+
allow(browser).to receive(:url) { @authenticated ? 'https://github.com/' : 'https://github.com/sessions/two-factor' }
|
|
83
|
+
allow(browser).to receive(:text_field).and_return(credential_field)
|
|
84
|
+
allow(credential_field).to receive(:wait_until).and_return(credential_field)
|
|
85
|
+
allow(browser).to receive(:text_field).with(name: 'app_otp').and_return(otp_field)
|
|
86
|
+
allow(otp_field).to receive(:wait_until) do
|
|
87
|
+
expect(@frames).to be_empty
|
|
88
|
+
@ready = true
|
|
89
|
+
otp_field
|
|
90
|
+
end
|
|
91
|
+
allow(browser).to receive(:input).with(name: 'commit').and_return(submit)
|
|
92
|
+
allow(submit).to receive(:click!) { @authenticated = true if @otp_written }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'types actual native OATH digits only after the authenticator field is ready' do
|
|
96
|
+
expect(otp_field).to receive(:set).with('00012345') { @otp_written = true }
|
|
97
|
+
expect(PWN::WWW::GitHub.login(login_opts)).to equal(browser_obj)
|
|
98
|
+
expect(@frames).to eq([select_apdu, list_apdu, calculate_apdu])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'does not touch a token for an already authenticated redirect' do
|
|
102
|
+
@authenticated = true
|
|
103
|
+
expect(otp_field).not_to receive(:set)
|
|
104
|
+
expect(PWN::WWW::GitHub.login(login_opts)).to equal(browser_obj)
|
|
105
|
+
expect(@frames).to be_empty
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'rejects absent consent before asking for a password or navigating' do
|
|
109
|
+
expect(PWN::Plugins::AuthenticationHelper).not_to receive(:mask_password)
|
|
110
|
+
expect(browser).not_to receive(:goto)
|
|
111
|
+
expect do
|
|
112
|
+
PWN::WWW::GitHub.login(login_opts.except(:password).merge(yubikey: yubikey.except(:confirm)))
|
|
113
|
+
end.to raise_error(ArgumentError, /confirm: true/)
|
|
114
|
+
expect(@frames).to be_empty
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'open3'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'json'
|
|
8
|
+
|
|
9
|
+
RSpec.describe 'controller spill boundary through actual Dispatch' do
|
|
10
|
+
%w[checkpoint transcript].each do |mode|
|
|
11
|
+
it "does not create source files when a worker redirects the parent #{mode} spill path" do
|
|
12
|
+
Dir.mktmpdir('spill-boundary-') do |root|
|
|
13
|
+
source = File.join(root, 'source')
|
|
14
|
+
home = File.join(root, 'home')
|
|
15
|
+
work = File.join(root, 'work')
|
|
16
|
+
FileUtils.mkdir_p([source, home, work])
|
|
17
|
+
fixture = File.expand_path('../support/harness/controller_spill.rb', __dir__)
|
|
18
|
+
stdout, stderr, status = Open3.capture3({ 'HOME' => home }, RbConfig.ruby, '-I', File.expand_path('../../lib', __dir__), fixture, source, work, mode, chdir: work)
|
|
19
|
+
expect(status.success?).to be(true), stderr
|
|
20
|
+
row = JSON.parse(stdout)
|
|
21
|
+
expect(row['changed']).to be(false), row.inspect
|
|
22
|
+
expect(row['dispatch_success']).to be(false)
|
|
23
|
+
expect(row['dispatch_error']).to include('Permission denied')
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
%w[ordinary aliased].each do |mode|
|
|
29
|
+
it "denies the #{mode} controller-state inventory while preserving workspace output" do
|
|
30
|
+
Dir.mktmpdir('controller-inventory-') do |root|
|
|
31
|
+
source = File.join(root, 'source')
|
|
32
|
+
home = File.join(root, 'home')
|
|
33
|
+
work = File.join(root, 'work')
|
|
34
|
+
FileUtils.mkdir_p([source, home, work])
|
|
35
|
+
fixture = File.expand_path('../support/harness/controller_state.rb', __dir__)
|
|
36
|
+
stdout, stderr, status = Open3.capture3({ 'HOME' => home }, RbConfig.ruby, '-I', File.expand_path('../../lib', __dir__), fixture, source, work, mode, chdir: work)
|
|
37
|
+
expect(status.success?).to be(true), stderr
|
|
38
|
+
row = JSON.parse(stdout)
|
|
39
|
+
expect(row.dig('result', 'success')).to be(true), row.inspect
|
|
40
|
+
attempts = row.dig('result', 'result')
|
|
41
|
+
expect(attempts.length).to eq(29)
|
|
42
|
+
expect(attempts.values.uniq).to eq(['denied']), attempts.inspect
|
|
43
|
+
expect(row['source_unchanged']).to be(true)
|
|
44
|
+
expect(row['output']).to eq('output remains writable')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|