Neutron, notre moteur d’IA, a obtenu un score de 97,1 % sur le benchmark CyberGym de l’UC Berkeley. En savoir plus

Analyse approfondie

Analyse approfondie

Contournement de l’authentification du garde par jeton de Laravel (CVE-2026-114509)

Laravel classe CVE-2026-114509 comme critique, CVSS v4.0 9.2 : un attaquant non authentifié peut se connecter sous l’identité d’un utilisateur existant du garde d’API historique token sur MySQL ou MariaDB en envoyant un corps JSON tel que {"api_token": [0]}, sans connaître de jeton d’API valide. Corrigé dans 12.68.0 et 13.29.0, avec le correctif également présent dans le tag 11.56.1. Une preuve de concept publique existe depuis le 22 octobre 2026, et aucune exploitation réelle n’est connue.

En bref

  • Le garde d’authentification par jeton intégré à Laravel, le pilote token, authentifie une requête en recherchant la valeur api_token dans la table des utilisateurs. Avant le correctif, le garde ne vérifiait jamais que le jeton était une chaîne : un corps JSON spécialement conçu transformait donc la recherche en requête WHERE api_token IN (...) contenant des valeurs choisies par l’attaquant.
  • Sur MySQL et MariaDB, un corps tel que {"api_token": [0]} amène la base de données à convertir chaque jeton texte stocké en nombre. Tout jeton commençant par une lettre est alors considéré comme égal à 0. La requête renvoie cet utilisateur et s’authentifie sous son identité, sans jeton valide ni attaque par force brute.
  • Laravel classe la faille comme critique, avec un score CVSS v4.0 de 9.2, et une preuve de concept publique a été publiée le 22 octobre 2026. La faille ne figure pas dans le catalogue CISA KEV et aucune exploitation réelle n’est connue au 24 octobre 2026.
  • Mettez immédiatement à jour vers 12.68.0, 13.29.0 ou le tag 11.56.1, ou ajoutez un middleware qui rejette les valeurs api_token autres que des chaînes. Le correctif a été livré le 25 août 2026 : une mise à jour des dépendances depuis cette date vous protège peut-être déjà.

Suis-je concerné ?

Vous êtes concerné si toutes ces conditions sont réunies :

  • config/auth.php définit un garde avec 'driver' => 'token' et un endpoint l’utilise (routes avec auth:api ou auth:<guard> lorsque ce garde est le garde par jeton).
  • Votre version du framework est antérieure à la version corrigée de sa branche : antérieure à 12.68.0 pour Laravel 12, à 13.29.0 pour Laravel 13, au tag 11.56.1 pour Laravel 11, ou toute branche en fin de vie (10.x et antérieures).
  • Votre table des utilisateurs se trouve sur MySQL ou MariaDB. PostgreSQL rejette la comparaison entre types incompatibles et SQLite ne renvoie aucune ligne, comme le démontre la PoC publique.
  • Le garde ne définit pas 'hash' => true sur PHP 8. Le créateur de gardes de Laravel attribue à hash la valeur false par défaut lorsque la clé est absente, et les configurations de squelette qui incluent le garde par jeton la définissent explicitement à false.

Vous n’êtes pas concerné si vous utilisez Laravel Sanctum ou Passport, ou tout garde autre que le pilote token. Sanctum lit un en-tête Bearer, toujours de type chaîne, le hache avec SHA-256 et stocke les jetons dans une table distincte.

Branche Versions affectées Version corrigée
13.x >= 13.0.0, < 13.29.0 13.29.0
12.x < 12.68.0 12.68.0
11.x < 11.56.1 tag 11.56.1
10.x et antérieures toutes les versions avec le garde par jeton aucune (fin de vie)

Vérifiez ce qui s’exécute dans vos déploiements :

# Is the token guard configured at all?
grep -n "'driver' => 'token'" config/auth.php

# Which framework version is installed?
composer show laravel/framework | grep versions

