Hiểu về lỗ hổng .NET tồi tệ nhất từ trước đến nay: request smuggling và CVE-2025-55315

Tôi thừa nhận, đây là một tiêu đề mang tính câu click khá cao, nhưng Microsoft đã cho điểm CVSS của lỗ hổng này là 9.9, mức điểm cao nhất từ trước đến nay của họ. Đã đến lúc hoảng loạn, phải không?

Trong bài viết này, tôi cố gắng cung cấp thêm một chút bối cảnh. Tôi giải thích cách hoạt động của lỗ hổng request smuggling nói chung, cách nó hoạt động trong trường hợp này, những gì kẻ tấn công có thể sử dụng nó để làm, cách lỗ hổng đã được sửa, và những gì bạn có thể làm để tự bảo vệ mình.

CẢNH BÁO: Tôi không phải là chuyên gia bảo mật, vì vậy đừng coi bất cứ điều gì trong bài viết này như chân lý hay lời khuyên. Tôi chỉ là một lập trình viên đang cố gắng hiểu mọi chuyện. 😄 Tất cả các chi tiết trong bài viết này đều dựa trên thông tin được cung cấp hoặc tham chiếu trong thông báo ban đầu.

Lỗ hổng CVE-2025-55315 là gì?

Vào ngày 14 tháng 10 năm 2025, trong một “Patch Tuesday” tiêu chuẩn của Microsoft, Microsoft đã phát hành các phiên bản mới cho tất cả các phiên bản .NET được hỗ trợ, đồng thời công bố một cảnh báo bảo mật: Microsoft Security Advisory CVE-2025-55315: .NET Security Feature Bypass Vulnerability. Tóm tắt cấp cao từ thông báo đó cho biết:

Việc diễn giải không nhất quán các yêu cầu HTTP (‘http request/response smuggling’) trong ASP.NET Core cho phép kẻ tấn công được ủy quyền vượt qua một tính năng bảo mật qua mạng.

Lời khuyên là “hãy vá tất cả mọi thứ của bạn”, nhưng điểm nhấn thực sự là lỗ hổng này đã được gán điểm CVSS 9.9 trên 10, nghe có vẻ khá tệ! Barry Dorrans (AKA blowdart), trưởng nhóm bảo mật .NET, đã đưa ra lời giải thích về lý do đằng sau điểm số này trong một bình luận về vấn đề gốc:

Lỗi này cho phép HTTP Request Smuggling, mà riêng nó đối với ASP.NET Core sẽ không ở mức cao như vậy, nhưng đó không phải là cách chúng tôi đánh giá mọi thứ…

Thay vào đó, chúng tôi đánh giá dựa trên cách lỗi có thể ảnh hưởng đến các ứng dụng được xây dựng trên ASP.NET.

Request Smuggling cho phép kẻ tấn công giấu một yêu cầu bổ sung bên trong một yêu cầu khác, và những gì yêu cầu ẩn đó có thể làm phụ thuộc rất nhiều vào ứng dụng cụ thể.

Yêu cầu được giấu có thể khiến mã ứng dụng của bạn:

  • Đăng nhập với tư cách người dùng khác (EOP)
  • Thực hiện yêu cầu nội bộ (SSRF)
  • Bỏ qua các kiểm tra CSRF
  • Thực hiện tấn công injection

Nhưng chúng tôi không biết điều gì có thể xảy ra vì nó phụ thuộc vào cách bạn viết ứng dụng của mình.

Điều đó nghe có vẻ rất đáng sợ! 😱 Vì vậy, bạn có thể hiểu được sự bối rối mà vấn đề này đã gây ra, đặc biệt là sự do dự trong việc giải thích chính xác “cách bạn viết ứng dụng” có nghĩa là gì.

Vì tò mò, tôi quyết định đào sâu hơn để thực sự hiểu về lỗ hổng này, nó có thể ảnh hưởng đến bạn như thế nào, và “cách bạn viết ứng dụng” có thể có ý nghĩa gì.

Request smuggling hoạt động như thế nào?

Trước khi đi vào lỗ hổng thực tế đã được vá trong ASP.NET Core và cách nó hoạt động, tôi nghĩ điều quan trọng là phải có một số kiến thức nền tảng về lớp khai thác tổng quát được gọi là HTTP request smuggling.

HTTP request smuggling là một kỹ thuật khai thác bảo mật đã được biết đến từ rất lâu (theo Wikipedia, nó được ghi nhận lần đầu vào năm 2005). Vấn đề cơ bản phát sinh khi bạn có hai máy chủ khác nhau xử lý một yêu cầu HTTP (ví dụ: một máy chủ và một máy chủ proxy), và hai máy chủ đó khác nhau trong cách chúng xử lý các yêu cầu HTTP “không hợp lệ”.

Trong tất cả các trường hợp HTTP request smuggling, việc khai thác hoạt động bằng cách tạo ra một yêu cầu HTTP không hợp lệ (hoặc đôi khi chỉ là một yêu cầu mơ hồ), trông hơi giống như hai yêu cầu HTTP được dán lại với nhau. Tóm lại, việc khai thác hoạt động như sau:

  • Máy chủ proxy nhận được yêu cầu HTTP mơ hồ
  • Máy chủ proxy chuyển tiếp yêu cầu (không thay đổi) đến máy chủ đích
  • Máy chủ đích diễn giải yêu cầu mơ hồ như hai yêu cầu HTTP được pipelined gửi đến máy chủ, và xử lý chúng riêng biệt

