Contournement du middleware Next.js (CVE-2025-29927) : pourquoi l'autorisation appartient à la route, pas à l'edge
En mars 2025, une vulnérabilité critique a été divulguée dans Next.js : un attaquant pouvait contourner le middleware en envoyant un seul en-tête HTTP. La faille, CVE-2025-29927, notée 9.1, affectait les applications qui implémentaient des contrôles d'authentification ou d'autorisation dans le middleware Next.js. La leçon dépasse le correctif. Le middleware est une couche de routage et de traitement des requêtes, pas une frontière d'autorisation ; la traiter comme telle transforme une commodité en point unique de défaillance.
1. Ce que fait le middleware Next.js
Le middleware s'exécute avant qu'une requête soit terminée. Il peut réécrire, rediriger, ajouter des en-têtes ou rejeter une requête. Un schéma courant :
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('session')?.value;
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
};
Cela ressemble à une barrière de contrôle d'accès. Ce n'en est pas une. C'est un filtre de requête exécuté dans le même processus que l'application, et il peut être ignoré si le framework croit que la requête est une sous-requête interne.
2. L'en-tête de sous-requête interne
Next.js utilise un en-tête interne, x-middleware-subrequest, pour éviter la récursion infinie lorsqu'un middleware déclenche un fetch qui rentre dans l'application. La valeur encode le chemin du middleware, répété jusqu'à une limite de profondeur. Dans les versions affectées, une requête externe fournissant la valeur attendue était traitée comme sous-requête interne et le middleware était entièrement ignoré.
# Illustration contre une instance de test affectée et autorisée
curl -i https://target.example.com/dashboard \
-H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware'
La valeur exacte dépend de la version et de l'arborescence (par exemple src/middleware si le fichier est sous src/). La limite est de cinq. Si l'en-tête correspond, la fonction middleware ne s'exécute pas et la requête passe directement au handler ou à la page.
3. Versions affectées et correctifs
- Next.js 12 : affecté avant 12.3.5 ; corrigé en 12.3.5.
- Next.js 13 : affecté avant 13.5.9 ; corrigé en 13.5.9.
- Next.js 14 : affecté avant 14.2.25 ; corrigé en 14.2.25.
- Next.js 15 : affecté avant 15.2.3 ; corrigé en 15.2.3.
L'avis est GHSA-f82v-jwr5-mffw. Le correctif empêche les requêtes externes de fournir l'en-tête interne, donc le middleware ne peut plus être ignoré par un client. La mise à jour est nécessaire mais insuffisante si l'application continue de reposer uniquement sur le middleware pour l'autorisation.
4. Pourquoi l'impact est grave
Beaucoup d'applications Next.js utilisent le middleware pour les contrôles les plus importants :
- Rediriger les utilisateurs non authentifiés hors de
/dashboard. - Bloquer
/adminsauf si la session contient un rôle administrateur. - Imposer l'isolation des locataires en réécrivant vers un backend spécifique.
- Ajouter des en-têtes de sécurité ou rejeter des régions bloquées.
Si le middleware est ignoré, la page ou le handler s'exécute quand même. Une page de tableau de bord qui récupère des données côté serveur peut afficher les données de la victime à un demandeur non authentifié. Une route API d'administration qui supposait le rôle déjà vérifié peut effectuer des actions privilégiées. La vulnérabilité n'accorde pas de session ; elle retire le contrôle censé l'exiger.
5. Tester prudemment
Ne testez que des applications que vous possédez ou êtes autorisé à évaluer. Séquence minimale :
- Demandez une route protégée sans session et confirmez la redirection ou le 401/403 attendu.
- Répétez avec l'en-tête
x-middleware-subrequestet la valeur adaptée à la version. - Comparez les codes de statut, les corps de réponse et les en-têtes
Location. - Si le contenu protégé apparaît, confirmez qu'il contient des données que l'utilisateur non authentifié ne devrait pas voir.
- Vérifiez si le handler vérifie indépendamment la session. Si oui, le contournement peut se limiter au rendu ; sinon, l'impact est plus large.
# Base
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' https://target.example.com/admin
# Sonde (test autorisé uniquement)
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' \
-H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
https://target.example.com/admin
6. Le correctif durable : autoriser dans la couche de données
Le middleware peut rester utile pour les redirections, la détection de langue et la manipulation d'en-têtes. Il ne doit pas être le seul endroit où une décision de sécurité est prise. Placez le contrôle d'autorisation à côté de l'accès aux données :
// app/api/invoices/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getSession } from '@/lib/auth';
import { db } from '@/lib/db';
export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
const session = await getSession(req);
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const invoice = await db.invoice.findFirst({
where: { id: params.id, tenantId: session.tenantId }
});
if (!invoice) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json(invoice);
}
Ce handler est sûr même si le middleware est contourné, mal configuré ou supprimé. Le contrôle de session et le filtre de locataire sont dans la fonction qui renvoie les données. C'est la propriété à viser : défense en profondeur, avec le contrôle faisant autorité au point d'usage.
7. Durcissement supplémentaire
- Bloquez l'en-tête à l'edge. Comme atténuation temporaire pour les déploiements non corrigés, supprimez ou rejetez
x-middleware-subrequestde toutes les requêtes externes au niveau du proxy ou du CDN. - Corrigez toutes les instances Next.js. Incluez l'auto-hébergement, les environnements de prévisualisation et les applications internes. Un staging sur une ancienne version reste un point d'entrée.
- Inventoriez l'usage du middleware. Cherchez
middleware.ts,middleware.jsetsrc/middleware. Pour chaque matcher, demandez : que se passe-t-il si cette fonction ne s'exécute jamais ? - Ajoutez des tests de régression. Testez les routes protégées sans session, avec session expirée, avec un autre locataire et avec l'en-tête interne présent.
- Journalisez les décisions. Enregistrez le sujet authentifié, la ressource et la décision. Un pic soudain de 200 sur des routes protégées sans session est une occasion de détection.
8. Conclusion
CVE-2025-29927 rappelle que les frameworks optimisent l'expérience développeur, pas les invariants de sécurité. Le middleware est un endroit élégant pour exprimer des règles de routage, mais un endroit fragile pour appliquer l'autorisation. L'en-tête à l'origine du contournement était un détail d'implémentation interne ; la vraie vulnérabilité était l'hypothèse qu'un filtre de requête ne pouvait pas être ignoré. Corrigez le framework, bloquez l'en-tête et déplacez le contrôle faisant autorité vers la fonction qui lit ou modifie réellement la ressource protégée. Un middleware contourné devient alors une redirection manquée, pas une fuite de données.