masterkale/simplewebauthn · Security
SimpleWebAuthn: Registration verification does not sufficiently ensure that attestation certificates chain to a trust anchor
- GHSA ID
- GHSA-6hxq-p678-4hr2
- Severity
- low
- Published
- Updated
- CVSS v4
- 2 · CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:A/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Description
Summary
validateCertificatePath() does not verify that an attestation's certificate chain actually terminates at a configured trust anchor. When walking the chain it stops at the first self-signed certificate it finds (which could be user-supplied), and exits early.
This happens before the configured Apple/Google/etc trust anchor (which is concatenated to the end of the chain) is reached.
A user can therefore register a credential and have the server accept it as if it were backed by a genuine Apple / Android SafetyNet / Yubikey / etc.
Details
packages/server/src/helpers/validateCertificatePath.ts:
The configured trust anchor is appended to the end of the untrusted chain (line 83):
const x5cWithTrustAnchor = x5cCertsParsed.concat([anchor]);
The walk then verifies each cert was signed by the next, but breaks on the first self-signed cert (lines 104–116):
if (issuer.subject === issuer.issuer) {
// Root cert detected, make sure it signed itself
const issuerSignedIssuer = await issuer.verify(
{ publicKey: issuer.publicKey, signatureOnly: true },
WebCrypto,
);
if (!issuerSignedIssuer) {
throw new InvalidSubjectAndIssuer();
}
break; // <-- exits before the appended trust anchor is ever checked if user supplied self-signed cert comes first
}
The success condition is therefore "the certs form an internally-consistent chain ending in some self-signed cert" Rather than "the chain terminates at one of the configured trust anchors."
Exploit shape
attacker sends: x5c = [ forgedLeaf (signed by attacker root),
attackerSelfSignedRoot ]
library builds: [ forgedLeaf, attackerSelfSignedRoot, <configured Apple/Google/etc root> ]
walk: forgedLeaf -> attackerSelfSignedRoot (verifies, attacker controls both)
attackerSelfSignedRoot is self-signed -> break
attackerSelfSignedRoot -> configured root (NEVER CHECKED)
return true. The configured anchor never gets checked.
As far as observed, all attestation enforcement uses validateCertificatePath when using MDS etc.
CWEs
- CWE-295 · Improper Certificate Validation
- CWE-296 · Improper Following of a Certificate's Chain of Trust
References
- https://github.com/MasterKale/SimpleWebAuthn/security/advisories/GHSA-6hxq-p678-4hr2 ↗
- https://github.com/MasterKale/SimpleWebAuthn/commit/67a41fed3dfd96cab1dcf414f6d1792a72e06e35 ↗
- https://github.com/MasterKale/SimpleWebAuthn/commit/8a53d70f42bcbb7c744ef1b90e6db35bc4b26d06 ↗
- https://github.com/MasterKale/SimpleWebAuthn/commit/dd0d73c716a528e6645efafbd43a972b64df71f9 ↗
- https://github.com/MasterKale/SimpleWebAuthn/releases/tag/v13.3.2 ↗
- https://github.com/advisories/GHSA-6hxq-p678-4hr2 ↗