Tôi nghĩ sẽ dễ hiểu nhất nếu có một ví dụ, vì vậy yêu cầu dưới đây cho thấy một ví dụ từ bài báo gốc năm 2005.

Lưu ý rằng đây không phải là một ví dụ về lỗ hổng request smuggling trong CVE-2025-55315, nó chỉ là một ví dụ đại diện cho request smuggling nói chung.

Hãy tưởng tượng kẻ tấn công gửi một yêu cầu HTTP trông như thế này:

POST /some_script.jsp HTTP/1.0
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 9
Content-Length: 204

this=thatPOST /vuln_page.jsp HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 95

param1=value1&data=<script>alert("stealing%20your%20data:"%2bdocument.cookie)</script>&foobar

Đặc điểm quan trọng của yêu cầu này là có hai header Content-Length trong yêu cầu đầu tiên (và một header sau trong yêu cầu bị giấu), với các giá trị khác nhau: 9 hoặc 204. Đây là cốt lõi của việc khai thác; sự khác biệt giữa header nào trong hai header đầu tiên mà proxy HTTP và máy chủ HTTP tôn trọng là nguyên nhân gây ra lỗ hổng.

Hãy xem xét cách khai thác hoạt động, từng bước một:

  • Kẻ tấn công gửi yêu cầu HTTP ở trên.
  • Proxy HTTP nhận yêu cầu, ghi nhận các header Content-Length trùng lặp, và chấp nhận header thứ hai, với độ dài 204. Điều đó có nghĩa là toàn bộ phần còn lại của yêu cầu được coi là nội dung message, và có vẻ ổn đối với proxy.
  • Proxy HTTP chuyển tiếp yêu cầu đến máy chủ đích.
  • Máy chủ này cũng ghi nhận header Content-Length trùng lặp, nhưng nó lấy header đầu tiên, với độ dài 9.
  • Máy chủ đọc 9 byte của nội dung (ví dụ: this=that) và coi đó là toàn bộ yêu cầu. Theo quan điểm của máy chủ, toàn bộ yêu cầu (hợp lệ) đã được nhận và nó xem phần dữ liệu còn lại như một yêu cầu hoàn toàn mới.
  • Điều đó có nghĩa là máy chủ đích thấy một yêu cầu HTTP hoàn toàn mới cần xử lý, POST /vuln_page.jsp, và coi nó như một yêu cầu mới.

Đó là cốt lõi của vấn đề; proxy thấy một yêu cầu, trong khi máy chủ đích thấy hai yêu cầu—yêu cầu thứ hai đã được “giấu” qua proxy đến máy chủ.

Kỹ thuật request smuggling được trình bày ở đây, nơi bạn có nhiều header Content-Length, không phải là ví dụ “chuẩn” mà bạn thường thấy được tham chiếu, nhưng tôi sử dụng nó ở đây vì nó dễ hiểu hơn theo nhiều cách.

Cuộc tấn công request smuggling chuẩn là khi bạn gửi cả header Content-Length và header Transfer-Encoding: chunked (header này xác định độ dài của nội dung như một phần của chính nội dung đó). Như trước đây, việc khai thác request smuggling dựa vào sự khác biệt trong cách proxy và máy chủ đích diễn giải các header mâu thuẫn này.

Như bạn đã thấy, request smuggling cho phép gửi một yêu cầu bí mật đến máy chủ đích mà máy chủ proxy trung gian không nhìn thấy. Trong phần tiếp theo, chúng ta sẽ xem xét tại sao điều đó lại là vấn đề và nó có thể bị khai thác như thế nào.

Kẻ tấn công có thể khai thác request smuggling như thế nào?

Thoạt nhìn, request smuggling có vẻ không phải là vấn đề lớn. Vậy máy chủ thấy hai yêu cầu, thì sao? Bạn luôn có thể gửi hai yêu cầu đến máy chủ mà không cần kỹ thuật này, phải không? Đúng và không.

Vấn đề với request smuggling thực sự là về sự không khớp giữa proxy và máy chủ đích. Nhờ sự không khớp này, và tùy thuộc vào hành vi và kỳ vọng của ứng dụng mục tiêu, kẻ tấn công có thể sử dụng request smuggling để:

  • Phản chiếu dữ liệu độc hại đến những người dùng khác trên các trang web dễ bị tấn công cross-site scripting.
  • Đầu độc bộ nhớ cache với dữ liệu xấu.
  • Đánh cắp thông tin xác thực hoặc dữ liệu khác từ các yêu cầu của khách hàng.
  • Gọi các endpoint không nên truy cập công khai (vì proxy sẽ chặn truy cập bên ngoài vào chúng).
  • Thay thế/ghi đè các kiểm soát xác thực do proxy xử lý.
  • Chuyển hướng người dùng đến các trang web độc hại trên các trang web dễ bị tấn công open-redirect.
  • Và nhiều hơn nữa…

Như bạn có thể thấy, tất cả đều là những điều tồi tệ™, vì vậy bạn có thể hiểu tại sao điểm 9.9 lại được đưa ra! 😱

Điều đó nói lên rằng, cần đề cập rằng không phải tất cả các cuộc tấn công này sẽ thành công đối với tất cả các ứng dụng. Một số phiên bản dễ hiểu nhất của các cuộc khai thác này là khi proxy không chỉ làm việc chuyển tiếp yêu cầu một cách “ngu ngốc”, mà thay vào đó nó đang xác thực hoặc cải thiện yêu cầu theo một cách nào đó.

