Craft CMS's race condition in Token Service potentially allows for token usage greater than the token limit
Description
Craft is a content management system (CMS). In versions 4.5.0-RC1 through 4.16.18 and 5.0.0-RC1 through 5.8.22, a Time-of-Check-Time-of-Use (TOCTOU) race condition exists in Craft CMS’s token validation service for tokens that explicitly set a limited usage. The getTokenRoute() method reads a token’s usage count, checks if it’s within limits, then updates the database in separate non-atomic operations. By sending concurrent requests, an attacker can use a single-use impersonation token multiple times before the database update completes. To make this work, an attacker needs to obtain a valid user account impersonation URL with a non-expired token via some other means and exploit a race condition while bypassing any rate-limiting rules in place. For this to be a privilege escalation, the impersonation URL must include a token for a user account with more permissions than the current user. Versions 4.16.19 and 5.8.23 patch the issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A TOCTOU race condition in Craft CMS token validation allows repeated use of single-use impersonation tokens, potentially leading to privilege escalation.
A Time-of-Check-Time-of-Use (TOCTOU) race condition exists in Craft CMS’s token validation service for tokens with a limited usage count [1]. In the getTokenRoute() method, the token’s usage count is read, checked against the limit, and then updated in separate non-atomic operations, allowing a race window [4]. This affects versions 4.5.0-RC1 through 4.16.18 and 5.0.0-RC1 through 5.8.22 [1].
To exploit this, an attacker must first obtain a valid user account impersonation URL with a non-expired token via some other means [1]. By sending concurrent requests to that URL, the attacker can use the single-use token multiple times before the database update completes, bypassing rate-limiting rules if present [4]. For privilege escalation, the impersonation token must correspond to a user account with more permissions than the attacker’s current user [1].
If successful, an attacker can reuse a token beyond its intended limit, potentially gaining unauthorized access to higher-privileged user accounts or performing actions restricted to that user [1][4]. This could lead to data exposure, modification, or full system compromise depending on the target user’s permissions.
The vulnerability is patched in Craft CMS versions 4.16.19 and 5.8.23 [1]. The fix introduces a mutex lock around the token validation logic to ensure atomicity [2]. Users are strongly advised to upgrade to the patched versions immediately [4].
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
craftcms/cmsPackagist | >= 4.5.0-RC1, < 4.16.19 | 4.16.19 |
craftcms/cmsPackagist | >= 5.0.0-RC1, < 5.8.23 | 5.8.23 |
Affected products
2Patches
13e4afe182799Fixed GHSA-6fx5-5cw5-4897
2 files changed · +33 −22
CHANGELOG.md+1 −0 modified@@ -5,6 +5,7 @@ - Fixed an error that could occur if the `purgeStaleUserSessionDuration` config setting was set to a duration interval string. ([#18238](https://github.com/craftcms/cms/issues/18238)) - Fixed XSS vulnerabilities. (GHSA-6j87-m5qx-9fqp, GHSA-3jh3-prx3-w6wc) - Fixed SSRF vulnerabilities. (GHSA-gp2f-7wcm-5fhx, GHSA-v2gc-rm6g-wrw9) +- Fixed a TOCTOU vulnerability. (GHSA-6fx5-5cw5-4897) ## 4.16.18 - 2026-01-09
src/services/Tokens.php+32 −22 modified@@ -122,35 +122,45 @@ public function getTokenRoute(string $token): array|false { // Take the opportunity to delete any expired tokens $this->deleteExpiredTokens(); - $result = (new Query()) - ->select(['id', 'route', 'usageLimit', 'usageCount']) - ->from([Table::TOKENS]) - ->where(['token' => $token]) - ->one(); - - if (!$result) { - // Remove it from the request so it doesn’t get added to generated URLs - Craft::$app->getRequest()->setToken(null); + $mutex = Craft::$app->getMutex(); + $lockKey = "token:$token"; + if (!$mutex->acquire($lockKey, 5)) { return false; } - // Usage limit enforcement (for future requests) - if ($result['usageLimit']) { - // Does it have any more life after this? - if ($result['usageCount'] < $result['usageLimit'] - 1) { - // Increment its count - $this->incrementTokenUsageCountById($result['id']); - } else { - // Just delete it - $this->deleteTokenById($result['id']); - - // Remove it from the request as well so it doesn’t get added to generated URLs + try { + $result = (new Query()) + ->select(['id', 'route', 'usageLimit', 'usageCount']) + ->from([Table::TOKENS]) + ->where(['token' => $token]) + ->one(); + + if (!$result) { + // Remove it from the request so it doesn’t get added to generated URLs Craft::$app->getRequest()->setToken(null); + return false; } - } - return (array)Json::decodeIfJson($result['route']); + // Usage limit enforcement (for future requests) + if ($result['usageLimit']) { + // Does it have any more life after this? + if ($result['usageCount'] < $result['usageLimit'] - 1) { + // Increment its count + $this->incrementTokenUsageCountById($result['id']); + } else { + // Just delete it + $this->deleteTokenById($result['id']); + + // Remove it from the request as well so it doesn’t get added to generated URLs + Craft::$app->getRequest()->setToken(null); + } + } + + return (array)Json::decodeIfJson($result['route']); + } finally { + $mutex->release($lockKey); + } } /**
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-6fx5-5cw5-4897ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-27128ghsaADVISORY
- github.com/craftcms/cms/commit/3e4afe18279951c024c64896aa2b93cda6d95fdfghsax_refsource_MISCWEB
- github.com/craftcms/cms/security/advisories/GHSA-6fx5-5cw5-4897ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.