ctf writeup repo

View on GitHub

Chain Race - Web challenge

Author: p4w@beerpwn

Twitter: https://twitter.com/p4w16


This challenge is about exploiting an SSRF vulnerability to connect to an internal service which is vulnerable to a race condition.

Challenge description

img desc


By opening the link given we come across a simple web-page that accept a user input. It expect an URL as parameter and it will make a request to the given URL, then the results will be printed back to the page.

img desc

This smell like SSRF vulnerability, indeed if we try to input this simple payload, we can fetch the index page. By using a proxy we can see that the requests will be sent to a PHP script testhook.php.

img desc

So the technology should be PHP, and we have an SSRF. Next step is to try if we can actually use other protocols such as file://, gopher://
Let’s start with file://, since will be useful if we can leak some files from the server. Following the challenge description, I started with the source code:

img desc

So that confirm that the application use php-curl to fetch our input, also there isn’t any check on our input.
Next file that I extracted was the passwd since we can potentially find some useful information about users on the system.

img passwd

As we can see it seems that there is an account localhost8080 that seems related to another hidden-service. Here I just trusted, that the port of the service could be 8080 since is contained in the name.
So let’s try to make an http request to

img hidde-service

Nice, there is another php web application binded to localhost. By reading the code associated we can see that it will give us the flag but only if we pass some checks.
Let’s highlight the source code where the checks are done:

img hidde-service

So to get the flag we need to pass these checks against this condition if (($_GET['secret'] == "0x1337") || $_GET['user'] == "admin") { die("nope"); }:

For the first and the second one we can just read the php-docs for the function strcmp and strcasecmp

img strcmp

Looking at return value of this function, to bypass the check we just need to pass a longer string then admin like adminA

img bypass_1

img strcasecmp

To bypass this we can just notice that strcasecmp is case insensitive, then we can use 0X1337 to bypass this one.

img bypass_2

The last one is the most tricky. In order to get the flag, it’s necessary that the unlink() will fail. One reason for unlink to fail is to unlink a non existent file. We can test this behavior with php-cli:

img unlink

Let’s analyze how the filename is created, this is the line of code responsible for that:
$temp_name = sha1(md5(date("ms").$_COOKIE['PHPSESSID'])); The first thing to notice is that the randomness of $temp_name is given by 2 thing, the first the date() function and the second one from the PHPSESSID. By testing the date function we can notice that the precision is the second.

img hidde-service

So that, if we make two distinct requests in a short period of time we will eventually get the same value from the date("ms") function. So theoretically this can be solved by using two process that will make the request separately.
The last problem to solve is the PHPSESSID value. If we make a request with the http protocol, we can’t control the PHPSESSID cookie value that will be generated by the session_start, but we have gopher:// wrapper and with that we can craft an entire http request containing our chosen PHPSESSID! Let’s test this part.
Testing locally with some debug, notice the PHPSESSID value img hidde-service Testing gopher on remote server img hidde-service img hidde-service

Nice, all seems to work. Now, to explain why the race condition can eventually solve the challenge and retrieve the flag, I’ve done this image that should explain how the race condition work.

img race

Flag time

Let’s go with race condition, for that I used burp intruder

img race

Cheers p4w!