[EN] GHSA-f9x3-7vq9-cph6: Authentication Bypass in Laravel TokenGuard
JOIN
JOIN
RESEARCH ENTRY 2026.10.20
en

[EN] GHSA-f9x3-7vq9-cph6: Authentication Bypass in Laravel TokenGuard

Gabriel Rodrigues
RESEARCHER Rafael Felix, Gabriel Rodrigues
READ TIME4 MINUTES
PUBLISHED20 Oct 2026
[EN] GHSA-f9x3-7vq9-cph6: Authentication Bypass in Laravel TokenGuard

Abstract

The Yokai Research team developed an in-depth analysis and proof-of-concept exploit for a critical authentication bypass in the Laravel framework, tracked as GHSA-f9x3-7vq9-cph6. The vulnerability is in the framework's built-in TokenGuard, the guard behind Laravel's auth:api protection was discovered by KIKOmanasijev and reported through the GitHub Security Advisory program. It affects framework versions below 13.29.0 (13.x) and below 12.68.0 (12.x), with a severity of 9.2 (Critical) under CVSS v4. When the application's user table lives on a MySQL or MariaDB database, an unauthenticated attacker can authenticate as an existing user without knowing any valid API token, by sending a single crafted request to any route protected by the guard. After the report, the Laravel team shipped fixes authored by Taylor Otwell on August 24-25, 2026 commits 9b21ce0a9bb, 8dbb08bc, 5056ca53 and the advisory was published on October 19, 2026. This publication provides security researchers with a detailed analysis of the root cause, the exact pre-fix and post-fix code, and a reproducible lab and proof-of-concept.

Technical Details

The vulnerability exploits the lack of strict type validation on the authentication credential and the type-coercion behavior of MySQL/MariaDB when comparing string columns against integers.

The vulnerable logic is in src/Illuminate/Auth/TokenGuard.php, specifically in the user() method (line 70) and the validate() method (line 111). The code before the fix is available in the historical view at original, and the patched version.

In the pre-fix version, user() accepts any credential that is not "empty" before querying the user provider:

$token = $this->getTokenForRequest();

if (!empty($token)) {
    $user = $this->provider->retrieveByCredentials([
        $this->storageKey => $this->hash ? hash('sha256', $token) : $token,
    ]);

PHP's empty() returns false for non-empty arrays, so a non-string credential an array passes the gate. The provider, EloquentUserProvider::retrieveByCredentials(), converts array values into a whereIn() clause:

foreach ($credentials as $key => $value) {
    if (is_array($value) || $value instanceof Arrayable) {
        $query->whereIn($key, $value);
    }

The resulting statement is select * from users where api_token in (?) limit 1, and because the payload arrives from a JSON body (json_decode produces a real PHP integer), PDO binds it as PARAM_INT. MySQL and MariaDB then compare the VARCHAR token column numerically: any value that does not start with a digit casts to 0 a behavior confirmed during validation with SELECT 'poc-token-abc' = 0` returning 1. The row matches, and the guard treats the request as fully authenticated.

Affected Endpoints

Any route protected by Laravel's token guard (middleware auth:api / guard configured with the token driver), on the credential parameter api_token:

Common use case:

Exploitation example:

The first user whose stored token does not start with a digit is returned by the query and populated into the guard as the authenticated user.

Proof of Concept

Based on the vulnerability reported by KIKOmanasijev, the Yokai research team developed a Python exploit and a fully reproducible Docker lab: https://github.com/hakaioffsec/GHSA-f9x3-7vq9-cph6-PoC

The lab runs the exact vulnerable commit (13.x#0189a60b) and the exact fix commit (13.x#9b21ce0a) side by side against the same MySQL 8 database:

0:00
/0:18

Fix

The Laravel team fixed the vulnerability by adding is_string() validation so non-string credentials never reach the user provider commit 9b21ce0a9bb, 13.x backported as 8dbb08bc in 12.x and 5056ca53 in 11.x.

-        if (! empty($token)) {
+        if (is_string($token) && ! empty($token)) {

-        if (empty($credentials[$this->inputKey])) {
+        if (! is_string($credentials[$this->inputKey] ?? null) || empty($credentials[$this->inputKey])) {

Validated through Git history, the fix first shipped in tags v13.27.0 (13.x), v12.68.0 (12.x) and v11.56.1 (11.x). The advisory designates >= 13.29.0 / >= 12.68.0 as patched a conservative designation, since no changes touched TokenGuard.php between v13.27.0 and v13.29.0.

Recommendations

It is recommended to update laravel/framework to version 13.29.0 or higher (12.x: 12.68.0 or higher 11.x: 11.56.1 or higher) or, at minimum, to the first tag containing the fix (v13.27.0).

Conclusion

Exploitation of this vulnerability allows an unauthenticated attacker to abuse PHP's loose empty() validation combined with an implicit whereIn() conversion and MySQL/MariaDB numeric casting to authenticate as an arbitrary existing user of a Laravel application, resulting in full account takeover on token-guarded routes. The fix is minimal strict string validation at the guard boundary.

Let's practice!

Hacking Club is a training platform focused on the practical development of cybersecurity professionals. The TokenGuard challenge replicates, in a controlled environment, a service vulnerable to the flaw discussed in this article, allowing researchers and professionals to consolidate the presented concepts by exploiting the vulnerability in a secure lab.

References