613 字
3 分鐘
CVE-2025-29927
2025-10-19

漏洞成因#

此漏洞是發生在Next.js的middleware上,版本為v14.2.25之前的v14v15.2.3之前的v15

middleware作用#

Middleware是一段在 Request(請求)到達實際頁面或 API Route 之前就會執行的程式碼,常見用處如下:

  1. 身分驗證:檢查用戶Cookie、攔截未授權請求
  2. 動態修改請求路徑,像是多語言路由或是將舊路徑導向新路徑
  3. 安全header設置:添加CSP、CORS等

主要成因#

用戶的請求在middleware會經過身分認證,像是在下面的範例程式碼中,如果通過帳號和密碼的驗證,則會觸發NextResponse.next()將請求向前發送,否則就會從定向回/login image

若身分驗證本身也會經過middleware(像是發請求到/login時也會經過middleware),則會造成無窮迴圈,因此需要機制來遇到重複的middleware時就讓請求向前。而這正是漏洞成因

v12.0.7漏洞原始碼

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就會直接放行

v15.2.2漏洞原始碼

const subreq = params.request.headers[`x-middleware-subrequest`]
const subrequests = typeof subreq === 'string' ? subreq.split(':') : []
const MAX_RECURSION_DEPTH = 5
const 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連上登入頁面 image

如果想要請求到/目錄,也就是dashboard則會被從定向回/login image

這時加上x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware

image

則可直接繞過驗證,成功進入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 incorrectly
if (
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

https://github.com/vercel/next.js

Next.js Middleware Authorization Bypass (CVE-2025-29927)

CVE-2025-29927
https://fuwari.vercel.app/posts/cve-2025-29927/
作者
hsu_K
發佈於
2025-10-19
許可協議
CC BY-NC-SA 4.0