漏洞成因
此漏洞是發生在Next.js的middleware上,版本為v14.2.25
之前的v14
與v15.2.3
之前的v15
middleware作用
Middleware是一段在 Request(請求)到達實際頁面或 API Route 之前就會執行的程式碼,常見用處如下:
- 身分驗證:檢查用戶Cookie、攔截未授權請求
- 動態修改請求路徑,像是多語言路由或是將舊路徑導向新路徑
- 安全header設置:添加CSP、CORS等
主要成因
用戶的請求在middleware會經過身分認證,像是在下面的範例程式碼中,如果通過帳號和密碼的驗證,則會觸發NextResponse.next()
將請求向前發送,否則就會從定向回/login
若身分驗證本身也會經過middleware(像是發請求到/login
時也會經過middleware),則會造成無窮迴圈,因此需要機制來遇到重複的middleware時就讓請求向前。而這正是漏洞成因
const subreq = params.request.headers[`x-middleware-subrequest`]const subrequests = typeof subreq === 'string' ? subreq.split(':') : []
if (subrequests.includes(middlewareInfo.name)) { result = { response: NextResponse.next(), waitUntil: Promise.resolve(), } continue}
此段用處是防止middleware在子請求(subrequest)中,不斷遞迴呼叫自己,但只要header裡有包含
middlewareInfo.name
就會直接放行
const subreq = params.request.headers[`x-middleware-subrequest`]const subrequests = typeof subreq === 'string' ? subreq.split(':') : []
const MAX_RECURSION_DEPTH = 5const depth = subrequests.reduce((acc, curr) => (curr === params.name ? acc + 1 : acc),0)
if (depth >= MAX_RECURSION_DEPTH) {return { waitUntil: Promise.resolve(), response: new runtime.context.Response(null, { headers: { 'x-middleware-next': '1', }, }),}}
這段是改良過後,要累積五次才會直接放通行,但基本沒什麼變
middlewareInfo.name
在較新版之後不是middleware
就是src/middleware
,因此只要header裡的x-middleware-subrequest
有重複五次的middleware
就可以繞過驗證(src/middleware
同理)
漏洞復現
環境:https://github.com/vulhub/vulhub/tree/master/next.js/CVE-2025-29927
Next.js版本:v15.2.2
docker-compose.yml
services: web: image: vulhub/nextjs:15.2.2 ports: - "3000:3000" environment: - NODE_ENV=production
啟動環境
docker-compose up -d
過程
將docker啟動後可以經由3000 port連上登入頁面
如果想要請求到/
目錄,也就是dashboard
則會被從定向回/login
這時加上x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
則可直接繞過驗證,成功進入Admin Dashboard
漏洞修復
此漏洞最主要的修復方式是檢查是不是外部來的x-middleware-subrequest
,如果是的話就自動刪除
// If this request didn't origin from this session we filter// out the "x-middleware-subrequest" header so we don't skip// middleware incorrectlyif ( header === 'x-middleware-subrequest' && headers['x-middleware-subrequest-id'] !== (globalThis as any)[Symbol.for('@next/middleware-subrequest-id')]) { delete headers['x-middleware-subrequest']}
Reference
Next.js and the corrupt middleware: the authorizing artifact
https://www.cnblogs.com/CVE-Lemon/p/18797265