Anatomie de deux failles de Next.js, et ce qu'elles disent du web
En août 2026, deux failles critiques de Next.js ont été corrigées dans les versions 15.5.24 et 16.3.3. Toutes deux permettent à un inconnu d’exécuter son propre code sur un serveur, sans mot de passe ni intrusion visible. Mais ce qui vaut vraiment le détour, c’est leur mécanisme. En regardant de près comment elles fonctionnent, on découvre une partie du web que l’on ne voit presque jamais. On comprend mieux ce qui fait réellement tourner un site, tout ce qui se cache sous sa surface et les responsabilités qu’implique le simple fait de l’héberger.
Les deux failles empruntent pourtant des chemins qui n’ont presque rien en commun. L’une, identifiée sous le nom GHSA-2xp9-vwfh-vxw4, passe par une image et permet l’exécution de code via l’optimisation d’images. L’autre, référencée comme CVE-2026-75604, tient à la manière d’écrire une adresse de fichier sous Windows. Elles arrivent malgré tout au même endroit puisqu’un inconnu finit par faire exécuter son propre code au serveur.
C’est ce chemin que l’on va suivre ici, moins pour s’alarmer que pour comprendre. Le sujet nous intéresse aussi pour une raison très concrète. Chez Nualt, nous construisons des frontends avec Next.js et nous voyons le framework revenir de plus en plus souvent dans les projets, notamment avec l’essor du vibe coding. Plus un outil devient accessible et populaire, plus il est utilisé par des équipes qui ne connaissent pas forcément toute la profondeur technique qu’il embarque. Comprendre ce qui se passe sous sa surface devient alors utile, même quand on ne travaille pas directement sur la sécurité.
Ces deux vulnérabilités ne sont d'ailleurs pas des accidents isolés. En un peu plus d'un an, Next.js a déjà connu un contournement d'authentification avec CVE-2025-29927, une exécution de code à distance au cœur de React avec CVE-2025-55182, ainsi qu'une autre faille capable d'aller récupérer les clés d'accès d'une infrastructure cloud avec CVE-2026-44578. À elle seule, cette cadence commence à raconter quelque chose.
Comment une image peut exécuter du code
Une simple image peut suffire à compromettre un serveur. Une image tout à fait banale en apparence, comme celles que l’on met en ligne chaque jour. Fabriquée spécialement par un attaquant, elle peut exploiter une faille et lui permettre de prendre le contrôle de la machine qui la traite.
Dit comme ça, ça paraît presque absurde. Pourtant, dès qu’on regarde le mécanisme de près, tout devient assez logique.

