Aug 7, 2026·6 min read·1 visit
Craft CMS native Passkey authentication fails to securely validate challenge options and discard/update the signature counter, allowing complete authentication bypass via captured request replay.
GHSA-WG23-69C2-GJC8 is a critical security vulnerability discovered in the native Passkey (WebAuthn) login implementation of Craft CMS. The vulnerability allows an attacker to bypass WebAuthn's core cryptographic challenge-response and signature-counter replay protections. By intercepting a single successful passkey login request body, an attacker can replay the identical request payload to generate additional authenticated active sessions, resulting in complete account takeover.
Craft CMS versions 5.0.0-RC1 through 5.10.4.1 contain an authentication bypass vulnerability within the native Passkey (WebAuthn) login implementation. This flaw allows an attacker to reuse a captured WebAuthn authentication assertion to establish unauthorized sessions. The vulnerability represents a failure to enforce core WebAuthn security specifications on the server side.
The vulnerable component resides in the core authentication handling logic, specifically within the controller and service classes responsible for validating WebAuthn assertions. Under normal conditions, WebAuthn relies on strict cryptographic challenge-response mechanisms and monotonically increasing counters to prevent replay attacks. These protections ensure that each login assertion is unique and used exactly once.
Due to implementation errors in Craft CMS, both the session challenge validation and the signature counter tracking were rendered ineffective. Consequently, any attacker capable of intercepting a valid passkey authentication request can replay the payload to achieve full account takeover. The flaw requires no special privileges and bypasses standard multi-factor authentication controls.
The vulnerability is a classic representation of CWE-294 (Authentication Bypass by Capture-replay) and is caused by two distinct software design failures. First, the application's passkey login controller deserialized the cryptographic challenge options directly from the client's unauthenticated HTTP request body. It did not verify them against a server-side session variable.
By trusting the client-supplied requestOptions parameter, the application allowed an attacker to supply a historic challenge value alongside a historic signature. The server validated the signature against the self-supplied challenge within the request, neutralizing the security guarantee of the challenge-response mechanism. This eliminated the temporal uniqueness constraint required by WebAuthn.
Second, the application failed to persist the updated signature counter (signCount) returned by the WebAuthn validation library. While the library successfully verified the assertion and returned an updated credential source object with an incremented counter, the application discarded this returned object. As a result, the database copy of the signature counter remained permanently static, rendering clone detection and replay prevention checks non-functional.
The execution flow for the vulnerability involves the controller extracting client parameters and passing them directly to the verification service. The diagram below illustrates how the validation checks succeed despite using replayed data.
The vulnerable controller code in src/controllers/UsersController.php extracted the challenge options directly from the POST request body:
// Vulnerable: Reads challenge options from client request
$requestOptions = $this->request->getRequiredBodyParam('requestOptions');The patch resolves this by reading the challenge parameters directly from the user's secure session and deleting the variable to prevent subsequent reuse:
// Patched: Extracts and removes options from server-side session
$requestOptions = SessionHelper::remove(Craft::$app->getAuth()->passkeyRequestOptionsParam);
if (!$requestOptions) {
return $this->asFailure(Craft::t('app', 'Passkey authentication failed.'));
}In src/services/Auth.php, the verification function initially verified the key but failed to capture or persist the updated key source:
// Vulnerable: Output of check() containing updated counter is discarded
$this->webauthnServer()->getAuthenticatorAssertionResponseValidator()->check(
$publicKeyCredentialSource,
$authenticatorAssertionResponse,
$publicKeyCredentialRequestOptions,
Craft::$app->getRequest()->getHostName(),
$userEntity->id,
);The patched service captures the output and caches it in the session temporarily:
// Patched: Captures and caches the updated credential source
$updatedPublicKeyCredentialSource = $this->webauthnServer()->getAuthenticatorAssertionResponseValidator()->check(
$publicKeyCredentialSource,
$authenticatorAssertionResponse,
$publicKeyCredentialRequestOptions,
Craft::$app->getRequest()->getHostName(),
$userEntity->id,
);
SessionHelper::set($this->passkeyCredSourceParam, $updatedPublicKeyCredentialSource);Finally, src/elements/User.php retrieves the updated source from the session and commits the new signature counter to the database:
// Patched: Commits the new counter to the repository
$updatedPublicKeyCredentialSource = Session::remove($authService->passkeyCredSourceParam);
$authService->webauthnServer()->getCredentialRepository()->saveCredentialSource($updatedPublicKeyCredentialSource);Exploiting this vulnerability requires the interception of a single valid Passkey authentication payload. An attacker must monitor or retrieve the raw HTTP request body transmitted during a legitimate user's login sequence to /actions/users/login-with-passkey. This can be achieved through network monitoring, server log exposure, or client-side compromise.
Once the payload is captured, the attacker can replay the identical JSON request body to the target application. Because the application uses the request's own client-supplied challenge options, the cryptographic verification succeeds. The server assumes the assertion is fresh because it is matched against the accompanying replayed challenge.
Because the application does not persist the updated signature counter, the server-side validator compares the replayed counter against a static database value. This allows the request to bypass both WebAuthn challenge verification and signature-counter freshness checks. The server then generates a new session cookie for the attacker, resulting in immediate session takeover.
The impact of this vulnerability is classified as critical, carrying a CVSS v4 score of 9.1. An attacker who successfully replays a captured assertion gains full access to the target user account, which may include administrative privileges depending on the compromised user's role. This bypasses the multi-factor authentication guarantees typically provided by physical security keys.
This vulnerability undermines the primary security assumption of WebAuthn and Passkeys, which are designed to resist replay and credential-harvesting attacks. If passkey authentication is the primary factor of authentication, the bypass allows direct administrative access to the Craft CMS control panel.
With administrative access, an attacker can modify template files, execute arbitrary code via server configuration parameters, extract database contents, or completely deface the hosted web application. The lack of a corresponding CVE does not minimize the severity of this flaw, as it represents a complete authentication failure within a core identity component.
The primary remediation path is upgrading the Craft CMS framework to version 5.10.5 or later. Administrators can perform this update via Composer by running the command composer update craftcms/cms --with-dependencies inside their root project directory.
If an immediate upgrade is not possible, administrators should disable the native Passkey (WebAuthn) login feature within the application's authentication configuration. This restricts users to standard password-based login or alternative multi-factor authentication methods that do not rely on the vulnerable WebAuthn wrapper.
Security teams can detect potential historical exploitation by querying the database table webauthnrecords. A signature counter that remains static across multiple successful login events for a single passkey indicates that the vulnerable code path was executed without updating the state.
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
craftcms/cms Craft CMS | >= 5.0.0-RC1, < 5.10.5 | 5.10.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-294 |
| Attack Vector | Network (AV:N) |
| CVSS v4 Score | 9.1 (Critical) |
| Exploit Status | PoC / Known Mechanics |
| Impact | Authentication Bypass / Account Takeover |
The application receives sensitive authentication credentials and accepts them on subsequent requests without sufficient validation of freshness or uniqueness.
An unsafe option guard bypass vulnerability exists in GitPython before version 3.1.58. When keyword arguments are passed to Git commands with split_single_char_options=False, GitPython's argument validation helper fails to inspect the combined short-option value. This discrepancy allows short-option token smuggling or clustering manipulation, enabling remote attackers to bypass option blocklists and execute arbitrary system commands.
An architectural flaw in the Footnote extension of league/commonmark allows unauthenticated remote attackers to trigger severe denial of service conditions through algorithmic complexity exploitation (O(N^2) CPU and memory exhaustion) and path-delimiter injection leading to unexpected application-level crashes.
An algorithmic complexity vulnerability in the UniqueSlugNormalizer component of the league/commonmark PHP library allows unauthenticated remote attackers to trigger severe CPU resource consumption and Denial of Service (DoS) by submitting a Markdown document containing a high volume of duplicate headings. The slug generation loop resets its sequential search index back to 1 for every collision, resulting in a quadratic execution path. This flaw affects versions from 2.0.0-beta1 up to and including 2.8.3, and is patched in version 2.9.0.
A Denial of Service vulnerability exists in the league/commonmark package for PHP when using the XML rendering subsystem. Due to unconstrained indentation based on AST depth, rendering deeply nested elements leads to asymmetric resource consumption (quadratic output size complexity).
Craft CMS contains an authenticated remote code execution vulnerability due to a sanitization bypass in its search condition configuration parser. An attacker with access to the control panel can inject unsafe Yii2 behavior configurations wrapped inside a JSON-encoded string. When decoded and merged by the application, these keys bypass the global config cleanse filter and are evaluated by the Yii2 component factory, leading to arbitrary code execution.
An authenticated remote code execution vulnerability exists in Craft CMS due to a flaw in how the Twig template sandbox policy handles class-level allowlists. Prior to the fix, the security policy allowed arbitrary public methods from parent classes of allowed interfaces, allowing authenticated attackers to invoke Yii component methods such as attachBehavior on element models to load arbitrary classes and execute system commands.