Ví dụ: nếu bạn có một proxy đặt trước máy chủ của mình chịu trách nhiệm xử lý TLS termination và xác thực khách hàng cũng như nhận dạng bằng chứng chỉ, thì request smuggling có thể được sử dụng để vượt qua các kiểm tra này và chèn nhận dạng của riêng bạn.

Là một ví dụ về cuộc tấn công đó, yêu cầu HTTP dưới đây minh họa việc sử dụng Content-LengthTransfer-Encoding để “giấu” yêu cầu đến /admin khỏi proxy front-end, và chèn một header X-SSL-CLIENT-CN độc hại, mà thông thường sẽ được thêm bởi proxy front-end:

POST /example HTTP/1.1
Host: some-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 64
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
X-SSL-CLIENT-CN: administrator
Foo: x

Trong ví dụ này, máy chủ cho rằng header X-SSL-CLIENT-CN: administrator được thêm bởi proxy, và vì vậy máy chủ cho rằng proxy đã thực hiện tất cả xác thực và ủy quyền cần thiết. Kẻ tấn công có thể thực hiện yêu cầu với tư cách là một người dùng hoàn toàn khác.

Request smuggling rõ ràng là một vấn đề lớn khi bạn có proxy front-end thực hiện một số chức năng, nhưng ngay cả khi về cơ bản nó là một proxy “ngu ngốc”, request smuggling vẫn có thể được sử dụng để đánh cắp và trích xuất dữ liệu từ các yêu cầu của những người dùng khác, ngay cả khi trang web bị tấn công không dễ bị tổn thương bởi cross-site scripting hoặc các lỗ hổng khác.

Trong các cuộc tấn công này, chỉ cần có chức năng hiển thị dữ liệu do người dùng cung cấp (dù đã được làm sạch) cũng có thể đủ để đánh cắp thông tin xác thực của những người dùng khác. Vì vậy, một thứ đơn giản như hiển thị tên người dùng hoặc bình luận cũng có thể đủ.

Bài viết này đã đủ dài, và có rất nhiều cuộc tấn công khác nhau, vì vậy tôi sẽ dừng lại ở đây cho việc xem xét các khai thác. Nếu bạn muốn tìm hiểu thêm về những gì có thể xảy ra, cùng với các giải thích đơn giản và ví dụ về các cuộc khai thác, tôi khuyên bạn nên xem tài liệu của PortSwigger về khai thác request smuggling.

Request smuggling chỉ áp dụng nếu tôi có proxy?

Nói chung, khi mọi người nói về request smuggling, họ thường nói về trường hợp bạn có nhiều máy chủ: ví dụ chuẩn là máy chủ proxy và máy chủ đích, như tôi đã thảo luận. Nhưng đừng bị lừa, những vấn đề và lỗ hổng này có thể áp dụng ngay cả khi bạn không thực sự sử dụng proxy.

Đặc điểm chính của lỗ hổng là có cơ hội gây nhầm lẫn giữa hai “hệ thống”, dù chúng có phải là “máy chủ” đầy đủ hay không. Điều này rõ ràng áp dụng cho máy chủ proxy, nhưng cũng có thể áp dụng cho ứng dụng của bạn nếu bạn đang làm bất cứ điều gì mà bạn đọc/thao tác/chuyển tiếp các luồng yêu cầu, hoặc nơi có khả năng gây nhầm lẫn bên trong cùng một ứng dụng.

Đối với các ứng dụng ASP.NET Core, nếu bạn đang làm việc với HttpRequest.Body hoặc HttpRequest.BodyReader, hoặc các phương pháp tương tự khác thì bạn có thể dễ bị tổn thương ngay cả khi bạn không sử dụng rõ ràng máy chủ proxy. Ngay cả khi bạn không nghĩ ứng dụng của mình là proxy hoặc đang sử dụng proxy, nếu bạn đang làm những việc “giống proxy”, thì bạn có thể dễ bị tổn thương.

Nói cách khác, nếu bạn đang đọc, thao tác hoặc chuyển tiếp trực tiếp các luồng yêu cầu trong ASP.NET Core, thay vì chỉ dựa vào model binding tích hợp sẵn, thì bạn có thể gặp rủi ro với các cuộc tấn công request smuggling. Rất khó để liệt kê tất cả các vector tấn công, vì vậy bạn nên coi bất kỳ mã nào làm điều đó như một con đường khai thác tiềm năng.

Chúng ta đã đề cập đến cách request smuggling hoạt động và có thể bị khai thác nói chung, vì vậy đã đến lúc xem xét phiên bản cụ thể của request smuggling được nhắm mục tiêu trong lỗ hổng .NET CVE-2025-55315.

Request smuggling trong CVE-2025-55315 hoạt động như thế nào?

Như chúng ta đã thấy, HTTP request smuggling là một kỹ thuật tổng quát dựa trên sự khác biệt giữa proxy và máy chủ trong cách chúng phân tích cú pháp các yêu cầu HTTP. Tôi đã chỉ ra hai phiên bản cụ thể: header Content-Length trùng lặp và sự nhầm lẫn Content-Length/Transfer-Encoding, nhưng những phiên bản này không đầy đủ. Có các biến thể của các cách tiếp cận này cũng dẫn đến request smuggling.

