
Einige Proof-of-Concept (POCs) für CVE-2025-29927, CVE-2026-27978 und CVE-2026-29057 in Next.js.
Dieses Repository enthält reproduzierbare Proof-of-Concept-Umgebungen für drei Next.js-Sicherheitslücken. Jeder PoC enthält ein verwundbares Ziel, ein behobenes Ziel und ein Skript, das den Verhaltensunterschied zwischen beiden demonstriert.
Das Ziel dieses Projekts ist es, die Grundursache und die praktischen Auswirkungen jedes Problems in einer minimalen Umgebung leicht beobachtbar zu machen.
| CVE | Advisory | Auswirkung | Verwundbare Version | Behobene Version |
|---|---|---|---|---|
CVE-2025-29927 | GHSA-f82v-jwr5-mffw | Autorisierungsbypass, wenn die Zugriffskontrolle nur auf Middleware basiert | 15.2.2 | 15.2.3 |
CVE-2026-27978 | GHSA-mq59-m269-xvcx | Origin: null-Bypass der Server Actions CSRF-Prüfungen | 16.1.6 | 16.1.7 |
CVE-2026-29057 | GHSA-ggv3-7p47-pfv8 | HTTP-Request-Smuggling durch Rewrites an ein externes Backend | 15.5.12 | 15.5.13 |
NEXT-16.2.4-IMAGE-REDIRECT | lokaler Quellcode-Audit-Fund | Bypass der Remote-Allowlist des Bildoptimierers durch Weiterleitungen | 16.2.4 | nicht verifiziert |
NEXT-16.2.4-IMAGE-LOCAL-REWRITE | lokaler Quellcode-Audit-Fund | Lokale URL des Bildoptimierers kann durch externe Rewrites privaten Upstream erreichen | 16.2.4 | nicht verifiziert |
Die unten stehenden Hashes stammen aus dem Upstream vercel/next.js.
| CVE | Verwundbarer Release-Commit | Behobener Release-Commit | Relevanter Patch-Commit |
|---|---|---|---|
CVE-2025-29927 | v15.2.2 -> f4552826e1ed15fbeb951be552d67c5a08ad0672 | v15.2.3 -> 535e26d3c69de49df8bd17618a424cbe65ec897b | 52a078da3884efe6501613c7834a3d02a91676d2 |
CVE-2026-27978 | v16.1.6 -> adf8c612adddd103647c90ff0f511ea35c57076e | v16.1.7 -> bdf3e3577a6d55ea186a48238d61fbd8da07a626 | a27a11d78e748a8c7ccfd14b7759ad2b9bf097d8 |
CVE-2026-29057 | v15.5.12 -> d23f41c42506005fe6978e076a1ccbf8979e4925 | v15.5.13 -> cfd5f533b08df3038476dcd54f1d6d660d85f069 | dc98c04f376c6a1df76ec3e0a2d07edf4abdabd6 |
.
|- docker-compose.yml
|- pocs/
| |- cve-2025-29927/
| |- cve-2026-27978/
| |- cve-2026-29057/
| |- next-16.2.4-image-redirect-allowlist-bypass/
| `- next-16.2.4-image-local-rewrite-ssrf/
`- scripts/
|- run-cve-2025-29927.mjs
|- run-cve-2026-27978.mjs
|- run-cve-2026-29057.mjs
|- run-next-16.2.4-image-redirect-allowlist-bypass.mjs
`- run-next-16.2.4-image-local-rewrite-ssrf.mjs
docker compose verfügbar ist.docker compose up --build
Freigegebene Ports:
3001 -> CVE-2025-29927 verwundbar3002 -> CVE-2025-29927 behoben3003 -> CVE-2026-27978 verwundbar3004 -> CVE-2026-27978 behoben3005 -> CVE-2026-29057 verwundbar3006 -> CVE-2026-29057 behoben3007 -> NEXT-16.2.4-IMAGE-REDIRECT3008 -> NEXT-16.2.4-IMAGE-LOCAL-REWRITEIn diesem PoC ist /dashboard nur durch Middleware geschützt:
export function middleware(request) {
const session = request.cookies.get('session')?.value
if (session !== 'admin') {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
Die Autorisierungsprüfung selbst ist nicht falsch. Das Problem ist, dass die Route vollständig von der Annahme abhängt, dass die Middleware-Ausführung nicht übersprungen werden kann.
In betroffenen Next.js-Versionen konnten externe Anfragen weiterhin den internen Header x-middleware-subrequest liefern, und die Laufzeit behandelte diesen Wert als vertrauenswürdige Middleware-Metadaten. Die relevante verwundbare Logik war:
const INTERNAL_HEADERS = [
'x-middleware-rewrite',
'x-middleware-redirect',
'x-middleware-set-cookie',
'x-middleware-skip',
'x-middleware-override-headers',
'x-middleware-next',
'x-now-route-matches',
'x-matched-path',
]
export const filterInternalHeaders = (headers) => {
for (const header in headers) {
if (INTERNAL_HEADERS.includes(header)) {
delete headers[header]
}
}
}
x-middleware-subrequest wurde dort nicht gefiltert, sodass vom Angreifer kontrollierte Eingaben die Middleware-Laufzeit erreichen konnten. Dieser Wert wurde dann verwendet, um die Rekursionstiefe abzuleiten:
const subreq = params.request.headers['x-middleware-subrequest']
const subrequests = typeof subreq === 'string' ? subreq.split(':') : []
const depth = subrequests.reduce(
(acc, curr) => (curr === params.name ? acc + 1 : acc),
0
)
if (depth >= MAX_RECURSION_DEPTH) {
return {
response: new Response(null, {
headers: {
'x-middleware-next': '1',
},
}),
}
}
Wenn ein Angreifer middleware:middleware:middleware:middleware:middleware sendet, könnte die Laufzeit schlussfolgern, dass die Rekursionstiefe bereits erreicht wurde, und die Anfrage weiterleiten, ohne die Anwendungs-Middleware auszuführen.
docker compose up --build cve-2025-29927-vuln cve-2025-29927-fixed
node scripts/run-cve-2025-29927.mjs http://localhost:3001
node scripts/run-cve-2025-29927.mjs http://localhost:3002
Erwartetes Verhalten:
3001 gibt eine Weiterleitung ohne den Exploit-Header zurück, aber 200 OK mit x-middleware-subrequest.3002 leitet weiterhin zu /login um, da der interne Header von externen Eingaben nicht mehr als vertrauenswürdig angesehen wird.Dieser PoC stellt eine normale Server Action bereit, die serverseitigen Zustand ändert:
'use server'
import { cookies } from 'next/headers'
import { revalidatePath } from 'next/cache'
import { recordTransfer } from '../lib/state'
export async function transferFunds(formData) {
const cookieStore = await cookies()
const session = cookieStore.get('session')?.value
if (!session) {
throw new Error('Victim session cookie is missing.')
}
const amount = Number(formData.get('amount') || '0')
recordTransfer(session, amount)
revalidatePath('/')
}
Das Problem liegt nicht in transferFunds() selbst. Das verwundbare Verhalten lag in der Next.js CSRF-Validierung für Server Actions. In betroffenen Versionen wurde Origin: null wie eine fehlende Herkunft behandelt, anstatt wie eine explizite undurchsichtige Herkunft:
const originHeader = req.headers['origin']
const originDomain =
typeof originHeader === 'string' && originHeader !== 'null'
? new URL(originHeader).host
: undefined
const host = parseHostHeader(req.headers)
if (!originDomain) {
warning = 'Missing `origin` header from a forwarded Server Actions request.'
} else if (!host || originDomain !== host.value) {
if (isCsrfOriginAllowed(originDomain, serverActions?.allowedOrigins)) {
// Ignore it
} else {
const error = new Error('Invalid Server Actions request.')
// ...
}
}
Da 'null' zu undefined wurde, konnten Anfragen von undurchsichtigen Herkünften wie sandboxed iframes den Host/Origin-Vergleichspfad umgehen und dennoch mit den Cookies des Opfers verarbeitet werden.
docker compose up --build cve-2026-27978-vuln cve-2026-27978-fixed
node scripts/run-cve-2026-27978.mjs http://localhost:3003
node scripts/run-cve-2026-27978.mjs http://localhost:3004
Erwartetes Verhalten: