Node.js 漏洞:Node2Shell 修補程式與 Node.js 版本確認(WordPress)

Node2Shell 是 2026 年 10 月 21 日公開修補程式的 Node.js 漏洞 CVE-2026-111497 的別稱,官方標題描述的是 Google Brotli 解碼器的越界寫入缺陷。由於解壓縮經過特製的 Brotli 壓縮資料時就可能導致遠端程式碼執行,第一步是確認伺服器上 Node.js、Bun 與 libbrotli 的版本,並升級到修補版本。
這是在安裝 WordPress 7.1.3 的 Nginx + Apache 架構伺服器上,確認使用 Brotli 解碼器的套件,以及 WordPress 遠端請求經過 libbrotli 的路徑的過程。
1️⃣ Node.js 漏洞 Node2Shell:缺陷原理與影響範圍
Node2Shell 這個名稱,是資安公司 Imperva 的研究團隊在 10 月 22 日的研究文章中取的別稱。nodejs.org 公告的標題是 [Brotli decoder out-of-bounds write permits remote code execution]。缺陷位於 Node.js 內建的 Google Brotli 解碼器原始碼(c/dec/decode.c),因此修改應用程式的程式碼也擋不住。
根據 NVD 紀錄,Google Brotli 1.0.3~1.2.0 的解碼器在解壓縮經過特製的壓縮串流時,會寫入超出緩衝區結尾的位置,破壞相鄰的解碼器狀態記憶體。未登入的遠端攻擊者可以利用這個缺陷讀出伺服器記憶體的內容、使服務中斷,並執行任意程式碼。
嚴重程度的標示有兩種。nodejs.org 公告評為 High,發布 CVE 的 Google 則依 CVSS 4.0 給出 9.2 分(Critical)。截至 10 月 23 日,NVD 尚未完成自己的分析,因此還沒有 NVD 分數。弱點類型是未檢查回傳值(CWE-252)與越界寫入(CWE-787)。
根據 nodejs.org 安全公告,Node.js 22.x、24.x、26.x 這些仍在支援的版本線全部受影響,修補版本是 22.23.4、24.22.1、26.12.1。同一份公告也指出,已結束支援的版本線在每次發布安全公告時一律視為受影響。
使用同一個解碼器的程式,即使在 Node.js 之外也都是對象。Google 在 10 月 21 日推出修正同一個 CVE 的 Brotli 函式庫 1.0.10、1.1.1、1.2.1 三個版本。Bun 在 10 月 22 日的 1.4.4 公告中,把 Brotli 列入安全修正清單,儲存庫的 issue 也指出該版本包含更新 brotli 的修正。
| 目的地 | 影響範圍 | 修補版本 | 依據 |
|---|---|---|---|
| Node.js | 22.x · 24.x · 26.x 所有支援中的版本線 · 已結束支援的版本線 | 22.23.4 · 24.22.1 · 26.12.1 | nodejs.org 安全公告(2026-10-21) |
| Google Brotli 函式庫 | 1.0.3 ~ 1.2.0 | 1.0.10 · 1.1.1 · 1.2.1 | NVD 紀錄 · google/brotli 發行版本(2026-10-21) |
| Bun | 1.4.4 公告的安全修正清單中有 Brotli | 1.4.4 | Bun 公告(2026-10-22) |
| 發行版的 libbrotli 套件 | 以 Brotli 1.0.3~1.2.0 為基礎製作的套件 | 發行版安全公告推出的版本(截至 2026-10-23 尚未推出) | 各發行版的安全追蹤頁面 |
Node.js 本身不會自行解壓縮收到的 Brotli 資料。根據主機業者 Vercel 的安全公告,暴露在風險中的位置是應用程式或函式庫解壓縮來自外部的壓縮資料之處。例如內建的 fetch() 收到 br 回應時,以及 Express 5、body-parser 2 解壓縮 br 請求本文時;只負責接收的預設 HTTP 伺服器不會解壓縮。
截至 10 月 23 日,尚未確認有實際遭到利用的情形。CISA 的已知遭利用漏洞目錄(KEV,10 月 21 日版本)中沒有這個 CVE。CISA 在 CVE 紀錄中標示的利用階段,已在 10 月 23 日改為 [概念驗證(PoC)公開]。
解碼器放在哪裡,決定了要做的事。Node.js 官方執行檔把解碼器內建在執行檔裡,也有像 Debian 的 nodejs 套件那樣使用系統 libbrotli 的版本。Linux 發行版則把同樣的原始碼安裝成 libbrotli 套件,讓 curl 等程式共用。
2️⃣ WordPress 與 PHP 網站也受影響:遠端請求用 libbrotli 解壓縮回應的路徑
即使是沒有安裝 Node.js 的 WordPress 伺服器,也有以 libbrotli 套件形式存在的同一個解碼器。截至 10 月 23 日,WordPress、curl、PHP 方面都沒有針對這個 CVE 的官方說明,curl 官方漏洞清單中也沒有這個 CVE。因此本章只透過核心程式碼、curl 文件與測試伺服器的實測確認結構。
在實測的 Nginx + Apache 架構伺服器上,需要解碼器函式庫(libbrotlidec)的套件有五個,其中與 WordPress 相連的是 libcurl。
rpm -q --whatrequires 'libbrotlidec.so.1()(64bit)' | sed 's/-[0-9].*//'freetype
brotli-devel
libjxl
libcurl
libsouplibcurl 需要 libbrotli,是為了解壓縮經過壓縮的 HTTP 回應。根據 curl 官方文件,與 brotli 函式庫一起建置的 libcurl 會自動解壓縮 Content-Encoding 為 br 的回應。PHP 的 curl 擴充功能會直接呼叫這個 libcurl。
ldd /usr/lib64/libcurl.so.4 | grep brotli libbrotlidec.so.1 => /lib64/libbrotlidec.so.1 (0x00007f32803ed000)
libbrotlicommon.so.1 => /lib64/libbrotlicommon.so.1 (0x00007f327f858000)php -i | grep BROTLIBROTLI => YesWordPress 使用的 libcurl 版本,在管理後台的 [工具 › 網站狀態] 的 [網站狀態資訊] 分頁展開 [伺服器] 區塊後,會顯示在 [cURL 版本] 這一列。這一列不會顯示是否支援 brotli,所以要像上面一樣用伺服器指令確認。
圖中的 [cURL 版本] 列會顯示 libcurl 版本,以及一起建置的 SSL 函式庫。根據 curl 文件,br 從 7.57.0 開始支援,所以這個值低於 7.57.0 就無法解壓縮 br 回應。若在此版本以上,就用伺服器指令確認建置時是否加入了 brotli。
WordPress 核心的 Requests 函式庫在向外部網址發出請求時,會把 curl 控制代碼的 [CURLOPT_ENCODING] 值設為空字串。根據 curl 文件,空字串代表接受該 libcurl 支援的所有壓縮方式。因此,對方伺服器若送來以 br 壓縮的回應,就會由 WordPress 伺服器的 libbrotli 解壓縮。
grep -n CURLOPT_ENCODING wp-includes/Requests/src/Transport/Curl.php110: curl_setopt($this->handle, CURLOPT_ENCODING, '');
212: curl_setopt($this->handle, CURLOPT_ENCODING, 'none');第 110 行是所有請求的預設值,第 212 行只會在解壓縮失敗的請求改以不壓縮的方式重送時執行。從外部無法直接攻擊這個路徑,必須先控制 WordPress 會從中取得回應的對方伺服器,例如核心與外掛的更新檢查、oEmbed 或 RSS 訂閱來源。
流程從來自外部的 br 壓縮資料開始。在 Node.js 的 fetch()、Express 5、Bun 的 fetch(),或 WordPress 的 libcurl 呼叫的 libbrotli 解壓縮這些資料的階段觸發缺陷,結果導致在伺服器上執行程式碼。修補版本會擋下這個流程,所以要做的是把解碼器換成修補版本。
Apache 的 mod_brotli 只連結用來壓縮回應的編碼器函式庫(libbrotlienc),因此用這個模組開啟壓縮的設定不在解碼器缺陷的路徑上。
ldd /usr/lib64/httpd/modules/mod_brotli.so | grep brotli libbrotlienc.so.1 => /lib64/libbrotlienc.so.1 (0x00007f34b4b79000)
libbrotlicommon.so.1 => /lib64/libbrotlicommon.so.1 (0x00007f34b4a79000)3️⃣ 確認 Node.js 版本:伺服器上有沒有 Node.js、Bun 與 libbrotli,以及各自的版本
要做的事從清點伺服器上裝了什麼開始。Node.js 用 [node -v] 查看版本,數值低於所屬版本線的 22.23.4、24.22.1 或 26.12.1 就在影響範圍內。
| 你的環境 | 確認指令 | 修正的位置 |
|---|---|---|
| nodejs.org 安裝檔 · nvm · Docker 官方映像 | [node -v] | Node.js 本身(22.23.4 · 24.22.1 · 26.12.1) |
| 發行版 nodejs · ldd 中有 libbrotli 列 | [ldd $(command -v node)] | libbrotli 套件 |
| 發行版 nodejs · ldd 結果 0 | [ldd $(command -v node)] | nodejs 套件(發行版安全公告之後) |
| 共用主機的 Node.js Selector(CloudLinux alt-nodejs) | 業者公告 · 套件 release 編號 | 業者發布的套件 + 重新啟動應用程式 |
| Bun | [bun --version] | Bun 1.4.4 以上 |
| 沒有 Node.js(只有 WordPress) | 用 [ldd] 確認 libcurl | libbrotli 套件 + 重新啟動 php-fpm · 在那之前採取暫時措施 |
| 代管型主機 | 業者公告 · 詢問 | 業者升級的套件 |
PC(Windows、macOS)使用 nodejs.org 的修補版本安裝檔;nvm 先用 [nvm ls] 查看版本,再像 [nvm install 24] 這樣以版本線編號重新安裝。Docker 官方 node 映像的 22、24、26 標籤已在 10 月 22 日以修補版本重新建置,因此要重新拉取映像、建置並部署。Vercel 公告也建議把自行建置的容器映像、CI 執行環境與本機開發環境升級到修補版本。
以 dnf module 串流 安裝 Node.js 的 伺服器,[node -v] 會顯示該串流版本線的版本;即使版本線相同,末端數字未達修補版本也必須升級。
node -vv24.xx.xNode.js 使用系統的 libbrotli 還是內建解碼器,可以用 [ldd] 確認。輸出中有 libbrotlidec 列,就是使用共用函式庫的版本;像下面這樣是 0,就是內建解碼器,必須升級 Node.js 本身才能修正。
ldd $(command -v node) | grep -c brotli0Bun 用 [bun --version] 確認版本,低於 1.4.4 就升級。
在 RPM 系列中,用 [rpm -q libbrotli] 查看 libbrotli 套件的版本。套件版本號是發行版自己編的,所以不要直接和 Google 的發行版本號比較,而是和發行版安全公告針對這個 CVE 編號推出的版本比較。
rpm -q libbrotlilibbrotli-1.0.x-x.el9_x.x86_64發行版是否已發布公告,用 [dnf updateinfo info --cve CVE-2026-111497] 確認。輸出若是空的,代表該儲存庫還沒有這個 CVE 的公告。
dnf updateinfo info --cve CVE-2026-111497在版本號不變的情況下只移植修正的套件,會在變更紀錄中留下 CVE 編號。因此用 [rpm -q --changelog libbrotli | grep CVE-2026-111497] 確認,有出現一列就是已修補的套件。
rpm -q --changelog libbrotli | grep CVE-2026-111497截至 10 月 23 日,Debian、Ubuntu、Red Hat、AlmaLinux 的 libbrotli 套件都還沒有這個 CVE 的修補版本。Alpine 只有 nodejs 套件推出了修補版本,依據是各發行版的安全追蹤頁面。所以目前的順序是留意發行版是否發布安全公告,發布後再升級。
Debian、Ubuntu 系列的套件名稱是 libbrotli1,並在某家代管型主機的 Debian 12 測試伺服器上確認了相同項目。這台伺服器同樣是 libcurl 連結 libbrotlidec,PHP 的 curl 擴充功能也支援 brotli。變更紀錄用 [zcat /usr/share/doc/libbrotli1/changelog.Debian.gz | grep CVE-2026-111497] 查看。
dpkg -l libbrotli1 | tail -1ii libbrotli1:amd64 1.1.0-2+0~20240305.8+debian12~1.gbp62df71 amd64 library implementing brotli encoder and decoder (shared libraries)ldd /usr/lib/x86_64-linux-gnu/libcurl.so.4 | grep brotli libbrotlidec.so.1 => /lib/x86_64-linux-gnu/libbrotlidec.so.1 (0x00007fa2baac6000)
libbrotlicommon.so.1 => /lib/x86_64-linux-gnu/libbrotlicommon.so.1 (0x00007fa2b9cb9000)4️⃣ 更新 Node.js:升級到修補版本並重新確認
代管型主機和共用主機的伺服器套件由業者升級,所以要透過業者公告或詢問,確認 Node.js 與 libbrotli 是否已修補。若是自行管理伺服器,Node.js 就升級到 nodejs.org 公告的修補版本,Bun 則用 [bun upgrade] 升級到 1.4.4 以上。
以發行版模組串流安裝的 Node.js,要等修補版本進入該儲存庫後,再以 [dnf upgrade nodejs] 的形式升級。直接從 nodejs.org 下載安裝的伺服器,則換成同一處的修補版本。已結束支援的版本線不會推出修補版本,所以要移到仍在支援的 22、24、26 版本線。
共用主機的 Node.js Selector(CloudLinux alt-nodejs)另外內含一份 brotli,因此升級系統的 libbrotli 也修不好。根據 CloudLinux 公告,必須換成業者發布的套件後重新啟動應用程式,新的執行檔才會執行。同一份公告也指出,已結束支援的版本 [node --version] 的值不會改變,所以要以套件的 release 編號確認。
libbrotli 套件要在發行版安全公告推出後升級該套件,並重新啟動已把函式庫載入記憶體的處理程序,才會換成新的函式庫。共用函式庫是在處理程序啟動時讀取,所以在 WordPress 伺服器上,連同重新啟動 php-fpm 才算一個完整步驟。
升級後,確認 [node -v] 是否為修補版本以上,以及 [rpm -q libbrotli] 是否為發行版公告的版本。變更紀錄中是否已加入 CVE 編號,用 [rpm -q --changelog libbrotli | grep CVE-2026-111497] 確認。重新啟動 php-fpm 後,也要確認服務狀態與網站回應。
systemctl is-active php-fpmactivecurl -s -o /dev/null -w '%{http_code}\n' 'https://example.com/?r=1'2005️⃣ 修補前的暫時措施:關閉 WordPress 遠端請求的 br 解壓縮
Red Hat 的 CVE 說明建議設定應用程式,不接受或不自動解壓縮來自不可信來源的 br 壓縮。
在修補套件推出之前,現在就能擋下 WordPress 端的路徑。WordPress 提供每次遠端請求都會傳入 curl 控制代碼的 [http_api_curl] 掛鉤,在這裡把 [CURLOPT_ENCODING] 設為 null,就會關閉 libcurl 的自動解壓縮。
把下面的檔案放進 [wp-content/mu-plugins/] 資料夾,就會套用到所有遠端請求,檔名只是範例。要復原時刪除這個檔案即可。
<?php
// no-brotli-decode.php — WordPress 遠端 請求中 libcurl 自動 解壓縮 關閉(libbrotli 修補 版本 推出 為止)
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'http_api_curl', function ( $handle ) {
curl_setopt( $handle, CURLOPT_ENCODING, null );
}, 99 );很容易以為把 [CURLOPT_ENCODING] 縮小為 [gzip, deflate] 就能排除 br。但這個值只是把 br 從請求標頭的 Accept-Encoding 中拿掉,對方伺服器若用 br 回應,libcurl 仍會照收到的內容解壓縮。在測試網站用同樣的值取得 httpbin.org/brotli,結果 Content-Encoding 為 br 的回應照樣被解壓縮並可讀取。
根據 curl 文件,能關閉自動解壓縮的值只有 null;設為 null 時不會送出 Accept-Encoding 標頭,收到的本文也不會解壓縮。gzip、deflate 回應由 WordPress 的 Requests 函式庫自行解壓縮,照樣可以讀取;只有 br 回應會維持未解壓縮的狀態。
這個掛鉤是 WordPress 接在 [curl.before_send] 位置上的,Requests 會在執行 curl 前一刻呼叫這個位置。它在 Curl.php 第 174 行被呼叫、在第 205 行執行,因此核心在第 110 行放入的空字串值會在兩者之間被覆寫。
是否生效,用下面的檢查檔確認。httpbin.org 是會回傳收到的請求標頭、也會回傳以 br 與 gzip 壓縮之回應的測試網址,可以一次看出 WordPress 送出什麼、解壓縮什麼。
<?php
// br-check.php — WordPress 遠端 請求 哪種 壓縮 傳送 是否解壓縮 確認(httpbin.org 測試 地址)
$h = json_decode( wp_remote_retrieve_body( wp_remote_get( 'https://httpbin.org/headers' ) ), true );
echo 'Accept-Encoding: ', isset( $h['headers']['Accept-Encoding'] ) ? $h['headers']['Accept-Encoding'] : '(none)', PHP_EOL;
foreach ( array( 'brotli', 'gzip' ) as $p ) {
$r = wp_remote_get( 'https://httpbin.org/' . $p );
$readable = strpos( wp_remote_retrieve_body( $r ), ': true' ) !== false;
echo $p, ': Content-Encoding=', wp_remote_retrieve_header( $r, 'content-encoding' ), ' / ', strlen( wp_remote_retrieve_body( $r ) ), ' bytes / ', ( $readable ? 'readable' : 'not decoded' ), PHP_EOL;
}放入檔案之前,Accept-Encoding 中有 br,br 回應也會被解壓縮並可讀取。
wp eval-file br-check.phpAccept-Encoding: deflate, gzip, br
brotli: Content-Encoding=br / 316 bytes / readable
gzip: Content-Encoding=gzip / 317 bytes / readable放入檔案之後,Accept-Encoding 標頭消失,br 回應維持未解壓縮,gzip 回應則照樣可以讀取。
wp eval-file br-check.phpAccept-Encoding: (none)
brotli: Content-Encoding=br / 199 bytes / not decoded
gzip: Content-Encoding=gzip / 272 bytes / readable升級到發行版已修補的 libbrotli 套件並完成 php-fpm 重新啟動後,刪除這個檔案即可恢復原狀。
🔢 FAQ & 推薦內容
ℹ️ 聯盟行銷說明
本站內容包含聯盟行銷連結。訪客透過這些連結購買商品或服務時,本站會從賣家獲得佣金。在這個過程中,購買者支付的金額(遇到活動折扣時金額會降低↓)並不會因此提高。刊登的價格、折扣與庫存資訊以撰寫當時為準,可能與實際情況不同,購買前請向賣家做最後確認。商品的挑選與評價依本站自訂的標準撰寫,是否支付佣金不會影響介紹順序或評價內容。