Lỗ hổng request smuggling trong CVE-2025-55315 dựa trên một biến thể mà (theo như tôi biết) lần đầu tiên được báo cáo vào tháng 6 năm 2025 bởi Jeppe Bonde Weikop trên blog của họ. Biến thể này dựa vào Transfer-Encoding và tính năng Chunk Extensions.

Tất cả các chi tiết và hình ảnh trong phần này đều dựa trên mô tả và ví dụ trong bài viết gốc. Bài viết đó rất xuất sắc, vì vậy nếu bạn muốn biết thêm chi tiết và giải thích, bạn chắc chắn nên đọc nó, và sau đó bạn có thể bỏ qua phiên bản rút gọn mà tôi cung cấp ở đây.

Để hiểu lỗ hổng, trước tiên chúng ta sẽ xem xét cách hoạt động của chunked transfer encoding và chunk extensions là gì. Sau đó, chúng ta sẽ xem xét cách các kết thúc dòng không hợp lệ có thể dẫn đến sự khác biệt trong việc diễn giải một yêu cầu. Cuối cùng, chúng ta sẽ xem xét cách sự khác biệt trong diễn giải này có thể mở đường cho request smuggling, và cách ASP.NET Core đã sửa vấn đề.

Transfer-Encoding: chunked và chunk extensions

Để hiểu lỗ hổng, trước tiên chúng ta cần hiểu cách hoạt động của Transfer-Encoding: chunked, và cách chunk extensions làm phức tạp mọi thứ.

Khi bạn đang gửi một yêu cầu, bạn có thể không phải lúc nào cũng biết trước yêu cầu đó lớn đến mức nào. Hãy lấy một ví dụ thực tế về việc tuần tự hóa một đối tượng .NET sang JSON vào nội dung yêu cầu. Cách duy nhất để biết chắc chắn dữ liệu đã tuần tự hóa sẽ lớn đến mức nào là thực sự tuần tự hóa nó. Vì vậy, bạn có thể tuần tự hóa dữ liệu vào bộ nhớ trước khi viết yêu cầu, nhưng nếu dữ liệu rất lớn, điều đó có thể gây ra vấn đề với việc cấp phát mảng lớn.

Thay vào đó, Transfer-Encoding: chunked cho phép gửi dữ liệu yêu cầu trong nhiều “khối” (chunks). Bạn cần biết kích thước của từng khối riêng lẻ, nhưng không cần biết kích thước tổng thể của dữ liệu, hoặc có bao nhiêu khối. Điều này hoạt động tốt cho việc tuần tự hóa vào một bộ đệm nhỏ, gửi bộ đệm nhỏ đó dưới dạng một khối, và sau đó sử dụng lại bộ đệm để tuần tự hóa phần tiếp theo, cho đến khi bạn đã tuần tự hóa toàn bộ đối tượng.

Về mặt yêu cầu HTTP, mỗi khối bao gồm một header và một body. Header bao gồm số byte được định dạng thập lục phân, theo sau là kết thúc dòng \r\n (CRLF). Body của khối sau đó là số byte được chỉ định, theo sau là một \r\n khác. Bạn có thể có bao nhiêu khối tùy thích, và yêu cầu sẽ tiếp tục được truyền cho đến khi bạn gửi một khối có độ dài 0, báo hiệu kết thúc yêu cầu.

Ví dụ: HTTP POST sau đây cho thấy việc gửi một số JSON đến một endpoint, nhưng JSON được gửi dưới dạng ba khối riêng biệt:

  • Khối 1: Header là 9 cho biết 9 byte sẽ được gửi (theo sau là \r\n), và sau đó là 9 byte phần đầu của tài liệu JSON trong body khối, một lần nữa theo sau là \r\n.
  • Khối 2: Header là e cho biết 14 byte (14 trong hệ thập lục phân là e) sẽ được gửi (theo sau là \r\n), và sau đó là 14 byte còn lại của phần cuối tài liệu JSON, theo sau là \r\n.
  • Khối cuối cùng là một khối “rỗng”, 0\r\n\r\n, báo hiệu kết thúc yêu cầu.

Chúng ta sẽ thấy ngay sau đó rằng các kết thúc dòng rất quan trọng.

Đó là chunked transfer encoding “bình thường”, vì vậy bây giờ chúng ta đến với chunk extensions. Chunk extensions là một phần của giao thức HTTP 1.1 cho phép thêm các cặp key-value metadata vào các khối riêng lẻ. Một chunk extension được chỉ định bằng dấu ; sau độ dài header khối, theo sau là một hoặc nhiều cặp key-value dưới dạng key=value. Điều quan trọng là phải hiểu rằng chunk extensions không phải là một phần của dữ liệu mà trình xử lý yêu cầu thấy; chunk extensions chỉ là metadata về khối riêng lẻ. Và tóm lại, chúng hoàn toàn vô dụng 😅

Nói một cách gần đúng, không ai quan tâm đến chunk extensions; các triển khai client không gửi chúng, và các máy chủ chỉ đơn giản bỏ qua chúng. Nếu vậy, tại sao chúng có thể là nguyên nhân của một lỗi nghiêm trọng như vậy trong .NET?

Vấn đề là cách triển khai bỏ qua chúng…

Chunk extensions không hợp lệ với kết thúc dòng không chính xác

Nói chung với HTTP, các triển khai client và máy chủ thường cố gắng tuân theo nguyên tắc mạnh mẽ (robustness principle) là “hãy bảo thủ trong những gì bạn gửi, và khoan dung với những gì bạn chấp nhận”. Thật không may, chính sự khoan dung này đôi khi có thể khiến chúng ta gặp rắc rối. Suy cho cùng, chính sự khoan dung xung quanh các yêu cầu chứa cả header Content-LengthTransfer-Encoding là nguyên nhân gốc rễ của việc khai thác request smuggling ban đầu.