Une image AVIF piégée arrive sur le serveur. Celui-ci la confie à sharp pour l’optimiser, qui s’appuie à son tour sur libheif pour la décoder. C’est au moment du décodage que la faille est déclenchée.
Ce qu'un site fait de chaque image
On ne le voit pas, mais une image affichée sur un site peut passer par toute une petite usine avant d’arriver à l’écran. Next.js peut la recevoir, la redimensionner, la recompresser et servir une version différente selon l’appareil utilisé. C’est précisément ce travail qui permet d’éviter d’envoyer une énorme image de plusieurs mégaoctets à un téléphone qui n’en a pas besoin. Le site charge plus vite et, en général, personne n’a de raison de penser davantage à ce qui vient de se passer. Sauf que pour optimiser une image, il faut commencer par l’ouvrir. Et ouvrir un fichier revient déjà à demander à un programme de lui faire suffisamment confiance pour essayer de le comprendre.
C’est là que les choses deviennent intéressantes.
La chaîne qui décode, et son maillon du fond
Next.js ne sait pas décoder seul tous les formats d’image. Il délègue une partie de ce travail à sharp, une bibliothèque spécialisée dans leur traitement. Sharp s’appuie elle-même sur d’autres composants encore plus spécialisés. Pour les images AVIF, sharp utilise notamment la bibliothèque libheif pour lire et décoder le fichier. On obtient donc plusieurs programmes écrits par plusieurs équipes et empilés les uns sur les autres. Le développeur qui choisit Next.js n’a probablement jamais choisi libheif. Il n’a probablement jamais lu son code. Il peut très bien ignorer jusqu’à son existence. Elle travaille pourtant sur son serveur chaque fois que la chaîne en a besoin. Et la faille se trouve là, tout en bas, dans l’un des étages que presque personne ne va regarder.
C’est l’un des aspects les plus fascinants de cette vulnérabilité. L’application visible n’a pas besoin d’être mal écrite. Le défaut peut se trouver plusieurs couches plus profond, dans un composant dont son développeur n’a jamais entendu parler, et remonter malgré tout jusqu’au serveur entier.
Le piège, et le débordement
L’attaquant prend alors une image AVIF et la construit d’une manière très particulière. Son objectif n’est pas qu’elle s’affiche mal. Il veut provoquer un comportement anormal du décodeur à la seconde où celui-ci tente de la lire.Pour comprendre comment on passe d’une image à l’exécution de code, il faut séparer trois moments.
Tout commence par une erreur dans la manière dont le décodeur évalue l’espace mémoire dont il aura besoin. Libheif lit les informations contenues dans le fichier, en estime la taille, puis réserve une zone suffisamment grande pour les manipuler.
L’image piégée est conçue pour fausser ce calcul. Le décodeur alloue alors moins de mémoire qu’il ne lui en faut réellement, puis commence à y écrire les données qu’il vient de lire.
À ce stade, il y a bien une vulnérabilité, mais pas encore de prise de contrôle. Si les données débordent de manière imprévisible, le résultat le plus probable reste simplement le plantage du programme.
C’est précisément là que commence la partie la plus impressionnante de l’exploitation.
Si l’attaquant maîtrise suffisamment bien le contenu du fichier et le comportement du décodeur, ce dépassement peut atteindre des informations qui influencent la suite du programme. L’exploitation consiste ensuite à utiliser cette corruption de mémoire pour détourner son déroulement normal.
Le passage du débordement à l’exécution de code n’a donc rien d’automatique. Il dépend de la version du logiciel, de l’organisation de sa mémoire et des protections présentes sur le serveur. Découvrir qu’un programme écrit trop loin est une première étape. Trouver comment transformer cette erreur en contrôle réel du programme en est une autre, beaucoup plus exigeante.
C’est aussi ce qui rend ce type de faille fascinant. Entre « cette image fait planter le décodeur » et « cette image permet de contrôler son exécution », il peut y avoir une quantité considérable de compréhension du fonctionnement interne de la machine.
curl -X POST https://exemple.fr/api/upload \
-F "image=@image-avif-piegee.avif"Cette commande n’est pas l’exploit en elle-même. Elle ne contient pas nécessairement les instructions qui finiront par être exécutées et elle ne suffit évidemment pas sur n’importe quel site. Elle représente simplement le geste qui amène le fichier piégé jusqu’au composant vulnérable dans une application où une telle route existe.
Le reste se produit parce que le serveur fait exactement ce qu’on attend de lui.
Le déguisement reste presque parfait puisque, vu de l’extérieur, il ne s’est produit qu’une chose parfaitement banale. Un site a tenté de traiter une image.
La faille qui tient dans un caractère
La seconde vulnérabilité est d’une nature complètement différente et, à sa manière, encore plus subtile.
Cette fois, l’attaque repose sur un simple caractère, et non sur une image trafiquée, un débordement mémoire ou des données dissimulées dans un fichier.
Quelqu’un s’est demandé ce qui se passerait si l’on utilisait un antislash Windows à un endroit où Next.js s’attendait surtout à trouver une barre oblique. La question paraît minuscule. En tirant suffisamment loin sur ce fil, elle permet pourtant de faire sortir le serveur du dossier dans lequel il devrait rester, puis de l’amener jusqu’à l’exécution de code.
Cette attaque est beaucoup plus spécifique que la précédente puisqu’elle repose sur une particularité de Windows. Sur Linux et macOS, les dossiers d’un chemin sont séparés par une barre oblique.
dossier/sous-dossier/fichierWindows accepte aussi l’antislash.
dossier\sous-dossier\fichierToute la faille naît de cette différence. Next.js et Windows ne donnaient pas exactement le même sens au même chemin. Un caractère que le premier ne considérait pas comme dangereux pouvait être parfaitement compris par le second pour naviguer dans les dossiers du serveur.

