5 phút đọc

Lỗ hổng Brotli của Node.js đã chạy được mã trên bản chính thức, và Python cũng dính

CVE-2026-111497 (CVSS 9,2) nằm trong bộ giải nén Brotli bằng C mà Node.js, Bun, gói Brotli của Python và nhiều phần mềm khác cùng dùng. Imperva đã chạy được mã trên Node.js 24.22.0 bản chính thức. Nâng Node.js, nâng gói Brotli và brotlicffi trong Python, rồi build lại image.

Xem nhanh trong 1 phút

App Python của bạn chưa bao giờ import brotli, nhưng nếu gói Brotli nằm trong môi trường thì mỗi lần requests.get() gọi ra ngoài, nó vẫn có thể đang giải nén Brotli. Đó là chi tiết làm mình phải viết lại về lỗ hổng mà blog đã đưa hôm 21/10.

Robot linh vật ôm hai gói hàng bị ép chặt, một gói nứt tỏa tia đỏ

Hôm đó bài Node.js vá 10 lỗ hổng coi CVE-2026-111497 là chuyện của Node.js, và lúc ấy chưa ai cho thấy nó chạy được mã thật. Năm ngày sau, hai điều đã khác: có người chạy được mã trên bản Node.js chính thức, và lỗi hóa ra nằm ở thư viện Brotli viết bằng C của Google, thứ mà rất nhiều phần mềm khác cũng đóng gói bên trong. Giới bảo mật đang gọi nó bằng mấy cái tên Node2Shell, HTTP2Shell, Broccoli2Shell.

Đã có người chạy được mã

Theo The Hacker News, nhóm Red Team của Imperva chạy được mã của họ trên image Node.js 24.22.0 chính thức cho Linux arm64, không sửa gì, không cần trình gỡ lỗi. Không phải lần nào cũng trúng, họ phải thử vài lần, và Imperva không công bố mã khai thác. Bản 24.22.1 từ chối đúng dữ liệu đó.

Ngày 22/10 còn có một bản PoC công khai trên GitHub. Bản này chỉ chạy được mã trên một bản build cố tình làm yếu đi; trên bản thường nó làm Node.js sập và đọc lộ được một ít bộ nhớ. Đừng tải về chạy thử trên máy mình, mã khai thác từ tài khoản lạ hay kèm mã độc.

Đến 23/10 chưa có báo cáo tấn công thật, và lỗi chưa nằm trong danh sách KEV của CISA. Google chấm 9,2/10 theo CVSS 4.0, Node.js xếp mức cao. Một bản sập server là đủ phiền, còn đường tới chạy mã đã có người đi rồi, nên mình nghĩ không nên đợi tới lúc có tin bị khai thác hàng loạt mới vá.

Không chỉ Node.js

Lỗi nằm trong bộ giải nén Brotli bằng C, từ bản 1.0.3 tới 1.2.0. Google sửa trong mã nguồn từ tháng 2 nhưng tới 21/10 mới ra bản phát hành 1.0.10, 1.1.1 và 1.2.1. Phần mềm nào chép bộ giải nén này vào bên trong thì phải tự ra bản vá riêng.

Với người làm web và app, mấy chỗ cần để ý:

  • Node.js tự đóng gói Brotli, nên thư viện Brotli của hệ điều hành không liên quan. Nâng Express hay body-parser cũng không sửa được, phải nâng chính Node.js.
  • Bun sửa từ 1.4.4.
  • Python: gói Brotli sửa ở 1.2.1, brotlicffi sửa ở 1.2.1.0. Khi một trong hai gói có mặt, urllib3 và requests tự giải nén phản hồi Brotli. httpx (cài kèm httpx[brotli]), aiohttp và Scrapy cũng vậy.
  • Ruby gem brotli 0.9.1, extension brotli của PHP 0.21.1, .NET 8.0.33, 9.0.22, 10.0.14 đã có bản vá.
  • Envoy chỉ dính khi bật bộ giải nén Brotli, Istio đã có bản 1.31.2, 1.30.6, 1.29.9.