Lưu ý rằng HTTP 1.1 RFC hiện cấm chuyển tiếp cả hai header này, chính xác là để tránh các cuộc tấn công request smuggling.

Đối với chunk extensions, sự khoan dung thường được xây dựng một cách tình cờ vào các triển khai máy chủ. Vì không có triển khai nào thực sự làm bất cứ điều gì với chunk extensions, cách tiếp cận chuẩn để xử lý chúng khi phân tích cú pháp header khối chỉ là bỏ qua chúng. Khi một dấu ; được phân tích cú pháp, thông thường chỉ cần tìm kiếm kết thúc dòng và bỏ qua mọi thứ ở giữa.

Đối với ASP.NET Core (trước khi sửa lỗi), khi tìm thấy ; trong header khối, Kestrel sẽ “phân tích cú pháp” extension, nhưng trên thực tế, nó sẽ tìm kiếm ký tự xuống dòng \r và sau đó kiểm tra \n theo sau, bỏ qua mọi thứ ở giữa, hơi giống như thế này (được đơn giản hóa rất nhiều so với mã gốc):

private void ParseExtension(ReadOnlySequence<byte> buffer)
{
    while(true)
    {
        // Chunk-extensions not currently parsed
        // Just drain the data
        var extensionCursor = buffer.PositionOf(ByteCR);
        var suffixBuffer = buffer.Slice(extensionCursor); // skips over extensionCursor bytes

        var suffixSpan = suffixBuffer.Slice(0, 2).ToSpan();

        if (suffixSpan[1] == '\n')
        {
            // We consumed the \r\n at the end of the extension, so switch modes.
            return;
        }

        // Otherwise, keep reading data until we do find \r\n
        buffer = ReadMoreData();
    }
}

Triển khai trong ASP.NET Core không có gì đặc biệt; hầu hết các máy chủ chỉ đơn giản bỏ qua các byte cho đến khi chúng tìm thấy \r\n. Câu hỏi lớn là chính xác cách các máy chủ tìm kiếm \r\n. Điều gì xảy ra nếu chúng thấy một \r đơn lẻ, hoặc một \n đơn lẻ? Chúng có coi đó giống như \r\n không? Chúng có ném lỗi nếu tìm thấy \r hoặc \n không ghép đôi không? Hay chúng bỏ qua nó và tiếp tục tìm kiếm \r\n?

Sự mơ hồ đó là trung tâm của lỗ hổng request smuggling CVE-2025-55315. Sự khác biệt trong cách các triển khai proxy và máy chủ xử lý \r hoặc \n độc lập trong header khối cho phép các cuộc khai thác request smuggling sử dụng sự mơ hồ này.

Lưu ý rằng theo RFC, các nhà triển khai không được coi \r hoặc \n là các ký tự kết thúc dòng “hợp lệ” cho header khối, và cả \r\n đều không được phép ở nơi khác trong header khối, vì vậy các triển khai chính xác phải từ chối các yêu cầu bao gồm các kết thúc dòng độc lập này trong header khối.

Để rõ ràng hoàn toàn, ví dụ sau đây giống như triển khai trước đó nhưng với một header khối không hợp lệ trong chunk extension của khối thứ hai. Thay vì kết thúc bằng \r\n, chunk extension kết thúc bằng một \n đơn lẻ.

Đó là nguyên nhân gốc rễ của lỗ hổng request smuggling, vì vậy trong phần tiếp theo, chúng ta sẽ xem xét cách điều này có thể được sử dụng để tạo ra một yêu cầu HTTP độc hại.

Khai thác chunk extensions không hợp lệ cho request smuggling

Cũng giống như các ví dụ khác về request smuggling, cách tiếp cận chunk extensions dựa vào sự khác biệt trong cách proxy phân tích cú pháp yêu cầu so với máy chủ tiếp theo. Sự khác biệt này có nghĩa là proxy thấy một yêu cầu, trong khi máy chủ đích thấy hai yêu cầu, và cho phép tất cả các cách khai thác tương tự mà tôi đã thảo luận trước đó.

Như đã thảo luận, những ví dụ này đến từ bài viết blog xuất sắc này, vì vậy hãy xem bài viết đó để biết thêm chi tiết, các biến thể của cuộc tấn công và các cách khai thác lỗ hổng khác.

Ví dụ sau đây cho thấy một yêu cầu HTTP độc hại khai thác sự khác biệt trong xử lý kết thúc dòng giữa proxy và máy chủ đích để giấu một yêu cầu đến endpoint /admin. Chúng ta có thể tưởng tượng rằng proxy được cấu hình để tự động từ chối các yêu cầu đến /admin thông thường, và máy chủ cho rằng proxy xử lý điều đó cho chúng ta.