# Which database backend?
grep DB_CONNECTION .env

# Does composer already flag it?
composer audit

Dans une image de conteneur :

docker run --rm <image> php -r "echo shell_exec('composer show laravel/framework 2>/dev/null | grep versions') ?: 'composer not in image';"

L’historique du garde compte : les squelettes Laravel 6.x et 7.x incluaient par défaut le garde par jeton avec 'hash' => false, tandis que les squelettes 8.x et 9.x ne contiennent aucun garde d’API. config/auth.php n’est jamais écrasé par composer update : une application mise à niveau depuis un ancien squelette peut donc toujours utiliser ce garde sans que cela soit évident. La PoC publique documente cet historique de configuration, confirmé par les fichiers des squelettes 6.x et 7.x. Packagist a enregistré 19 679 151 téléchargements de laravel/framework le mois dernier (vérification le 24 octobre 2026) : de nombreux déploiements doivent donc effectuer cette vérification.

Comment corriger le problème ?

Mettez le framework à jour vers la première version corrigée de votre branche :

# Laravel 12
composer require "laravel/framework:^12.68.0" --update-with-dependencies

# Laravel 13
composer require "laravel/framework:^13.29.0" --update-with-dependencies

Le correctif existe également dans le tag v11.56.1 de laravel/framework, ce qui a été vérifié en lisant src/Illuminate/Auth/TokenGuard.php à ce tag. Le bulletin lui-même ne mentionne que les plages 12.x et 13.x : confirmez donc la présence du correctif dans le code si vous figez la version à 11.56.1.

Si vous ne pouvez pas effectuer la mise à jour, ajoutez un middleware qui rejette les jetons autres que des chaînes avant qu’ils n’atteignent le garde, comme le recommande la PoC :

public function handle($request, Closure $next)
{
    $token = $request->input('api_token');
    if ($token !== null && !is_string($token)) {
        abort(400, 'Invalid token format');
    }
    return $next($request);
}

Le middleware bloque cette classe d’attaque. Il a toutefois des limites : il doit être enregistré sur chaque route ou groupe susceptible d’atteindre le garde par jeton et ne corrige pas la confusion de types sous-jacente. Une règle de pare-feu applicatif web rejetant les valeurs JSON ou de requête dans lesquelles api_token est un tableau obtient le même résultat ; la PoC fournit des motifs de requête et de JSON pour cela. La correction durable consiste à effectuer la mise à jour ou à migrer l’API vers Laravel Sanctum, qui n’utilise pas du tout cette recherche.

Quelle est la cause racine ?

Le garde par jeton traitait tout identifiant non vide comme une chaîne, et Eloquent transforme un identifiant de type tableau en liste IN. Un tableau JSON devenait donc une comparaison SQL avec une liste de valeurs choisies par l’attaquant.

La vérification vulnérable, dans src/Illuminate/Auth/TokenGuard.php à la version v12.67.0, ligne 83 :

$token = $this->getTokenForRequest();

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

empty() renvoie false pour un tableau non vide : ! empty([0]) passe donc la vérification et le tableau [0] poursuit son chemin. Deux autres éléments complètent la chaîne :

  • Le créateur de gardes dans src/Illuminate/Auth/AuthManager.php (ligne 166) transmet $config['hash'] ?? false : une configuration sans clé hash conserve donc le jeton brut. Avec 'hash' => true sur PHP 8, hash('sha256', [0]) lève une TypeError et la requête se termine par une erreur plutôt que par un contournement. La PoC précise que PHP 7 émet seulement un avertissement puis poursuit : les anciennes piles restent donc exposées même avec le hachage activé.
  • EloquentUserProvider::retrieveByCredentials (ligne 129) traite les identifiants de type tableau comme une liste, par conception, afin que d’autres parcours d’authentification puissent rechercher plusieurs candidats :
