Lời nói đầu:
Bài viết trước mình đã nói về khái niệm cơ bản của HRS và một số dạng cơ bản với HTTP/1.1. Bài viết này mình sẽ đi vào HRS với HTTP/2.0, nó khá phức tạp hơn so với HTTP/1.1 tuy nhiên cũng không khó lắm đâu ^^. Cùng đi vào bài nhé~
HTTP request smuggling với HTTP/2.0
Tham khảo: https://portswigger.net/research/http2
HTTP/2 downgrading
Nguồn gốc chính của lỗ hỏng này là front-end server sử dụng giao thức HTTP/2 để giao tiếp với máy khách trong khi đó nó lại dùng HTTP/1.1 để giao tiếp với back-end server.
Khi đó user có thể chèn thêm các phần độc hại vào header mà hợp lệ với HTTP/2, tuy nhiên nó lại được phân tích ở máy chủ back-end với giao thức HTTP/1.1, khi đó lỗi HRS sẽ xảy ra.
Front-end HTTP/2 server thường sử dụng message length có sẵn trong các data frames gửi đi theo giao thức này. Trong khi để Backend hiểu được thì cần downgrade xuống HTTP/1 bao gồm 2 loại là TE và CL dẫn đến lỗi H2-TE và H2-CL.
H2.CL Desync:
Theo HTTP/2 RFC, ta có độ dài ở trong mỗi frame do đó tiêu đề Content-length là không cần thiết, tuy nhiên vẫn có thể đặt Content-length trong header.
Khi user gửi request đến front-end server có chứa trường Content-Length
, front-end server có thể sẽ chấp nhận trường này dù độ dài không đúng. Sau đó nó sẽ hạ cấp request từ HTTP/2.0 xuống HTTP/1.1 và giữ nguyên trường Content-Length
, tại back-end server nó có phân tích trường Content-Length
với giá trị mà user cho vào, bây giờ user có thể chèn thêm các phần độc hại và một giá trị Content-Length
không phù hợp với server để trigger HRS.
Ví dụ:
1 | :method POST |
Sau khi hạ cấp nó sẽ tạo thành yêu cầu như sau:
1 | POST / HTTP/1.1 |
Sau khi front-end server gửi yêu cầu đến back-end server phần còn lại sẽ được xem như là của yêu cầu mới, khi một người dùng khác gửi yêu cầu đến cùng thời điểm, phần còn lại sẽ được gắn vào đầu của yêu cầu mới và lỗi HRS được kích hoạt.
1 | POST / HTTP/1.1 |
Trả về 200 OK
Yêu cầu của nạn nhân sẽ trở thành yêu cầu sau:
1 | GET /abc HTTP/1.1 |
Trả về 404 Not Found
H2.TE
H2.TE Desync - Máy chủ Front-end downgrade H2 thành H1 và không xem xét loại bỏ các trường liên quan đến connection-specific header fields
Request gửi đến FE server:
:method | POST |
---|---|
:path | /identitfy/XUI |
:authority | id.b2b.oath.com |
transfer-encoding | chunked |
1 | 0 |
FE downgrade mà không tuân thủ việc loại bỏ transfer-encoding:
1 | POST /identity/XUI HTTP/1.1 |
Phía server chọn transfer encoding và H2.TE xảy ra, tương tự như CL.TE.
Bây giờ request sẽ trở thành:
- Request 1
1
2
3
4
5
6POST /identity/XUI HTTP/1.1
Host: id.b2b.oath.com
Content-Length: 66
Transfer-Encoding: chunked
0 - Request 2
1
2
3
4
5GET /oops HTTP/1.1
Host: psres.net
Content-Length: 10
x=
H2.TE via Request Header Injection
Request gửi đến FE server:
:method | POST |
---|---|
:path | / |
:authority | start.mozilla.org |
foo | b\r\n transfer-encoding: chunked |
1 | 0\r\n |
Sau khi FE downgrade, \r\n
trigger header injection:
1 | POST / HTTP/1.1\r\n |
Bây giờ bài toán lại trở về H2.TE
HTTP request tunnelling
Client-side Desync
Tham khảo: https://www.youtube.com/watch?v=KN8WF1q04no
Đôi khi server sẽ trả về 2 response với cùng một request nếu Connection là keep-alive và gửi chung 2 request trong 1 request.
Để server phân biệt được khi nào 2 request, khi nào thì chỉ là 1 request và phần sau là phần body thì ta cần thêm giá trị content-length.
Tuy nhiên một vài server sẽ không mong đợi Content-Length và sẽ mặc định là 0. Khi này server sẽ nghĩ đó là 2 request dù đó là POST request như sau:
Và 2 response sẽ được trả về.
Pause-based desync
1 | POST /resources HTTP/1.1 |