Trong ví dụ này, kẻ tấn công tạo một header khối bị lỗi với chunk extension bằng cách gửi 2;\n. Dấu ; đảm bảo rằng cả proxy và máy chủ đều coi header như một chunk extension, nhưng việc sử dụng \n thay vì \r\n dẫn đến phân tích cú pháp khác biệt:

  • Proxy chỉ thấy một yêu cầu:
    • Nó coi \n là một kết thúc dòng “hợp lệ” cho header khối
    • Nó sau đó coi xx là body của khối
    • 47 là header khối tiếp theo
    • 71 byte tiếp theo (47 là hex, tức 71 trong thập phân) được coi là body của khối
    • Cuối cùng là khối rỗng
  • Máy chủ thấy hai yêu cầu:
    • Máy chủ bỏ qua \n đơn lẻ và bỏ qua tất cả cho đến xx\r\n
    • Nó sau đó coi 47 là body của khối
    • Nó thấy một khối kết thúc, 0\r\n\r\n và nghĩ rằng yêu cầu đã kết thúc
    • Phần dữ liệu còn lại được coi là một yêu cầu hoàn toàn riêng biệt, chỉ chứa một khối rỗng trong body

Đây là ví dụ đơn giản nhất, nhưng về cơ bản bạn có thể khai thác sự khác biệt này theo tất cả các cách tôi đã mô tả trước đây. Chính xác ý nghĩa đối với ứng dụng của bạn là gì thì khó có thể nói, nhưng vì tất cả các loại tấn công vượt qua bảo mật, đánh cắp thông tin xác thực và injection đều có thể xảy ra, thật dễ hiểu tại sao lỗ hổng này nhận được điểm CVSS 9.9.

Một điều rất thú vị tôi tìm thấy là xem xét các cảnh báo bảo mật cho cùng một lỗi trong các triển khai HTTP khác từ các ngôn ngữ khác. Trong máy chủ python aiohttp và ruby puma, ví dụ, lỗ hổng chỉ được đánh giá ở mức độ nghiêm trọng vừa phải trong cả hai trường hợp. Trong netty, nó thậm chí còn được đánh giá ở mức độ nghiêm trọng thấp.

Theo như tôi biết, các máy chủ này về cơ bản dễ bị tổn thương giống như ASP.NET Core, vì vậy đó chỉ là một điểm dữ liệu thú vị, và tôi nghĩ nó phản ánh cách Microsoft thực sự muốn đảm bảo điều này nhận được sự chú ý xứng đáng và khách hàng vá các ứng dụng của họ!

Lỗ hổng đã được sửa như thế nào?

Giống như hầu hết các bản sửa lỗi cho request smuggling, giải pháp là ngừng khoan dung và/hoặc mơ hồ về cách các kết thúc dòng độc lập được xử lý trong header khối.

Trong ASP.NET Core, PR sửa lỗi thực hiện điều này bằng cách kiểm tra rõ ràng bất kỳ kết thúc dòng nào, thay vì chỉ tìm kiếm \r. Nếu nó tìm thấy một kết thúc dòng và nó không phải là \r\n một cách chính xác, thì Kestrel sẽ ném ra một ngoại lệ KestrelBadHttpRequestException và trả về phản hồi 400.

Tôi sẽ đề cập ở đây rằng có một công tắc AppContext để chọn tham gia vào hành vi phân tích cú pháp nguy hiểm/dễ bị tổn thương sau khi bạn đã vá ứng dụng của mình, nhưng xin đừng sử dụng nó, tôi không thể tin rằng thực sự có một lý do chính đáng (hoặc an toàn) để làm điều đó. 😅

Lỗ hổng đã được vá trong ASP.NET Core, vậy bạn nên làm gì?

Bạn nên làm gì?

Rõ ràng tin tốt ở đây là có một bản sửa lỗi cho ASP.NET Core. Như được mô tả trong vấn đề gốc, điều quan trọng là cập nhật lên phiên bản được hỗ trợ mới nhất của ASP.NET Core càng sớm càng tốt.

Chưa có bằng chứng công bố nào về việc lỗ hổng request smuggling đang bị khai thác trên thực tế, nhưng với số lượng lớn các cách mà request smuggling có thể được sử dụng, liệu chúng ta có biết không? 🤔

Điều đó có nghĩa là bạn nên cập nhật phiên bản .NET 8, .NET 9 hoặc .NET 10 của mình:

Phiên bản dễ bị tổn thương Phiên bản đã vá thấp nhất
.NET 10 10.0.0-rc1 10.0.0-rc2
.NET 9 9.0.0 – 9.0.9 9.0.10
.NET 8 8.0.0 – 8.0.20 8.0.21

Nếu bạn đang sử dụng ASP.NET Core 2.3 trên .NET Framework, thì bạn sẽ cần cập nhật phiên bản Microsoft.AspNetCore.Server.Kestrel.Core của mình:

Phiên bản dễ bị tổn thương Phiên bản đã vá thấp nhất
Microsoft.AspNetCore.Server.Kestrel.Core 2.0.0-2.3.0 2.3.6

Nếu bạn đang thực hiện triển khai tự chứa (self-contained) cho các ứng dụng của mình, bạn sẽ cần cập nhật lên các phiên bản đã vá và sau đó triển khai lại các ứng dụng của mình.

Và nếu bạn đang sử dụng các phiên bản cũ hơn của .NET Core? Vâng, thì bạn không thể vá… HeroDevs cung cấp hỗ trợ bổ sung cho các phiên bản .NET không còn được hỗ trợ (và đã xác nhận họ sẽ vá nó trong .NET 6), nhưng lỗ hổng này tồn tại trong hầu hết tất cả các phiên bản của .NET Core theo như tôi biết. Cá nhân tôi đã thử nghiệm xuống đến .NET Core 3.0 và tôi có thể xác nhận rằng lỗ hổng tồn tại và không có bản vá nào cho bạn. Điều tốt nhất nên làm là cập nhật lên một phiên bản .NET được hỗ trợ.