foreach ($credentials as $key => $value) {
    if (is_array($value) || $value instanceof Arrayable) {
        $query->whereIn($key, $value);
    }

L’identifiant fourni au garde cesse donc d’être « le jeton à comparer » et devient « la liste de jetons avec lesquels l’utilisateur est autorisé à correspondre ». Les commits du correctif, tous deux intitulés « fix token retrieval » et datés du 24 août 2026, éliminent la confusion de types en exigeant une chaîne avant la recherche :

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

La même vérification est ajoutée à validate(), le deuxième point d’entrée qui transmet un identifiant à retrieveByCredentials sans aucun hachage.

Comment la faille est-elle exploitée ?

L’attaque nécessite une seule requête avec un corps JSON, comme le démontre la PoC publique :

  1. L’attaquant envoie POST /api/user avec Content-Type: application/json et le corps {"api_token": [0]}. La méthode request->input() de Laravel décode le JSON : l’identifiant devient donc le tableau PHP [0], un entier dans une liste.
  2. TokenGuard::getTokenForRequest() renvoie [0] depuis les entrées de la requête, et ! empty([0]) passe la vérification.
  3. Lorsque hash vaut false, le tableau brut atteint retrieveByCredentials, qui construit WHERE api_token IN (0) parce que l’identifiant est un tableau.
  4. MySQL et MariaDB comparent la colonne texte au nombre 0 en convertissant chaque jeton stocké en nombre. Un jeton commençant par une lettre, cas courant pour les jetons d’API, est converti en 0 et considéré comme égal.
  5. ->first() renvoie la première ligne correspondante. Le laboratoire de la PoC crée quatre utilisateurs et la requête revient authentifiée sous l’identité de l’administrateur, le premier utilisateur créé, sans qu’aucun jeton valide ait été envoyé.
  6. Chaque requête suivante de cette session s’exécute avec les permissions de cet utilisateur : le contournement devient donc une prise de contrôle de compte sur chaque route protégée par le garde.

La requête exacte de l’attaquant issue de la PoC, reproductible dans son laboratoire :

curl -s http://target/api/user \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST -d '{"api_token": [0]}'

Une chaîne de requête telle que ?api_token[]=0 transmet également un tableau, mais contenant la chaîne "0", que la base de données compare comme du texte. Le chemin exploitable est donc le corps JSON contenant un nombre sans guillemets.

Déroulement de l’attaque

flowchart LR A["POST avec un corps JSON
{"api_token": [0]}"] --> B["getTokenForRequest()
renvoie le tableau [0]"] B --> C["! empty([0]) passe,
aucune vérification is_string"] C --> D["L’identifiant tableau
devient WHERE api_token IN (0)"] D --> E["MySQL convertit les jetons texte
en nombres, ceux débutant par une lettre en 0"] E --> F["first() renvoie le
premier utilisateur correspondant"] F --> G["Requête authentifiée
sous l’identité de cet utilisateur"]

Que peut faire un attaquant ?

L’attaquant s’authentifie sous l’identité d’un utilisateur existant du garde par jeton et obtient tout ce que l’accès API de cet utilisateur autorise : lecture, écriture et suppression via les routes de l’API, ainsi que toute donnée accessible par cette couche de routage. Voici les conditions et limites tirées du bulletin et de la PoC :

  • Aucun identifiant, aucune interaction utilisateur et aucune position réseau particulière ne sont nécessaires, au-delà de l’accès à l’API.
  • Le garde doit utiliser le pilote token sur MySQL ou MariaDB, avec hash à false. Les déploiements PostgreSQL et SQLite ne sont pas affectés par la comparaison avec un entier.
  • L’attaquant ne choisit pas le compte. La requête renvoie la première ligne correspondante, qui était celle de l’administrateur dans le laboratoire de la PoC parce que cet utilisateur avait été créé en premier.
  • Les déploiements Sanctum et Passport ne sont pas affectés. Leurs mécanismes de recherche de jetons diffèrent.

Pourquoi le correctif fonctionne-t-il ?

Le correctif ajoute is_string() avant la vérification de vacuité aux deux points d’entrée, user() et validate() :

-        if (! empty($token)) {
+        if (is_string($token) && ! empty($token)) {
             $user = $this->provider->retrieveByCredentials([
-        if (empty($credentials[$this->inputKey])) {
+        if (! is_string($credentials[$this->inputKey] ?? null) || empty($credentials[$this->inputKey])) {

Un tableau JSON échoue désormais à la vérification de type chaîne et l’authentification renvoie null, le résultat que le garde était toujours censé produire. Le correctif résout entièrement ce problème au niveau du garde : aucun identifiant autre qu’une chaîne ne peut désormais atteindre retrieveByCredentials via le garde par jeton. Il ne modifie pas retrieveByCredentials lui-même, qui transforme toujours les identifiants de type tableau en listes IN pour les fournisseurs qui les utilisent délibérément. Le code applicatif qui transmet des tableaux aux recherches d’authentification reste donc responsable de ses propres risques.

Comment vérifier si j’ai été compromis ?

Laravel ne publie aucun indicateur de compromission. La PoC documente la forme de l’exploitation, et les motifs sont sans ambiguïté puisque les clients légitimes envoient un jeton de type chaîne :

  • Chaînes de requête dans les journaux d’accès : api_token[]=0 et api_token[0]=0.
  • Corps JSON dans les journaux applicatifs ou du WAF : "api_token":[0] et "api_token":[false].
  • Anomalies à examiner indépendamment du motif : réponses réussies sur des routes du garde par jeton depuis des sessions sans émission de jeton correspondante, et comptes administrateurs agissant depuis de nouvelles adresses source.

Chronologie

  • 2026-08-24 : Taylor Otwell enregistre le correctif « fix token retrieval » sur les deux branches prises en charge.
  • 2026-08-25 : Laravel publie les versions v12.68.0 et v13.29.0 avec le correctif.
  • 2026-10-19 : Laravel publie le bulletin GHSA-f9x3-7vq9-cph6, classe la faille comme critique avec un score CVSS v4.0 de 9.2 et crédite KIKOmanasijev.
  • 2026-10-21 : la CNA GitHub publie la fiche CVE-2026-114509.
  • 2026-10-22 : une preuve de concept publique accompagnée d’un laboratoire Docker est publiée sur GitHub.
  • 2026-10-23 : la GitHub Advisory Database publie et examine le bulletin, en ajoutant CWE-287 et CWE-1287.
  • 2026-10-24 : publication de cet article.

FAQ

Laravel Sanctum ou Passport sont-ils affectés ?

Non. Sanctum lit l’en-tête Bearer, toujours de type chaîne, et compare un hachage SHA-256 à une table distincte de jetons d’accès personnels : le tableau n’atteint donc jamais une recherche. Passport authentifie via des parcours OAuth2 avec un stockage et une recherche différents. Le bulletin concerne uniquement le garde par jeton intégré.

Pourquoi l’attaque fonctionne-t-elle uniquement sur MySQL ou MariaDB ?

L’identifiant de type tableau finit par être comparé au nombre 0. MySQL et MariaDB convertissent alors la colonne texte en nombre pour cette comparaison, ce qui transforme la plupart des jetons réels en 0. PostgreSQL lève une erreur de type pour la même requête, tandis que l’affinité de types de SQLite ne renvoie aucune ligne ; ces deux comportements sont démontrés dans le laboratoire de la PoC. Le bug du garde existe partout, mais la comparaison exploitable nécessite cette conversion implicite.

'hash' => true me protège-t-il ?

Sur PHP 8, cela transforme l’attaque en erreur : hash('sha256', [0]) lève une TypeError et la requête échoue au lieu d’authentifier quelqu’un. Sur PHP 7, la fonction de hachage émet seulement un avertissement et renvoie null ; la PoC considère donc cette combinaison comme toujours exposée. Si le hachage constitue votre seule défense, effectuez tout de même la mise à jour. Notez aussi que les configurations des squelettes 6.x et 7.x incluaient le garde par jeton avec 'hash' => false.

L’attaquant peut-il choisir un compte précis ?

Pas directement. La recherche renvoie la première ligne correspondant aux critères de la base de données : l’attaquant obtient donc le premier utilisateur correspondant, ce qui était dans le laboratoire de la PoC le premier utilisateur créé, souvent un administrateur. Un attaquant peut également envoyer plusieurs nombres dans le tableau JSON pour élargir les correspondances après conversion, mais la sélection reste celle que la base de données renvoie en premier.

Mon application utilise une version de Laravel en fin de vie. Que faire ?

Ajoutez le middleware qui rejette les valeurs api_token autres que des chaînes, ou une règle WAF utilisant les motifs ci-dessus, et prévoyez une migration vers Sanctum, le chemin recommandé par la PoC. Les branches en fin de vie ne recevront pas le correctif : le garde lui-même reste donc vulnérable tant qu’il est configuré.

Scannez vos applications web avec Ostorlab pour identifier les versions du framework Laravel et les configurations du garde par jeton qu’elles exposent, y compris les anciens déploiements toujours configurés avec le pilote token.

Références

  • Bulletin Laravel GHSA-f9x3-7vq9-cph6, publié le 19 octobre 2026 : https://github.com/laravel/framework/security/advisories/GHSA-f9x3-7vq9-cph6 (consulté le 24 octobre 2026)
  • Fiche CVE-2026-114509 avec le vecteur CVSS v4.0 9.2 : https://cveawg.mitre.org/api/cve/CVE-2026-114509 (consultée le 24 octobre 2026)
  • Entrée de la GitHub Advisory Database avec les classifications CWE-287 et CWE-1287 : https://github.com/advisories/GHSA-f9x3-7vq9-cph6 (consultée le 24 octobre 2026)
  • Commit du correctif sur la branche 12.x, daté du 24 août 2026 : https://github.com/laravel/framework/commit/9b21ce0a9bb2b2599978bc0b0df4abea952ad31c (consulté le 24 octobre 2026)
  • Commit du correctif sur la branche 13.x, daté du 24 août 2026 : https://github.com/laravel/framework/commit/8dbb08bccac1be095dba4a9d259a419620f1e8a5 (consulté le 24 octobre 2026)
  • Version v12.68.0, publiée le 25 août 2026 : https://github.com/laravel/framework/releases/tag/v12.68.0 (consultée le 24 octobre 2026)
  • Version v13.29.0, publiée le 25 août 2026 : https://github.com/laravel/framework/releases/tag/v13.29.0 (consultée le 24 octobre 2026)
  • Code corrigé de TokenGuard au tag v11.56.1 : https://raw.githubusercontent.com/laravel/framework/v11.56.1/src/Illuminate/Auth/TokenGuard.php (consulté le 24 octobre 2026)
  • Code vulnérable de TokenGuard à la version v12.67.0 : https://raw.githubusercontent.com/laravel/framework/v12.67.0/src/Illuminate/Auth/TokenGuard.php (consulté le 24 octobre 2026)
  • Preuve de concept publique avec un laboratoire Docker, publiée le 22 octobre 2026 : https://github.com/tajulasri/-CVE-2026-114509-POC (consultée le 24 octobre 2026)
  • Configuration d’authentification du squelette Laravel 7.x avec le garde par jeton : https://raw.githubusercontent.com/laravel/laravel/7.x/config/auth.php (consultée le 24 octobre 2026)
  • Configuration d’authentification du squelette Laravel 6.x avec le garde par jeton : https://raw.githubusercontent.com/laravel/laravel/6.x/config/auth.php (consultée le 24 octobre 2026)
  • Statistiques de téléchargement Packagist pour laravel/framework : https://packagist.org/packages/laravel/framework.json (consultées le 24 octobre 2026)