Bản port Brotli viết bằng Java, JavaScript thuần, C# và Rust không bị chạy mã.

Phần Python mới được xác nhận lỗi, chưa ai chạy được mã. Nhóm OpenSourceMalware cho một luồng dữ liệu khoảng 23 MB chạy qua cả hai bản: 1.2.0 cứ đòi thêm dữ liệu mãi, 1.2.1 từ chối ngay với mã lỗi -17. Aikido vẫn khuyên coi là việc gấp, vì cùng đoạn code C đó đã bị khai thác bên Node.js.

Khi nào app bị ảnh hưởng

Phải có dữ liệu Brotli do người khác kiểm soát đi tới bộ giải nén. Có hai đường hay gặp.

Đường thứ nhất là app gọi ra ngoài. fetch() của Node.js và requests của Python tự giải nén phản hồi. Ô “dán link để xem trước”, tính năng nhập dữ liệu từ URL, bot đọc trang web, agent AI tự lướt web là những chỗ app đi lấy dữ liệu từ server mà bạn không kiểm soát. Theo một nguồn, bỏ br khỏi header Accept-Encoding không chặn được, vì server độc vẫn có thể trả về Brotli.

Đường thứ hai là request gửi tới có Content-Encoding: br. Express 5.0.0 tới 5.3.0 và body-parser 2.0.0 tới 2.3.0 tự giải nén loại body này. Koa body parser và @fastify/compress bật giải nén request cũng vậy. Express 4, body-parser 1.x và server http trơn thì không.

Code tự gọi zlib.brotliDecompress, createBrotliDecompress hay brotli.decompress trên dữ liệu người dùng gửi lên thì khỏi bàn.

Kiểm tra app của bạn

Node.js, chạy trên máy, trên server và trong từng image:

node -v
docker run --rm <image-cua-ban> node -v
npm ls express body-parser
grep -rn "brotliDecompress\|createBrotliDecompress" --include=*.js --include=*.ts . | grep -v node_modules

Cần đạt 22.23.4, 24.22.1 hoặc 26.12.1. Còn chạy 20, 23 hay 25 thì không có bản vá, lên 22, 24 hoặc 26.

Python, chạy trong từng virtualenv và từng image:

pip show Brotli brotlicffi
grep -in "brotli" requirements*.txt poetry.lock uv.lock 2>/dev/null

Có bản dưới 1.2.1 thì nâng:

pip install -U "Brotli>=1.2.1"          # hoặc "brotlicffi>=1.2.1.0"

Tới 23/10, pip-audit, Dependabot và Safety chưa báo lỗi này vì cơ sở dữ liệu lỗ hổng chưa nối CVE với hai gói trên. Giờ OSV đã ghi đủ, nhưng phần lớn bản Brotli lỗi lại nằm sẵn trong runtime hay phần mềm khác chứ không phải gói Python, nên máy quét báo sạch vẫn chưa đủ. Cứ xem số phiên bản bằng tay.

Nâng xong phải khởi động lại tiến trình và build lại image. Sửa lockfile mà container đang chạy vẫn là image cũ thì chưa xong. Nếu chưa vá được ngay, chặn request có Content-Encoding: br ở reverse proxy, load balancer hay WAF. Cách này chỉ chặn được đường thứ hai, phản hồi Brotli mà app tự đi lấy thì không chặn được. Python có thêm cách tạm là gỡ hẳn Brotli và brotlicffi; requests vẫn giải nén gzip bình thường, nhưng server nào cứ trả Brotli sẽ làm app báo lỗi.


App của bạn có chỗ nào tự đi lấy dữ liệu từ link người dùng dán vào không, và bạn kiểm tra rồi thấy Brotli nằm ở đâu? Kể trong cộng đồng Vibe Code & Bảo Mật nhé.

Nguồn

Chia sẻ lên Facebook

Bình luận

Đang tải bình luận…