⚠️ Nếu bạn đang chạy ASP.NET Core sử dụng <=.NET Core 3.0, .NET Core 3.1, .NET 5, .NET 6 (trừ khi được HeroDevs hỗ trợ), hoặc .NET 7, thì bạn đang dễ bị tổn thương và không có bản vá nào. Bạn nên cập nhật lên phiên bản .NET được hỗ trợ càng sớm càng tốt. Trớ trêu thay, nếu bạn đang mắc kẹt với các ứng dụng Web Forms hoặc MVC .NET Framework cũ, bạn dường như không dễ bị tổn thương.

Cũng đáng lưu ý rằng nếu bạn đang mắc kẹt với một trong những phiên bản framework cũ này và không thể nâng cấp, thì có lẽ cách tốt nhất để tự bảo vệ mình là đảm bảo rằng bạn có một proxy phía trước ứng dụng của mình được xác nhận là không dễ bị tổn thương (mặc dù rõ ràng bạn có thể dễ bị tổn thương bởi các khai thác khác 😅).

Ví dụ, Azure App Services (AAS) đã xác nhận rằng các ứng dụng chạy trong AAS không còn dễ bị tổn thương nữa, ngay cả khi bạn chưa cập nhật, vì proxy mà AAS sử dụng (bản thân nó là một proxy ASP.NET Core dựa trên YARP) đã được vá. Bằng cách chặn các yêu cầu ở cấp độ proxy, các yêu cầu mơ hồ sẽ không bao giờ đến được ứng dụng của bạn, vì vậy bạn được bảo vệ.

Thật không may, hiện tại, vẫn chưa rõ ràng chính xác tình trạng của bạn nếu bạn đang sử dụng dịch vụ khác ngoài AAS để lưu trữ ứng dụng của mình. Ngay cả IIS cũng chưa được xác nhận là an toàn hay dễ bị tổn thương tại thời điểm này, nhưng tôi đã thực hiện một số thử nghiệm không chính thức trên máy Windows 11 của mình và theo như tôi biết, nó dễ bị tổn thương.

Lưu ý rằng nhiều người trong vấn đề gốc đang cố gắng thử nghiệm IIS bằng cách sử dụng phiên bản Content-Length/Transfer-Encoding của request smuggling, điều này không áp dụng ở đây; chúng tôi quan tâm đến phiên bản dựa trên chunk extensions.

Một điểm thú vị khác là đây là lỗ hổng chỉ trong HTTP/1.0 và HTTP/1.1; nó không phải là lỗ hổng trong HTTP/2 hoặc HTTP/3. HTTP/2 và HTTP/3 không hỗ trợ chunked transfer encoding, và thay vào đó sử dụng một lớp framing nhị phân khác, hiệu quả hơn cho việc truyền dữ liệu. Vì vậy, một cách khác để bảo vệ các ứng dụng mà bạn không thể nâng cấp có thể là thực thi rằng các client chỉ có thể sử dụng HTTP/2 hoặc HTTP/3. Hãy lưu ý rằng điều đó có thể làm hỏng rất nhiều client vẫn đang sử dụng HTTP/1.1!

Bạn có thể cấu hình các giao thức HTTP được phép bởi Kestrel bằng cách cấu hình các endpoint Kestrel của bạn. Tài liệu cho thấy nhiều cách khác nhau để làm điều này.

Làm thế nào để biết bạn có bị ảnh hưởng không?

Cách “đơn giản nhất” để biết bạn có bị ảnh hưởng không là kiểm tra phiên bản .NET bạn đang sử dụng để chạy ứng dụng của mình, sử dụng dotnet --info và xác minh rằng bạn đang sử dụng một trong các phiên bản đã vá. Nếu đúng, bạn an toàn. Đó là cách duy nhất “được hỗ trợ” để biết bạn an toàn, và đó là cách tôi khuyên dùng. Theo như tôi biết, hiện tại không có công cụ tổng quát nào để chỉ vào một ứng dụng và tìm hiểu xem nó có dễ bị tổn thương không, mặc dù có thể viết một công cụ như vậy.

Các bạn tại HeroDevs đã triển khai lại các bài kiểm tra chức năng từ bản sửa lỗi ASP.NET Core gốc dưới dạng một ứng dụng console được biên dịch với nhiều phiên bản ASP.NET Core. Họ đã sử dụng điều này để xác nhận rằng các phiên bản chưa vá của .NET 8-.NET 10 dễ bị tổn thương, trong khi các phiên bản đã vá thì không. Họ cũng sử dụng điều này để xác minh .NET 6 dễ bị tổn thương, và tôi đã điều chỉnh nó để xác nhận mọi thứ xuống ít nhất .NET Core 3.0 đều dễ bị tổn thương.

Bài kiểm tra trong bản tái tạo hoạt động bằng cách gửi một yêu cầu chunked transfer encoding đến ASP.NET Core, với một kết thúc dòng không hợp lệ trong header chunk extension. Lỗ hổng được xác định bằng cách ASP.NET Core “bị treo”, chờ thêm dữ liệu, cho đến khi nó hết thời gian chờ. Phiên bản “đã sửa” ngay lập tức ném ra ngoại lệ BadRequest được bao gồm trong bản sửa lỗi.

