Safeguard
Vulnerability Analysis

OpenSSH agent forwarding RCE (CVE-2016-10009)

A malicious or compromised SSH server could abuse forwarded ssh-agent connections to load arbitrary PKCS#11 modules and run code on the client.

James
Principal Security Architect
7 min read

A malicious or compromised SSH server could hijack a forwarded ssh-agent connection to force the client's agent into loading an attacker-supplied PKCS#11 shared library — turning a routine SSH login into remote code execution on the client's own machine. Tracked as CVE-2016-10009, this OpenSSH agent-forwarding flaw sat in production fleets for years before a Qualys security audit surfaced it, and it remains a useful case study for any team still running legacy ssh-agent binaries or permissive ForwardAgent policies today.

What Went Wrong

OpenSSH's ssh-agent supports PKCS#11 tokens — hardware security modules and smartcards — by dynamically loading a PKCS#11 provider library (a .so shared object) named by the caller. That "load this library" request travels over the same agent protocol that gets forwarded across SSH connections when a client enables ForwardAgent (or connects with ssh -A).

The vulnerable code did not restrict where that PKCS#11 library path could point, nor confirm the request originated from a trusted local process. Because a forwarded agent socket is, by design, reachable from the remote host the user is logged into, a hostile or already-compromised SSH server could send a specially crafted agent request asking the client's forwarded ssh-agent to load a PKCS#11 module from an arbitrary path — including one it planted on a shared filesystem or one referencing a network path. Once loaded, that shared library executes with the privileges of the user running ssh-agent on their workstation or jump host, giving the attacker code execution on the client side, not the server. This inversion is what makes the bug notable: agent forwarding is normally trusted "one-way," and this flaw broke that trust boundary.

The same OpenSSH security audit that turned up CVE-2016-10009 also produced three sibling CVEs (CVE-2016-10010, CVE-2016-10011, CVE-2016-10012) covering a privilege-separation monitor bypass, a pre-auth compression heap issue, and moduli/sandbox weaknesses, respectively. They shipped together in one release, which is worth knowing if you're reconciling changelogs or vulnerability-scanner output that lumps them into a single "OpenSSH 7.4 security fixes" bucket.

Affected Versions and Components

  • Component: ssh-agent (and the ssh-pkcs11-helper process it spawns), specifically the PKCS#11 provider-loading path.
  • Affected versions: All OpenSSH releases prior to 7.4 that were built with PKCS#11 support enabled (--enable-pkcs11 at compile time). PKCS#11 support has shipped as a portable OpenSSH build option since OpenSSH 5.4 (2010), so the exposure window spans roughly six years of releases across most major Linux distributions and BSD variants that enabled the feature.
  • Trigger condition: The client must have agent forwarding active (ForwardAgent yes in ssh_config, a per-connection -A flag, or an equivalent agent socket exposed to the remote session) and must connect to a server the attacker controls or has compromised. Users who never enable agent forwarding are not exposed to this specific vector, though the underlying missing path validation is still worth patching.
  • Fixed in: OpenSSH 7.4, released December 19, 2016, which introduced an allowlist restricting PKCS#11 (and later FIDO/security-key) module loading to a small set of trusted paths, overridable only with an explicit -P flag passed to ssh-agent.

Severity, Exploitability, and KEV Status

NVD's legacy CVSSv2 vector for CVE-2016-10009 scores it 9.3 (Critical)AV:N/AC:M/Au:N/C:C/I:C/A:C — reflecting unauthenticated, network-reachable, full compromise of confidentiality, integrity, and availability once the prerequisite (agent forwarding to an attacker-controlled host) is met. Later CVSSv3-style rescoring by various vendors generally lands in the high-severity range as well, since the "attack complexity" reduction for needing a specific configuration doesn't offset the unauthenticated, full-impact outcome.

