Phiên bản Node.js của bạn có dính lỗ hổng Brotli RCE? Kiểm tra ngay trong 2 phút
Hãy chạy lệnh này ngay bây giờ. Nếu phiên bản bạn đang dùng thấp hơn mức tối thiểu trong bảng dưới đây, hệ thống của bạn có nguy cơ bị ảnh hưởng bởi lỗ hổng Node.js Brotli RCE (CVE-2026-111497) — và kẻ tấn công hiện đã bắt đầu khai thác nó.
node -v | Dòng phiên bản | Bị ảnh hưởng | Đã vá từ | Mới nhất (05/11/2026) |
|---|---|---|---|
| 26.x | Dưới 26.12.1 | 26.12.1 | 26.13.0 |
| 24.x | Dưới 24.22.1 | 24.22.1 | 24.22.2 |
| 22.x | Dưới 22.23.4 | 22.23.4 | 22.23.4 |
| 25.x, 20.x, cũ hơn | End-of-Life: Chưa được vá và không còn được đánh giá | Không có bản vá | Nâng cấp lên 24 hoặc 26 |
Để kiểm tra thêm lần nữa, bạn có thể truy vấn trực tiếp từ binary:
node -p "require('node:zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH" Bản vá đã bổ sung thêm hằng số này. Chúng tôi đã chạy thử nghiệm trên chín bản build macOS chính thức thuộc ba dòng phiên bản được hỗ trợ: mọi bản build đã vá đều in ra -17, và mọi bản build cũ hơn đều in ra undefined. Tuy nhiên, hãy tin tưởng kết quả từ node -v trước tiên, vì một bản phân phối Linux (distro) khi backport bản vá có thể không thêm hằng số này.
Đừng dựa vào process.versions.brotli. Giá trị này đều trả về 1.2.0 trên cả 24.21.0 lẫn 24.22.1, và 1.1.0 trên cả 22.23.3 lẫn 22.23.4. Node.js đã cherry-pick bản vá mà không thay đổi phiên bản hiển thị, do đó trường thông tin này không thể cho bạn biết hệ thống đã được vá hay chưa.
Tóm tắt nhanh (TL;DR)
| Câu hỏi | Câu trả lời |
|---|---|
| Lỗ hổng này là gì? | Lỗi ghi ngoài vùng bộ nhớ (out-of-bounds write) trong bộ giải mã Brotli đi kèm với Node.js, còn được gọi là Node2Shell hoặc HTTP2Shell |
| Mức độ ảnh hưởng | Gây crash ứng dụng, rò rỉ bộ nhớ (memory disclosure), hoặc thực thi mã từ xa (remote code execution) |
| Mức độ nghiêm trọng | HIGH (Cao) theo Node.js; 9.8 CRITICAL (Nghiêm trọng - CVSS 3.1) theo NVD |
| Đã bị khai thác chưa? | Có. CISA KEV đã đưa lỗ hổng này vào danh mục vào ngày 27 tháng 10 năm 2026 |
| Có cần đăng nhập không? | Không, theo vector của NVD và phân tích từ Akamai |
| Đối tượng nào có nguy cơ? | Các ứng dụng giải mã dữ liệu Brotli không kiểm soát: request body trong Express 5, response của fetch(), các lệnh gọi trực tiếp đến node:zlib |
| Cách khắc phục | Nâng cấp runtime, build lại image, khởi động lại các tiến trình |
| Có giải pháp tạm thời không? | Khuyến cáo chính thức không đề cập giải pháp nào. Các biện pháp đối phó tạm thời bên dưới giúp giảm thiểu phơi nhiễm nhưng không bảo vệ toàn diện |
ELLIO đã ghi nhận lưu lượng khai thác qua các cảm biến bảo mật của mình vào ngày 26 tháng 10, chỉ năm ngày sau khi bản vá được phát hành. Payload thu thập được nhắm mục tiêu vào Node.js 24.22.0 trên kiến trúc x86-64. Mặc dù giai đoạn cuối của payload đó đã thất bại trong các thử nghiệm của ELLIO, ELLIO cũng xác nhận rằng việc thực thi mã hoàn toàn có thể xảy ra trên các bản build 24.22.0 mặc định. CISA đã ấn định hạn chót cho các cơ quan liên bang Hoa Kỳ hành động là ngày 10 tháng 11, đồng thời đánh dấu tình trạng sử dụng trong mã độc tống tiền (ransomware) là "Chưa xác định" (Unknown). Dữ liệu đo kiểm từ xa của Akamai cho thấy tính đến ngày 26 tháng 10, có tới 61% hệ thống mạng vẫn đang chạy các phiên bản bị ảnh hưởng.
Giải mã Node2Shell: Cơ chế hoạt động của lỗ hổng Brotli
Node2Shell là một lỗi ghi ngoài vùng bộ nhớ (out-of-bounds write) nằm trong bộ giải mã Brotli được tích hợp sẵn bên trong Node.js runtime. Một luồng nén có cấu trúc bất thường (malformed) có thể khiến bộ giải mã ghi tràn qua phần cuối của bộ đệm nội bộ 8 byte, dẫn đến làm hỏng bộ nhớ tiến trình. Hậu quả có thể khiến ứng dụng bị crash, rò rỉ dữ liệu bộ nhớ, hoặc mã độc do kẻ tấn công kiểm soát được thực thi ngay trên máy chủ của bạn.
Nguyên nhân gốc rễ bắt nguồn từ việc phân loại sai lỗi. Một thao tác chuyển đổi khối (block switch) không hợp lệ lại được trả về dưới dạng "cần thêm dữ liệu đầu vào" (I need more input) thay vì "dữ liệu này đã bị hỏng" (this data is broken), khiến bộ giải mã dạng luồng (streaming decoder) liên tục ghi thêm byte vào bộ đệm nhỏ đó.
Lỗi này nằm trong thư viện Brotli của Google, từ phiên bản 1.0.3 đến 1.2.0, vì vậy đây không chỉ là vấn đề riêng của Node.js. Node.js đã vá bản sao nội bộ bằng cách cherry-pick commit upstream 0ed4268a vào bản phát hành 24.22.1 cùng các nhánh tương ứng khác. Lỗ hổng được báo cáo bởi hai nhà nghiên cứu Khai Tran và Sylvie Mayer thuộc OpenAI Codex Security.
Các công cụ quét lỗ hổng phụ thuộc (dependency scanner) thông thường sẽ không thể phát hiện ra điều này, bởi bộ giải mã đi kèm trực tiếp trong runtime chứ không phải một package độc lập. Lệnh npm install vẫn báo cáo 0 lỗ hổng cho dự án thử nghiệm Express 5 của chúng tôi khi chạy trên phiên bản Node.js 24.15.0 chưa được vá.
Những đối tượng nào có nguy cơ bị tấn công?
Một runtime chứa lỗ hổng mới chỉ là một nửa điều kiện. Nửa còn lại là phải có một đường dẫn đưa dữ liệu Brotli không đáng tin cậy vào bộ giải mã. Chúng tôi đã thử nghiệm từng đường dẫn trên Node.js 24.22.1 bằng cách gửi một body không hợp lệ vô hại (chuỗi văn bản not-valid-brotli) kèm header Content-Encoding: br, đồng thời phục vụ dữ liệu Brotli hợp lệ cho từng client.
| Đường dẫn / Thư viện | Phiên bản thử nghiệm | Có kích hoạt bộ giải mã? | Kết quả ghi nhận |
|---|---|---|---|
Express 5 express.json() | 5.3.0 (body-parser 2.4.0) | Có, theo mặc định | 400, "Decompression failed" |
Express 4 express.json() | 4.22.3 (body-parser 1.20.9) | Không | 415, unsupported encoding |
Koa @koa/bodyparser | 6.1.0 | Có, theo mặc định | 500, lỗi BrotliDecoder trong log |
Fastify cùng @fastify/compress | 9.3.0 | Có, sau khi đăng ký | 400, FST_CP_ERR_INVALID_CONTENT |
| Fastify thuần | 5.12.8 | Không | 400, invalid JSON |
Server node:http thuần hoặc http.get | 24.22.1 | Không | Dữ liệu byte thô đi qua trực tiếp |
fetch() tích hợp sẵn | 24.22.1 | Có, luôn luôn | Tự động giải mã response |
| axios, got, node-fetch | 1.21.0, 16.2.0, 3.3.2 | Có, theo mặc định | Tự động giải mã response |
zlib.brotliDecompress*, createBrotliDecompress | Mọi phiên bản | Có | Đây chính là decoder API |
Có hai kết quả rất đáng để lưu ý kỹ hơn. Thứ nhất, fetch() vẫn giải mã Brotli ngay cả khi request đã chỉ định Accept-Encoding: identity, bởi vì nó hoạt động theo header của response chứ không phụ thuộc vào yêu cầu của bạn. Bất kỳ máy chủ nào mà ứng dụng của bạn gọi tới đều có thể tiếp cận bộ giải mã, khiến các tính năng như webhook tester, xem trước liên kết (link previewer) hay nhập URL (URL importer) đều gặp rủi ro. Thứ hai, bản thân mã lỗi 400 chưa chứng minh được điều gì: cả Express 5 và Fastify thuần đều trả về mã này, nhưng chỉ có Express 5 là thực sự chạy qua bộ giải mã. Hãy đọc kỹ thông báo lỗi chi tiết.
Bạn có thể xác định các đường dẫn phơi nhiễm trong dự án bằng ba lệnh sau:
# Direct decoder use grep -rnE "brotliDecompress|createBrotliDecompress" src/ # Parsers that decode request bodies npm ls express body-parser @koa/bodyparser @fastify/compress # Base images and pinned versions grep -rnE "^FROM .*node" --include='Dockerfile*' . Chỉ cần thêm một lệnh gọi fetch() mới cũng có thể làm thay đổi hoàn toàn cục diện, vì vậy dù hiện tại "không tiếp cận được", giải pháp tối ưu vẫn luôn là cập nhật bản vá.
Cách khắc phục và cập nhật bản vá cho CVE-2026-111497
Hãy nâng cấp lên bản build mới nhất trong dòng phiên bản bạn đang dùng, chứ đừng chỉ dừng lại ở mức tối thiểu. Tính đến ngày 5 tháng 11, các nguồn cài đặt sau đây đều đã có bản build được vá lỗi:
| Phương thức cài đặt | Hành động cần thực hiện |
|---|---|
| nvm | nvm install 24 && nvm alias default 24 |
| Homebrew | brew upgrade node (hoặc node@24, node@22) |
| Docker | FROM node:24.22.2-slim, sau đó chạy docker build --pull |
| Gói phân phối Linux (distro) | Làm theo khuyến cáo bảo mật của bản phân phối bạn dùng, vì họ có thể backport bản vá dưới số hiệu phiên bản cũ hơn |
| CI | Cập nhật .nvmrc, engines và node-version, sau đó rebuild lại |
| Serverless | Ghi log process.version từ function đã triển khai; đừng mặc định là nền tảng đám mây đã tự động cập nhật |
Tiếp theo, hãy xử lý hai sơ hở phổ biến mà nhiều người thường bỏ sót:
- Khởi động lại toàn bộ tiến trình. Một tiến trình đang chạy sẽ tiếp tục giữ binary cũ trong bộ nhớ, do đó việc nâng cấp trên ổ đĩa sẽ không có tác dụng gì cho đến khi pm2, systemd hoặc bộ điều phối (orchestrator) của bạn khởi động lại nó.
- Rebuild lại những gì đã phân phối. Các image cũ vẫn chứa runtime cũ, và những image được ghim theo mã băm digest cần phải được quét lại vì tag sẽ không thể hiện được điều đó.
Cuối cùng, hãy chạy lại các bước kiểm tra ở đầu bài viết này bên trong container đã triển khai, sau đó tiếp tục kiểm tra trên CI runner và máy tính của lập trình viên. Để kiểm tra bản build bảo mật mới nhất cho từng dòng phiên bản:
curl -s https://nodejs.org/dist/index.json | jq -r '[.[] | select(.security == true and (.version | test("^v(22|24|26)\\.")))] | group_by(.version | split(".")[0]) | .[] | max_by(.date) | "\(.version) \(.date)"' Bạn vẫn đang dùng Node.js 20 hoặc 25? Sẽ không có bản build nào được phát hành cho bạn. Hãy tham khảo kế hoạch End-of-Life của Node.js 20 và tiến hành nâng cấp ngay.
Các giải pháp giảm thiểu rủi ro nếu chưa thể cập nhật ngay hôm nay
Những giải pháp này chỉ giúp bạn có thêm thời gian. Không biện pháp nào có thể thay thế hoàn toàn việc nâng cấp.
- Từ chối
brngay tại tầng biên (edge). ChặnContent-Encoding: brtại proxy hoặc bộ cân bằng tải của bạn, lưu ý kiểm tra không phân biệt chữ hoa/thường và quét trong cả danh sách giá trị nhưgzip, br. - Từ chối ngay trong ứng dụng. Đặt đoạn middleware này trước các body parser. Trong thử nghiệm Express 5 của chúng tôi, đoạn mã này trả về mã 415:
app.use((req, res, next) => { const encoding = req.headers['content-encoding'] || ''; if (/\bbr\b/i.test(encoding)) return res.status(415).end(); next(); }); - Tắt tính năng giải nén tự động. Cấu hình
express.json({ inflate: false })cũng trả về mã lỗi 415 cho cùng request đó. Thiết lập này cũng sẽ từ chối cả body nén bằng gzip, vì vậy hãy kiểm tra kỹ các client của bạn trước. - Kiểm soát các response chiều ra (outbound). Thư viện axios hỗ trợ tùy chọn
decompress: false, giúp giữ nguyên các byte chưa giải mã trong thử nghiệm của chúng tôi.fetch()thì không có công tắc chuẩn nào để tắt, do đó bạn nên giới hạn các host mà nó có thể truy cập bằng danh sách cho phép (allowlist) và các quy tắc egress. - Giới hạn phạm vi ảnh hưởng (blast radius). Phân tách mạng cho các dịch vụ Node.js, đồng thời giới hạn quyền đọc các tệp core dump, vì ELLIO đã lưu ý rằng sự cố crash có thể làm lộ dữ liệu trong bộ nhớ tại đó.
Hãy đặc biệt chú ý đến dấu hiệu mà ELLIO đã phát hiện đầu tiên: những đợt bùng nổ các request body nén bằng Brotli nhằm thăm dò xem máy chủ nào giải mã chúng. Lưu lượng Content-Encoding: br bất thường gửi tới một JSON API, hoặc sự gia tăng đột biến các trường hợp worker bị crash, là những tín hiệu đáng để bạn điều tra ngay lập tức.
Kiểm tra API hoạt động ổn định sau nâng cấp: Hãy để AI agent đảm nhận
Việc chuyển đổi phiên bản runtime có thể làm thay đổi hành vi của TLS, DNS và HTTP, và bản phát hành tháng 10 vừa qua đã tác động đến cả ba thành phần này. Đừng so sánh thủ công. Hãy để coding agent của bạn chạy Apidog CLI trước và sau khi nâng cấp, rồi báo cáo những điểm có sự thay đổi.
npm install -g apidog-cli@latest apidog login --with-token <YOUR_TOKEN> npx -y skills add https://apidog.com # pick your agent when prompted Đưa câu lệnh (prompt) này cho agent của bạn:
Run Apidog test suite <id> in project <id> against staging and save the results to reports/before. After I upgrade Node.js, add a test case for POST /orders that sends the plain text "not-valid-brotli" with Content-Encoding: br, asserts a 4xx status, then checks GET /health returns 200. Rerun the suite to reports/after and list every request whose result changed. Agent sẽ thực thi các lệnh sau:
apidog run --test-suite <id> --project <id> -e <envId> -r cli,junit --out-dir reports/before apidog cli-schema get test-case-create apidog cli-schema validate test-case-create --file case.json apidog test-case create --project <id> --file case.json apidog run --test-case <caseId> --project <id> -e <envId> -r cli,json apidog run --test-suite <id> --project <id> -e <envId> -r cli,junit --out-dir reports/after Test case bổ sung đó thực chất là một bài kiểm tra tình trạng (health check), không phải mã khai thác: phần body là văn bản thuần túy, và trong thử nghiệm của chúng tôi, nó đều thất bại với cùng một lỗi padding trên cả bản build bị lỗi lẫn bản build đã vá. Tuy nhiên, nó giúp phát hiện một lỗ hổng xử lý thực tế: bài test Koa của chúng tôi đã trả về lỗi 500 do lỗi giải mã không được xử lý, trong khi một public API đúng chuẩn phải phản hồi bằng mã 4xx. Hãy chỉ chạy thử nghiệm này trên môi trường staging.
Tài liệu tham khảo lệnh run liệt kê đầy đủ các cờ tùy chọn, và hướng dẫn CI/CD sẽ giúp bạn đưa quy trình chạy JUnit tương tự vào pipeline của mình. Gói miễn phí của Apidog hỗ trợ tối đa 4 người dùng.
Vì sao mốc thời gian 11 tháng 11 lại đáng chú ý tiếp theo?
Dự án Node.js đã thông báo trước về một bản phát hành mới cho các dòng 26.x, 24.x và 22.x vào khoảng hoặc ngay sau ngày 11 tháng 11 năm 2026, tiếp tục ở mức độ nghiêm trọng cao nhất là HIGH. Hiện vẫn chưa có mã CVE nào được công bố. Hướng dẫn về bản phát hành tháng 11 của chúng tôi sẽ trình bày cách thức chuẩn bị.
Hãy vá lỗ hổng Brotli ngay từ bây giờ thay vì chờ đợi để gộp chung vào đợt cập nhật đó. Lỗ hổng này hiện đã có thông tin chi tiết công khai và lưu lượng khai thác thực tế trên mạng, hơn nữa quá trình nâng cấp bạn thực hiện trong tuần này sẽ đóng vai trò như một buổi diễn tập cho đợt tiếp theo: cùng quy trình kiểm kê, cùng thao tác rebuild image, cùng bộ test suite. Các lỗ hổng PHP2Shell và React2Shell trước đây cũng từng diễn biến theo chiều hướng tương tự.
Câu hỏi thường gặp (FAQ)
CVE-2026-111497 đã bị khai thác trong thực tế chưa? Có. CISA đã thêm lỗ hổng này vào danh mục Known Exploited Vulnerabilities vào ngày 27 tháng 10 năm 2026 dưới mục Google Brotli, và ELLIO đã ghi nhận lưu lượng tấn công từ ngày 26 tháng 10.
Lỗ hổng này ở mức High hay Critical? Cả hai cách gọi đều có căn cứ thực tế. Node.js xếp loại ở mức HIGH. NVD chấm điểm 9.8 CRITICAL theo thang điểm CVSS 3.1, trong khi điểm số CVSS 4.0 do Google công bố là 9.2. Hãy lên kế hoạch dựa trên thực tế là lỗ hổng này đang bị khai thác.
Tôi đang dùng Express 4 thì có an toàn không? Phần request body của bạn không tiếp cận bộ giải mã, nhưng các lệnh gọi fetch(), axios và trực tiếp từ node:zlib thì vẫn có thể tiếp cận. Bản phát hành này cũng đã vá thêm chín lỗ hổng CVE khác, vì vậy dù sao bạn vẫn nên nâng cấp.
Node.js 20 có bị ảnh hưởng không? Dự án Node.js không đánh giá các dòng phiên bản đã kết thúc vòng đời (End-of-Life), và chúng sẽ không nhận được bản vá nào. Hãy coi các phiên bản 20.x và 25.x là chưa được vá và không được đánh giá, sau đó tiến hành nâng cấp.
Lỗ hổng này có ảnh hưởng đến nền tảng nào khác ngoài Node.js không? Có. Khuyến cáo bảo mật của GitHub đề cập đến các package PyPI brotli và brotlicffi, các nhà cung cấp cũng đã ghi nhận bản vá cho Bun 1.4.4 và .NET. Bạn cần cập nhật riêng từng bộ giải mã, vì việc nâng cấp Node.js chỉ bảo vệ bản sao đi kèm của chính nó.