Tôi đã thấy một số nhầm lẫn về bài kiểm tra này trên mạng; lập luận là “nếu cả phiên bản đã sửa và chưa sửa đều ném ra ngoại lệ, thì tại sao nó lại quan trọng”? Tuy nhiên, đó không phải là mục đích của bài kiểm tra. Thực tế là Kestrel bị tạm dừng chờ thêm dữ liệu cho thấy rằng một yêu cầu HTTP bị giấu sẽ được thực thi. Bạn có thể thấy cách điều này có thể được tận dụng để trích xuất dữ liệu hoặc tấn công những người dùng khác trong blog về chunk extensions hoặc trên trang web của PortSwigger.

Tôi đã sử dụng một cách tiếp cận tương tự để cố gắng hiểu liệu IIS có thể dễ bị tổn thương hay không bằng cách gửi cùng một yêu cầu HTTP được tạo đến IIS và xem nó có bị treo cho đến khi hết thời gian chờ hay không: nó đã bị treo trên phiên bản IIS của tôi (10.0.26100.1882):

# Gửi một yêu cầu HTTP với chunk extension không hợp lệ, và xem
# nó có hết thời gian chờ hay bị từ chối với mã 400... Nó hết thời gian chờ 🙁
echo -e "GET / HTTP/1.1\r\nHost:\r\nTransfer-Encoding: chunked\r\n\r\n1;\n" \
  | nc localhost 80

Vậy điều đó có chắc chắn có nghĩa là IIS dễ bị tổn thương không? Không, đừng tin tôi, tôi không phải là nhà nghiên cứu bảo mật 😅 Nhưng cho đến khi bạn nghe điều ngược lại, tôi sẽ chơi an toàn và giả định rằng IIS sẽ không bảo vệ bạn khỏi các cuộc tấn công request smuggling chunk extension. Và nói chung, tôi sẽ áp dụng các quy tắc tương tự cho bất kỳ proxy nào khác mà bạn đang dựa vào trong cơ sở hạ tầng của mình.

Và như một lời nhắc nhở cuối cùng, mặc dù request smuggling thường được mô tả và minh họa bằng cách sử dụng proxy phía trước máy chủ của bạn, nhưng chỉ không sử dụng proxy không có nghĩa là bạn tự động an toàn. Nếu bạn đang đọc, thao tác hoặc chuyển tiếp trực tiếp các luồng yêu cầu trong ASP.NET Core, thay vì chỉ dựa vào model binding tích hợp sẵn, thì bạn có thể gặp rủi ro với các cuộc tấn công request smuggling. Tốt nhất là chơi an toàn, vá các ứng dụng của bạn, và bất cứ khi nào có thể, hãy để lại sự phức tạp của việc thao tác yêu cầu cho ASP.NET Core.

Nói chung, tôi sẽ đảm bảo đăng ký vấn đề ASP.NET Core trên GitHub, vì có khả năng bất kỳ thông báo nào khác xung quanh vấn đề này cũng sẽ được báo cáo ở đó.

Tổng kết

Trong bài viết này, tôi đã thảo luận về lỗ hổng ASP.NET Core gần đây: Microsoft Security Advisory CVE-2025-55315: .NET Security Feature Bypass Vulnerability. Cảnh báo này cảnh báo về một lỗ hổng request smuggling ảnh hưởng đến về cơ bản tất cả các phiên bản của ASP.NET Core.

Tôi đã mô tả cách request smuggling hoạt động nói chung, sử dụng một ví dụ đơn giản về request smuggling để chỉ ra cách sự mơ hồ trong cách HTTP được phân tích cú pháp có thể dẫn đến các máy chủ HTTP proxy và HTTP xử lý cùng một yêu cầu HTTP theo những cách khác nhau. Điều này có thể dẫn đến máy chủ thấy hai yêu cầu trong khi proxy chỉ thấy một yêu cầu duy nhất.

Sau khi đi qua một ví dụ về request smuggling, tôi đã thảo luận về một số cách mà kẻ tấn công có thể khai thác lỗ hổng request smuggling. Điều đó bao gồm phản chiếu dữ liệu độc hại cho những người dùng khác của ứng dụng của bạn, trích xuất thông tin xác thực hoặc dữ liệu khác từ các yêu cầu của khách hàng, gọi các endpoint không nên truy cập công khai, và nhiều cuộc tấn công khác.

Tiếp theo, tôi đã đi qua lỗ hổng request smuggling cụ thể được xác định trong CVE-2025-55315. Lỗ hổng này sử dụng sự mơ hồ trong việc phân tích cú pháp chunk extensions khi gửi các yêu cầu sử dụng chunked transfer encoding. Chunk extensions thường bị bỏ qua bởi tất cả các máy chủ, nhưng việc xử lý khoan dung có thể dẫn đến sự khác biệt trong xử lý giữa proxy và máy chủ, cung cấp một con đường cho request smuggling.

Cuối cùng, tôi đã đi qua các bước giảm thiểu bạn nên thực hiện: vá các ứng dụng của bạn. Tôi đã mô tả thông tin hiện tại chúng ta có về các máy chủ proxy dễ bị tổn thương hoặc đã được vá, và cách các phiên bản cũ của ASP.NET Core sẽ không nhận được bản vá, vì vậy sẽ vẫn dễ bị tổn thương (xin cảm ơn lần nữa HeroDevs vì đã hỗ trợ .NET 6). Nếu bạn đang chạy trong AAS, thì bạn ổn, nhưng nếu không, bạn cần kiểm tra với nhà cung cấp proxy của mình để xác định xem bạn có dễ bị tổn thương hay không.

Chỉ mục