OpenSSH Forwarded-Agent Lock/Unlock State Confusion → Unauthorized PKCS#11 Provider Load (No CVE)
by bikini (exploitarium) · 2026-07-12
- Severity
- High
- Category
- network
- Affected product
- OpenSSH portable — ssh, sshd, ssh-agent
- Affected versions
- Portable tags V_9_3_P2 through V_10_4_P1 (provider restriction logic present since 9.3p2; failed-session-bind tracking added in 9.6p1, still reachable behind the locked gate through 10.4p1)
- Disclosed
- 2026-07-12
- Patch status
- unpatched
Tags
References
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-12 |
| Last Updated | 2026-07-12 |
| Author / Researcher | bikini (exploitarium) |
| CVE / Advisory | N/A |
| Category | network |
| Severity | High |
| CVSS Score | N/A (no official score published) — local-state-confusion bug reachable only through an established, agent-forwarded SSH session |
| Status | PoC — reproducible against a signed, checksum/signature-verified stock OpenSSH 10.4p1 build; no weaponized payload beyond starting an allowed PKCS#11 provider |
| Tags | openssh, ssh-agent, agent-forwarding, pkcs11, race-condition, state-confusion, no-cve, local-provider-abuse |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | OpenSSH portable — ssh, sshd, ssh-agent |
| Versions Affected | Portable tags V_9_3_P2 through V_10_4_P1 (provider restriction logic present since 9.3p2; failed-session-bind tracking added in 9.6p1, still reachable behind the locked gate through 10.4p1) |
| Language / Platform | C, Linux (tested on Ubuntu 24.04.4 LTS x86-64 under WSL2) |
| Authentication Required | Yes — the attacker must be (or control) the remote SSH server/session the victim connects to with agent forwarding enabled |
| Network Access Required | Yes — requires an active ssh -A (agent-forwarded) connection between victim client and the attacker-influenced remote side |
Summary
OpenSSH’s ssh-agent supports being locked with a password, during which it is supposed to refuse essentially all requests — including the session-bind@openssh.com extension that a forwarded agent connection uses to record which remote session it belongs to. The bug is a state-tracking gap: when the agent is locked, extension requests (including session-bind) are rejected at a global locked-check before the agent ever records that the bind was attempted or failed. The SSH client, meanwhile, still creates the forwarded agent channel even though the bind was refused. Once the agent is later unlocked through a separate, legitimate local connection, the persistent forwarded socket has no record of ever having attempted (and failed) a session bind — so the classification function socket_is_remote() incorrectly treats it as a local connection. A request on that forwarded socket to add a PKCS#11 provider (SSH_AGENTC_ADD_SMARTCARD_KEY) is then processed as if it came from a trusted local caller, bypassing the remote-provider restriction that OpenSSH added specifically to stop forwarded/remote agent connections from loading arbitrary PKCS#11 modules.
Vulnerability Details
Root Cause
The flaw spans OpenSSH’s client and agent:
clientloop.c’sclient_request_agent()creates a forwarded agent channel even whenssh_agent_bind_hostkey()reports the session bind was refused.ssh-agent.c’sprocess_message()applies a global “locked” check before its main request dispatcher — while locked, every extension request (includingsession-bind@openssh.com) is discarded beforeprocess_extension()can even parse which extension it was.- Because the bind request never reaches
process_ext_session_bind(), thesession_bind_attemptedmarker is never set for that forwarded socket. - After a separate, legitimate local connection unlocks the agent,
socket_is_remote()finds neither a recorded session identifier nor a failed-bind marker on the forwarded socket, so it (incorrectly) classifies it as local. process_add_smartcard_key()— which is supposed to reject provider-add requests from remote/forwarded sockets — evaluates its remote-socket check as false and forwards the request topkcs11_add_provider(), which startsssh-pkcs11-helperand loads the specified PKCS#11 module.
The same flawed classification is also consulted by the external-security-key provider path (process_add_identity()), not just the smartcard path.
Attack Vector
- Victim connects with
ssh -A(agent forwarding) to a server the attacker controls or has compromised, while their localssh-agenthappens to be locked. - The malicious/compromised server side (or a process on it) holds the forwarded
SSH_AUTH_SOCKconnection open. The locked agent refuses thesession-bind@openssh.comextension, but the SSH client still creates the forwarded agent channel regardless. - At some later point the victim unlocks their local agent through an unrelated, legitimate local action.
- The attacker-influenced remote side sends
SSH_AGENTC_ADD_SMARTCARD_KEYover the still-open forwarded socket, specifying a PKCS#11 module path that’s on the local provider allowlist. - Because the forwarded socket was never marked as having attempted (and failed) a session bind, the agent treats the request as local and initializes the specified provider — something that should be restricted to genuinely local callers.
Impact
A remote party the victim connects to with agent forwarding enabled can cause the victim’s local ssh-agent to load and initialize a PKCS#11 provider module of the attacker’s choosing (constrained to whatever’s on the local provider allowlist) without the provider-restriction check that OpenSSH specifically added to prevent forwarded/remote connections from doing this. Depending on what provider modules are permitted/present on the victim’s system, this could be leveraged to interact with security tokens or trigger provider-specific behavior the victim did not intend to expose to a remote party.
Environment / Lab Setup
Target: Stock, unmodified OpenSSH portable 10.4p1 (release archive checksum
and GPG signature verified against the official OpenSSH release key
7168 B983 815A 5EEF 59A4 ADFD 2A3F 414E 7360 60BA)
Platform: Ubuntu 24.04.4 LTS x86-64 (tested under WSL2), OpenSSL 3.0.13
Attacker: Python 3, standard library only (raw ssh-agent protocol socket I/O)
Tools: A stock OpenSSH install (ssh, sshd, ssh-agent, ssh-keygen) under one
prefix; a PKCS#11 module on the agent's provider allowlist
(p11-kit-trust.so used in the original replay)
Setup Steps
| |
Proof of Concept
See
poc.py,run.sh, andevidence/stock-openssh-10.4p1.txtin this folder — mirrored from bikini/exploitarium. Verified before ingestion:poc.pyis pure Python standard library implementing the raw ssh-agent wire protocol (length-prefixed request/response, agent lock/unlock,SSH_AGENTC_ADD_SMARTCARD_KEY) — no obfuscation, no unrelated network calls.run.shbuilds an entirely local, loopback-only lab (temporary Ed25519 keys, isolatedsshd/ssh-agentinstances, no external targets) and its cleanup trap removes all spawned processes and temp files. The included evidence file’s runner output and diagnostic log excerpts match the README’s documented reproduction exactly.
Step-by-Step Reproduction
run.sh automates the full sequence:
- Generate temporary Ed25519 host and client keys.
- Start an isolated
ssh-agenton a private Unix socket. - Start a loopback-only
sshdwith public-key auth and agent forwarding enabled. - Lock the agent (
poc.py control lock). - Connect with
ssh -Aand runpoc.py probeas the “remote” process, holding the forwarded agent socket open. - Unlock the agent through a separate local connection (
poc.py control unlock). - Send
SSH_AGENTC_ADD_SMARTCARD_KEYover the still-open forwarded socket. - Verify via client/agent debug logs that the bind was refused while locked, the forwarded channel was still created, and the provider was nonetheless loaded and initialized post-unlock.
Exploit Code
| |
Expected Output
lock_reply_type=6
unlock_reply_type=6
forwarded_socket_connected=true
provider_reply_type=5
target=OpenSSH_10.4p1, OpenSSL 3.0.13 30 Jan 2024
session_bind_refused_while_locked=true
forwarded_channel_opened=true
provider_helper_started=true
provider_initialized=true
reproduced=true
Detection & Indicators of Compromise
Remediation
| Action | Detail |
|---|---|
| Patch | None available at time of ingestion — no CVE or vendor advisory found for this specific finding. |
| Workaround | Avoid combining ssh -A (agent forwarding) with an unlocked-after-use workflow when connecting to untrusted or lower-trust hosts; consider ssh -a (no forwarding) or per-host ForwardAgent no for hosts that don’t need it. Restrict the PKCS#11/security-key provider allowlist to the minimum necessary. |
| Repair direction (per researcher) | Process session-bind@openssh.com while the agent is locked so per-socket security state is recorded before any later unlock, rather than discarding the extension request entirely at the locked gate. |
References
- Source repository — bikini/exploitarium
- OpenSSH portable 10.4p1 source archive
- OpenSSH agent restriction design
- ssh-agent manual
Notes
Surfaced by checking bikini/exploitarium directly at the user’s request for newest entries not yet in this archive — the rest of that repository’s contents (37 of 39 research folders) were already covered by an earlier 2026-07-03 ingestion sweep from the same source. This was the newer of the two genuinely new entries found (both dated 2026-07-11); the other, a Firefox 152.0.5 drive-by remote-code-execution chain, was deliberately not ingested — see the companion decision recorded in this session: unlike this OpenSSH finding (which requires an already-established, agent-forwarded connection to an attacker-influenced host) and unlike every other no-CVE entry already in this archive (which had defined vendor-disclosure timelines before publication), that Firefox chain is a fully working, unpatched, single-URL-visit RCE against current stock Firefox with no evidence of vendor coordination — archiving it would mean amplifying a live weapon against currently-unpatched users rather than documenting research about a resolved issue.
No CVE has been assigned to this finding as of ingestion. The repository’s own README states research is published without prior vendor notification (“At the time I post these, none have been reported”), which is a real disclosure-ethics concern worth being aware of for this source generally — but this specific finding’s narrower exploitability (requires an existing agent-forwarded SSH session to an attacker-influenced host, not a passive/drive-by vector) keeps it in line with the archive’s existing risk bar, unlike the Firefox chain.
| |