EPSS, which wasn't introduced until 2021, tends to rate long-tail 2016-era CVEs like this one modestly despite the high CVSS score — exploitation requires a victim who both enables agent forwarding and connects outbound to infrastructure the attacker controls, which is a narrower real-world path than, say, an unauthenticated web RCE. That said, low EPSS is not the same as low relevance: this exact pattern — trusted client tooling extending trust to a remote peer over a forwarded socket — recurs in modern supply-chain incidents involving CI runners, build agents, and jump-host architectures. As of this writing, CVE-2016-10009 does not appear on CISA's Known Exploited Vulnerabilities (KEV) catalog, but the absence of confirmed in-the-wild exploitation doesn't mean the exposure is gone — plenty of appliances and embedded Linux images still ship pre-7.4 OpenSSH builds and never revisit ssh_config defaults.

Disclosure Timeline

  • 2010: OpenSSH portable adds optional PKCS#11 support (--enable-pkcs11), introducing the code path that would later prove vulnerable.
  • 2016: The Qualys Security Team conducts a source-code audit of OpenSSH and identifies the PKCS#11 path-validation gap in ssh-agent, alongside the three related issues mentioned above.
  • December 19, 2016: OpenSSH 7.4 ships, fixing the flaw by restricting PKCS#11/provider loading to an allowlisted set of paths and adding the -P override flag for administrators who need to widen it deliberately.
  • Early 2017: CVE-2016-10009 is formally published in the NVD, retroactively assigned within the 2016 CVE block per that year's numbering, alongside CVE-2016-10010 through -10012.
  • Ongoing: Distribution maintainers backport the fix into supported OpenSSH package branches; the flaw becomes a standard checklist item in SSH hardening guides and CIS benchmarks referencing agent-forwarding risk.

Remediation

  1. Patch to OpenSSH 7.4 or later. This is the definitive fix — confirm with ssh -V (client) and sshd -V (server) that binaries in your fleet are current, and check that any bundled/embedded OpenSSH inside appliances, containers base images, or vendor firmware has also been rebuilt against a fixed version.
  2. Disable agent forwarding by default. Set ForwardAgent no in ssh_config / /etc/ssh/ssh_config unless a specific workflow genuinely requires it, and audit for scripts or wrapper aliases that silently pass -A.
  3. Prefer ProxyJump/ProxyCommand over agent forwarding for jump-host and bastion access patterns — it gets you through an intermediary host without exposing your local agent socket to it at all.
  4. Restrict PKCS#11/provider paths explicitly. Even on patched versions, verify the allowlist behavior matches your intent, and never run ssh-agent -P with a permissive path expression "just to make something work."
  5. Use confirmation-gated keys. Load sensitive keys with ssh-add -c so any agent operation — including from a forwarded connection — requires an interactive confirmation, which blunts a wide class of agent-abuse techniques beyond just this CVE.
  6. Inventory and monitor OpenSSH versions continuously. Legacy or embedded systems (network appliances, IoT/OT gear, older CI images) are the most common place stale, pre-7.4 builds persist long after servers and workstations get patched.
  7. Segment and least-privilege your jump hosts. If a host is compromised, limiting what an attacker on that box can reach — and whose forwarded agents transit it — reduces blast radius even if a future analogous flaw surfaces.

How Safeguard Helps

CVE-2016-10009 is a textbook example of a vulnerability whose real-world risk depends entirely on configuration and reachability, not just the presence of a vulnerable binary — exactly the gap Safeguard is built to close. Our reachability analysis flags whether a given asset actually has PKCS#11-enabled ssh-agent builds combined with agent-forwarding configurations turned on, so teams can triage the handful of genuinely exposed hosts instead of chasing every machine that merely has an old OpenSSH package installed. Griffin AI correlates that exposure against your deployment topology — jump hosts, CI runners, bastions — to explain in plain language which forwarded-agent paths a malicious server could realistically abuse. Continuous SBOM generation and ingestion give you a live inventory of OpenSSH versions across containers, appliances, and golden images, closing the "we forgot the embedded firmware" blind spot that lets CVEs like this linger for years. And where a fix is straightforward, Safeguard opens auto-fix pull requests to bump OpenSSH to a patched release or tighten ssh_config/sshd_config defaults, turning a hardening recommendation into a merged change instead of a backlog item.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.