Une requête contenant un chemin de fichier spécialement construit passe le filtre du serveur Next.js sous Windows. Elle peut alors sortir du dossier prévu et amener le serveur à charger un fichier qu’il n’aurait jamais dû atteindre.
La pièce autorisée
Pour répondre plus rapidement à certaines requêtes, Next.js garde des résultats déjà calculés dans un dossier de cache situé sur le disque du serveur.
Quand une demande arrive, il peut donc récupérer directement un fichier prêt à servir au lieu de refaire tout le travail. La frontière autour de ce dossier, elle, est essentielle. Pour comprendre pourquoi, il faut voir où ce cache se trouve dans un projet Next.js assez classique.
mon-projet/
├── app/
│ ├── page.tsx
│ └── api/
├── public/
│ └── images/
├── .next/
│ ├── cache/ ← le dossier autorisé
│ │ └── ...
│ └── server/
│ └── ...
├── node_modules/
├── .env
└── package.jsonCette arborescence représente simplement les dossiers du projet et leurs relations. Elle permet surtout de voir que le cache n’est pas isolé. À quelques niveaux de lui se trouvent le code de l’application, les dépendances, les fichiers générés par Next.js et parfois un fichier .env contenant des accès à une base de données ou à des services externes.
Le serveur doit donc pouvoir chercher des fichiers dans .next/cache sans qu’une requête venue de l’extérieur puisse l’envoyer librement ailleurs.
Toute la sécurité du mécanisme tient à cette frontière. Le visiteur peut influencer ce que le serveur cherche dans le cache. Il ne doit jamais pouvoir lui demander d’en sortir.
L'adresse qui sort de la pièce
C’est pourtant exactement ce qu’une traversée de chemin permet de tenter.
Pour retrouver un résultat dans son cache, Next.js construit une adresse à partir d’informations contenues dans la requête. Une partie du chemin utilisé par le serveur dépend donc de ce que le visiteur lui envoie. Avec une valeur normale, le serveur reste dans son cache.
.next/
└── cache/
└── page-contactMais les chemins de fichiers ont leur propre grammaire. La séquence .. signifie qu’il faut remonter vers le dossier parent. En la répétant, on peut transformer une adresse qui devait rester dans le cache en une adresse qui remonte dans le projet.
.next/
└── cache/
└── ..\
└── ..\
└── .envLe serveur part donc bien du dossier autorisé. C’est le chemin qu’on lui fournit qui lui demande ensuite de revenir en arrière, niveau après niveau. Cette technique est connue depuis longtemps sous le nom de traversée de chemin. Les applications filtrent donc normalement les adresses contenant ce type de tentative avant de laisser le système les utiliser. Next.js avait bien ce contrôle.
Seulement, le filtre et Windows ne lisaient pas exactement les chemins de la même manière.
Le caractère qui change tout
C’est ici que l’antislash devient intéressant.
On a vu que Windows accepte \ comme séparateur entre les dossiers. Le problème est que la protection de Next.js ne le traitait pas correctement comme l’équivalent de / dans tous les cas.
L’attaquant pouvait donc écrire sa traversée avec des antislashs.
.next\cache\..\..\.envLa subtilité tient à ce qui se passe ensuite.
Filtre Next.js ne reconnaît pas correctement la traversée
↓
Windows interprète \ comme un séparateur de dossiers
↓
..\.. fait réellement remonter de deux niveaux
Le filtre laisse donc passer une adresse parce qu’il ne reconnaît pas correctement sa structure. Quelques instants plus tard, Windows reçoit exactement la même chaîne et lui donne tout son sens.
Il comprend les antislashs comme des séparateurs. Il comprend les .. comme des instructions pour remonter dans l’arborescence. Le chemin qui semblait acceptable au moment du contrôle devient alors une véritable sortie du dossier autorisé.
Et tout l’exploit tient dans ce décalage.
Sortir du cache ne suffit toutefois pas, à lui seul, à exécuter du code. Cela donne d’abord à l’attaquant un pouvoir qu’il n’aurait jamais dû posséder, celui d’amener le serveur vers des fichiers situés ailleurs dans l’arborescence.
L’étape suivante consiste à atteindre un fichier qu’il contrôle et que Next.js peut ensuite charger comme du code. Dans les conditions vulnérables, cette traversée permet justement de relier les deux. Le serveur quitte son cache, atteint un fichier préparé par l’attaquant, puis le charge dans un contexte où son contenu peut être exécuté.
Ce que cette faille a de particulier
La chaîne technique est alors complète.
L’attaquant ne casse pas un décodeur et ne manipule pas directement la mémoire. Il exploite un désaccord entre le filtre de Next.js et Windows sur la manière de lire une adresse. C’est ce qui rend les deux failles presque opposées dans leur méthode. Dans la première, il faut construire un fichier suffisamment tordu pour dérégler un décodeur enfoui plusieurs couches sous le framework. Dans la seconde, il suffit de jouer avec un seul caractère jusqu’à trouver une différence d’interprétation entre Next.js et Windows. Il faut aussi garder une nuance importante. Cette seconde vulnérabilité ne concerne que les serveurs Next.js fonctionnant sous Windows, ce qui reste une configuration rare puisque l’immense majorité des déploiements web tournent sous Linux. Elle est donc superbe à regarder et beaucoup plus étroite à subir. Et cette différence raconte déjà une partie de la suite.
Ce que ces deux failles disent du web
Les deux vulnérabilités ne se situaient ni au même niveau ni dans le même type de composant. La première se trouvait dans libheif, une bibliothèque utilisée plusieurs étages sous Next.js pour décoder les images AVIF. La seconde concernait uniquement les serveurs Windows et reposait sur une mauvaise gestion des chemins de fichiers par Next.js. Dans les deux cas, le point de départ était presque invisible depuis l’application. Le développeur travaillait avec un framework, manipulait une API d’optimisation d’images ou de gestion de fichiers, et pouvait très bien ignorer les composants qui exécutaient réellement ces opérations. Pourtant, c’est à ce niveau plus profond que se trouvait la faille. Cette situation est devenue normale dans le développement web. Une application repose sur un framework, qui s’appuie sur des bibliothèques spécialisées. Ces bibliothèques utilisent parfois d’autres dépendances, lesquelles peuvent à leur tour appeler du code système ou des composants écrits plusieurs années auparavant pour répondre à un besoin très précis. Chaque couche apporte une fonction utile, mais aussi une nouvelle surface technique à maintenir. La faille AVIF rend cette profondeur particulièrement concrète. En choisissant Next.js, personne ne sélectionne directement libheif. Son nom n’apparaît pas forcément dans les fichiers du projet et son code reste généralement hors du champ de vision des équipes qui développent l’application. Pourtant, dès qu’une image AVIF est traitée, cette bibliothèque peut entrer en action et participer au fonctionnement du serveur. Cette dépendance indirecte n’est pas un défaut en soi. Elle permet de réutiliser le travail d’équipes spécialisées et d’éviter que chaque développeur réécrive lui-même un décodeur d’image, un moteur de compression ou une couche de compatibilité système. C’est grâce à cette organisation que des applications complexes peuvent être assemblées rapidement à partir de composants déjà éprouvés.
Cette efficacité a toutefois son revers. Plus une application dépend de couches nombreuses, plus une erreur située loin de son code visible peut avoir des conséquences importantes.
Là où vous hébergez, l'exposition n'est pas la même
Ces deux failles posent alors une question beaucoup plus concrète. Une fois le site en ligne, qui suit ce genre de problème et qui intervient lorsqu’un composant de la pile devient vulnérable ?
Le choix de l’hébergement change directement la réponse. Sur une plateforme gérée comme Vercel, une partie de ce travail est prise en charge au niveau de l’infrastructure. Dans le cas des deux vulnérabilités étudiées ici, l’optimisation AVIF concernée avait été désactivée avant leur divulgation publique et la faille liée aux chemins Windows ne correspondait pas à l’environnement utilisé par la plateforme. Les sites hébergés dans ces conditions n’avaient donc pas à mettre en place eux-mêmes une réponse spécifique à ces deux attaques.
Ce fonctionnement ne rend évidemment pas l’application invulnérable. Une plateforme comme Vercel ne peut pas corriger une authentification mal conçue ou une vulnérabilité propre au code métier d’un projet. Elle peut en revanche absorber une partie des problèmes qui concernent directement l’environnement d’exécution, les composants qu’elle opère et certaines dépendances communes à ses utilisateurs. C’est aussi une partie de ce que l’on délègue lorsque l’on choisit une plateforme gérée.
Avec un serveur que l’on exploite soi-même, cette couche intermédiaire disparaît. Il faut suivre les alertes de sécurité, déterminer si elles concernent réellement les versions et les composants installés, puis appliquer les correctifs lorsqu’ils sont nécessaires. Rien d’impossible pour une équipe qui sait administrer son infrastructure, mais c’est bien une activité supplémentaire et continue. La difficulté de ces deux failles montre surtout que l’information à surveiller ne vient pas toujours d’un endroit évident. Le composant vulnérable peut être une dépendance indirecte comme libheif ou un comportement très particulier d’un système d’exploitation.
C’est là que le choix d’hébergement devient moins théorique. L’auto-hébergement apporte davantage de contrôle et peut présenter des avantages réels en matière de coût ou de maîtrise des données, que nous avons détaillés dans notre article sur l’auto-hébergé et le PaaS. Il implique en contrepartie d’assumer directement la veille et la maintenance de l’environnement qui fait tourner l’application. Sur une plateforme gérée, une partie de ce travail est déplacée vers l’opérateur.
Chez Nualt, on part de ce que vous pouvez porter
Chez Nualt, le choix entre une plateforme gérée et un serveur exploité directement ne se fait donc pas par principe. On regarde le budget, le trafic, les contraintes liées aux données et les compétences disponibles autour du projet. On regarde aussi ce qui se passera après la mise en ligne.
Un serveur peut être parfaitement adapté à un projet au moment de sa conception et devenir un mauvais choix si personne n’est disponible pour suivre ses mises à jour, ses dépendances et les alertes de sécurité qui apparaîtront dans les années suivantes. À l’inverse, une équipe qui possède déjà ces compétences peut avoir de bonnes raisons de vouloir garder davantage de contrôle sur son infrastructure.
C’est pour cela que la maintenance fait partie du choix d’architecture dès le départ. Les deux failles que l’on vient de parcourir montrent assez bien pourquoi. Le problème à surveiller ne se trouve pas forcément dans le code que l’on a écrit soi-même. Il peut apparaître plusieurs couches plus bas, dans une bibliothèque dont on ignorait presque l’existence, et demander malgré tout une intervention sur le projet.
Notre rôle est donc moins de pousser systématiquement vers une solution que de choisir une infrastructure cohérente avec les personnes qui devront réellement s’en occuper une fois le site en ligne.


