Lỗ hổng QPACK trên HTTP/3: một kết nối nhỏ có thể quật ngã máy chủ

Phần mềm21 tháng 10, 2026·5 phút đọc

Nghiên cứu mới của FoxIO phát hiện lỗ hổng khuếch đại QPACK trên 7 trong 10 thư viện HTTP/3 được kiểm thử, cho phép kẻ tấn công từ xa chưa xác thực buộc máy chủ cấp phát bộ nhớ gấp hàng nghìn lần dữ liệu gửi đi. Nhiều dự án như quic-go, quiche, lsquic và Bun đã phát hành bản vá.

Lỗ hổng QPACK trên HTTP/3: một kết nối nhỏ có thể quật ngã máy chủ

Một nghiên cứu mới về bảo mật HTTP/3 đang khiến cộng đồng hạ tầng phải chú ý: chỉ với một kết nối duy nhất, kẻ tấn công chưa xác thực có thể buộc máy chủ HTTP/3 tiêu tốn lượng bộ nhớ lớn gấp hàng chục đến hàng nghìn lần so với dữ liệu thực sự gửi lên. Trong 10 thư viện được thử nghiệm, 7 thư viện dính lỗi, và nhiều dự án đã tung ra bản vá.

Minh họa tấn công khuếch đại QPACKMinh họa tấn công khuếch đại QPACK

QPACK là gì và vì sao nó bị lợi dụng?

HTTP/3 nén phần header bằng QPACK, cho phép các trường lặp lại như user-agent hay content-type được gửi dưới dạng chỉ mục ngắn thay vì chuỗi đầy đủ. QPACK dùng hai bảng tra cứu: bảng tĩnh (cố định theo đặc tả) và bảng động (xây dựng trong lúc chạy giữa client và server).

Chẳng hạn, một header dài 71 byte như strict-transport-security: max-age=31536000; includesubdomains; preload có thể được rút gọn thành đúng một byte là 0xFA — một tham chiếu tới mục số 58 trong bảng tĩnh.

Chính khả năng "nở" từ vài byte trên đường truyền thành hàng trăm byte trong bộ nhớ là gốc rễ của vấn đề. RFC 9114 mục 4.2.2 quy định máy chủ phải giới hạn kích thước header sau khi giải nén, tính theo độ dài tên và giá trị cộng thêm 32 byte phụ phí cho mỗi trường.

Lỗi nằm ở đâu?

Theo tác giả nghiên cứu, nhiều thư viện mắc ít nhất một trong các sai sót sau:

  • Chỉ kiểm tra kích thước khung đã nén, không kiểm tra kích thước sau giải nén
  • Giải nén toàn bộ rồi mới kiểm tra
  • Bỏ qua khoản phụ phí 32 byte mỗi trường
  • Mặc định không giới hạn khi cấu hình chưa được thiết lập
  • Không bao giờ áp dụng giới hạn, kể cả khi đã cấu hình

Kỹ thuật khai thác được đặt tên là "header accordion" (header kiểu đàn accordion): một khung HEADERS 1.000.000 byte có thể chứa khoảng 160.000 header tên duy nhất, lần lượt dùng tên một chữ cái (a–z), hai chữ cái (aa–zz), rồi ba và bốn chữ cái cho tới khi đầy hạn mức.

Chiều ngược lại cũng hiệu quả không kém: chèn một mục lớn khoảng 4 KiB vào bảng động, sau đó tham chiếu tới nó hàng nghìn lần bằng chỉ mục 1 byte. Mỗi tham chiếu lại buộc máy chủ cấp phát và sao chép toàn bộ mục — tỷ lệ khuếch đại lên tới 4064:1.

Các thư viện bị ảnh hưởng

quic-go — thư viện Go đứng sau Caddy, Traefik và cloudflared — giới hạn khung HEADERS ở 1 MiB nhưng áp lên phần tải QPACK đã nén. Hàm DecodeFull giải nén toàn bộ trong một lượt, biến khối 1 triệu byte thành 160.075 trường và giữ lại khoảng 15 MiB trong bộ nhớ. Lỗi được ghi nhận là CVE-2025-64702, và bản vá bổ sung giải nén tăng dần kèm phản hồi HTTP 431 khi vượt hạn mức.

