Lỗ hổng Node2Shell CVE-2026-111497 đang được giới quản trị máy chủ chú ý trong cuối tuần này. Lỗi nằm trong bộ giải nén Brotli, thứ được tích hợp sẵn trong Node.js. Theo thông báo bảo mật của Node.js ngày 21/10/2026, dữ liệu Brotli bị làm méo có thể ghi đè bộ nhớ tiến trình, dẫn tới thực thi mã từ xa. Đến 23 và 24/10, các nhà nghiên cứu xác nhận cùng lỗi này còn ảnh hưởng tới Bun, gói Python Brotli, Envoy và Istio.
Tóm tắt nhanh
- Lỗi gì: ghi ngoài vùng nhớ trong bộ giải nén Brotli, Node.js xếp mức High, điểm CVSS 4.0 là 9.2.
- Ảnh hưởng: Node.js 22.x, 24.x, 26.x và thư viện Brotli upstream từ 1.0.3 đến 1.2.0.
- Bản vá: Node.js 22.23.4, 24.22.1, 26.12.1; Bun 1.4.4; Python Brotli 1.2.1, brotlicffi 1.2.1.0.
- Khai thác: PoC công khai từ 22/10/2026, chưa ghi nhận tấn công thực tế theo các nguồn tham khảo.
CVE-2026-111497 hoạt động thế nào?
Theo mô tả của Node.js, khi gặp một lệnh chuyển khối (block switch) không hợp lệ, bộ giải nén lại báo là "cần thêm dữ liệu" thay vì báo lỗi định dạng. Bộ giải nén dạng luồng vì vậy tiếp tục chép dữ liệu vào một bộ đệm nội bộ chỉ có 8 byte, và ghi tràn ra ngoài.
Lỗi do nhóm OpenAI Codex Security (Khai Tran và Sylvie Mayer) báo cáo. Theo Aikido, bản sửa đã có trong mã nguồn Brotli từ tháng 2/2026 nhưng không được đánh dấu là lỗi bảo mật, nên chưa có bản phát hành nào chứa nó cho tới đợt này.
Những đường nào đưa dữ liệu độc vào ứng dụng?
- Request gửi vào: body có header
Content-Encoding: br. Theo Indusface, Express 5 và body-parser 2.x tự giải nén loại này theo mặc định. - Response nhận về: ứng dụng gọi tới máy chủ do kẻ tấn công kiểm soát. Hàm
fetch()tích hợp, axios, node-fetch, got đều tự giải nén. - Mã ứng dụng: truyền tệp hoặc trường dữ liệu từ người dùng vào
zlib.brotliDecompress().
Theo Aikido, Apache với mod_brotli chuẩn và Nginx không giải nén Brotli theo mặc định, nên bản thân web server không phải là điểm yếu.
Phiên bản bị ảnh hưởng và bản vá
| Phần mềm | Bị ảnh hưởng | Bản đã vá |
|---|---|---|
| Node.js | 22.x, 24.x, 26.x trước bản vá | 22.23.4, 24.22.1, 26.12.1 |
| Node.js hết hỗ trợ | 25.x, 23.x, 20.x trở về trước | Không có, phải chuyển nhánh |
| Bun | Bản dùng Brotli cũ | 1.4.4 (cần build lại tệp thực thi đã biên dịch) |
| Python Brotli | 1.2.0 trở về trước | 1.2.1 |
| brotlicffi | 1.2.0.x | 1.2.1.0 |
| Google Brotli | 1.0.3 đến 1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
| Envoy | Các nhánh cũ | 1.40.0, 1.39.3, 1.38.6, 1.37.8 |
| Istio | Các nhánh cũ | 1.29.9, 1.30.6, 1.31.2 |
Lưu ý: gói npm tên brotli không liên quan. Mã lỗi nằm trong runtime Node.js, nên cập nhật gói npm không giải quyết được gì. Bản Node.js do bản phân phối Linux đóng gói và liên kết với thư viện brotli của hệ thống thì cần cập nhật cả gói brotli của bản phân phối.
Cách kiểm tra trên VPS
# Xem phiên bản Node.js đang chạy
node -v
# Theo Indusface, bản đã vá in ra -17
node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH"
# Kiểm tra gói Python trong từng môi trường ảo
pip show Brotli brotlicffi
# Kiểm tra trong image Docker
docker run --rm <image> pip show Brotli brotlicffi
# Tìm gói nào kéo Brotli vào
pipdeptree -r -p Brotli
Aikido lưu ý rằng tính đến 23/10, pip-audit, Dependabot và Safety chưa gắn cờ các phiên bản Brotli trên PyPI bị ảnh hưởng. Vì vậy bạn nên kiểm tra thủ công thay vì chỉ trông vào công cụ quét.
Cập nhật và giảm thiểu
Nâng cấp
# Python
pip install -U "Brotli>=1.2.1"
pip install -U "brotlicffi>=1.2.1.0"
# Docker: build lại image với base image mới
docker build --pull -t app:latest .
Với Node.js, cài bản 22.23.4, 24.22.1 hoặc 26.12.1 bằng trình quản lý bạn đang dùng (nvm, NodeSource, gói của bản phân phối), rồi khởi động lại mọi tiến trình. Không cần khởi động lại hệ điều hành, nhưng tiến trình PM2, systemd hay container còn chạy runtime cũ thì vẫn bị ảnh hưởng.
Biện pháp tạm thời
Node.js cho biết không có biện pháp thay thế chính thức. Các hãng bảo mật đề xuất một số cách giảm rủi ro trong lúc chờ nâng cấp:
- Từ chối request có
Content-Encodingchứabrngay tại reverse proxy hoặc load balancer, trước khi tới ứng dụng. - Bỏ
brkhỏiAccept-Encodingkhi gọi ra ngoài. Theo Aikido, chỉ làm vậy là chưa đủ vì máy chủ độc vẫn có thể trả về Brotli. - Với Python, có thể gỡ Brotli và brotlicffi nếu không cần. Thư viện HTTP sẽ quay về gzip, deflate.
Ví dụ chặn tạm ở Nginx (đặt trong khối server hoặc location proxy tới ứng dụng Node.js). Đây là cách giảm thiểu tạm thời, không phải quy tắc chính thức của Node.js; ứng dụng nào thật sự nhận request body nén Brotli sẽ bị từ chối, nên chỉ dùng trong lúc chưa nâng cấp được:
if ($http_content_encoding ~* "br") {
return 415;
}
Việc nên làm ngay
- Liệt kê mọi nơi có Node.js: VPS, container, CI, cả các nhánh hết hỗ trợ.
- Nâng cấp lên bản vá, build lại image, khởi động lại dịch vụ.
- Rà các môi trường Python có Brotli hoặc brotlicffi.
- Theo dõi log: lỗi giải nén Brotli, tiến trình Node.js bị SIGSEGV, khởi động lại liên tục, hoặc Node.js sinh tiến trình
sh,curl,wgetbất thường.