quiche của Cloudflare (CVE-2026-12523) mắc hai lỗi cộng dồn: bỏ khoản phụ phí 32 byte (khiến giới hạn 1 MiB cho phép tới 1 triệu header thay vì khoảng 32.000), và cấp phát trước bộ đệm theo độ dài khung khai báo — kẻ tấn công khai 16 MiB rồi không gửi dữ liệu.

lsquic là trường hợp nghiêm trọng nhất với ba lỗi chồng lên nhau. Chỉ với chưa tới 10 MiB lưu lượng và 50 kết nối, kẻ tấn công có thể ghim bộ nhớ ở mức 5 GiB chỉ trong một giây. Lỗi đã được sửa trong lsquic 4.9.0.

Bun dùng lsquic nhưng thay callback kiểm soát header bằng mã riêng, chỉ kiểm tra từng header mà không cộng dồn. Tệ hơn, một lỗi biến đổi số nguyên khiến biến đếm dung lượng nhân đôi tới khi tràn về 0 và kẹt vĩnh viễn — biến cuộc tấn công khuếch đại thành tê liệt vĩnh viễn. Chỉ 60.000 tham chiếu mỗi luồng (5,8 MiB đường truyền) đủ để OOM-kill container 2 GiB.

aioquic (nền tảng của mitmproxy và hypercorn) lưu trữ MAX_FIELD_SECTION_SIZE nhưng không bao giờ kiểm tra. h3 của hệ sinh thái hyperium thì panic khi HeaderMap::append vượt dung lượng tối đa. Riêng Wireshark cũng dính lỗi DoS khi phân tích các gói tin này, ghi nhận là CVE-2025-13945.

Bài học về ranh giới trách nhiệm

Câu hỏi khó nhất không phải là "lỗi ở đâu" mà là ai chịu trách nhiệm giới hạn kích thước. Quá trình giải mã và lưu trữ header liên quan tới nhiều tầng: QUIC chỉ đếm byte nhận trên luồng, bộ giải mã QPACK dựng lại các trường, còn tầng HTTP/3 và ứng dụng biến chúng thành đối tượng request và giữ lại trong bộ nhớ.

Một khung HEADERS nhỏ có thể đại diện cho rất nhiều công việc. Giao thức đã cung cấp cách tính kích thước header sau giải nén, nhưng mã phía nhận phải thực thi nó.

Trường hợp nghttp3 cho thấy rõ sự mơ hồ này: thông số cấu hình chỉ mang tính quảng bá, còn ứng dụng phải tự thực thi giới hạn. Đây là điểm các đội vận hành tại Việt Nam cần lưu ý, nhất là khi tự triển khai máy chủ HTTP/3 sau CDN hoặc trong môi trường Kubernetes với giới hạn bộ nhớ container.

Khuyến nghị cho đội vận hành

  • Cập nhật ngay các thư viện đã có bản vá, đặc biệt nếu đang chạy Caddy, Traefik hay cloudflared
  • Đặt giới hạn rõ ràng cho kích thước field section sau giải nén thay vì để mặc định
  • Tắt HTTP/3 tạm thời nếu đang dùng mitmproxy và chưa thể nâng cấp (--set http3=false)
  • Rà soát hạn mức bộ nhớ của container và đặt cảnh báo cho các sự kiện OOM trong cgroup

Kiểm tra và vá lỗ hổng trên máy chủ HTTP/3Kiểm tra và vá lỗ hổng trên máy chủ HTTP/3

Nghiên cứu cũng phát hành vân tay JA4Scan-QUIC để quét các máy chủ HTTP/3 công khai trong hạ tầng của tổ chức, cùng mã tái hiện công khai trên GitHub. Nhóm nghiên cứu lưu ý một thông số giới hạn được quảng bá có thể trông rất yên tâm nhưng lại chẳng bảo vệ được gì nếu không ai thực thi nó — và đó chính là bài học cốt lõi của sự cố lần này.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