One of our informants met a guy who calls himself Elite Arthur, he is a real jackass, and he thinks he is the best hacker alive. We got reason to believe that the robots hired him to write the firmwares for their weapons. But to write such a firmware we need the key to sign the code. Luckily for us, our informant also found his website: …. your job is to hack the server, find the flag and show this little cocksucker how skilled he really is. We count on you.
Here is your
challenge: https://ctf.fluxfingers.net:1317.
Alternatively, you can reach the challenge without a reverse proxy but also
without SSL here: http://ctf.fluxfingers.net:1339
This challenge consists of two long parts. First some web stuff, then an exploitation challenge.
The web page
First, we had to find a way to get onto the server. Our best call was to find a PHP code execution.
We figured out the “Bug bounty” page would be worth trying to get some sourcecode out of. The download function seemed very suspicious. By modifying the INSERT statement through the ‘rating’ parameter we could download any file we wanted:
1 2 |
|
This adds a download to ‘index.php’ (encoded as hex). By looking through the sourcecode we found the code where we could get our code to be executed:
1 2 3 |
|
And the code where to insert it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
But we still need the admin password for that. By digging deeper in the code we found a way to reset the password of the admin. This is done in four (easy) steps:
- Request reset code for guest
- use the SQLi to read it
- reset password via GET with id=0x1 (vulnerable code shown below)
- insert our code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
|
When this code is executed, it resets the admin password and executes the code
you supply on the command line. Why is this resetting the admin password? By
using 0x1
we are exploiting the way php handles comparisons between different
types:
1 2 3 4 5 6 |
|
While the weak comparison in line 1 interprets 0x1
as 1, it matches the id of
‘guest’, but the intval on line 6 returns 0 for 0x1
, matching the id of
‘admin’. This allows us to use the reset code for ‘guest’ to reset the admin’s
password.
Exploitation
After we got shell access to the server, we found a file which looks like the
flag /home/arthur/sign_key.flag
, but for which we didn’t have read access.
However, there was a suid executable together with its source code, which does
have these permissions, so let’s take a look at it.
The binary has two modes, clean and sign, and will read the flag file and a password file in a constructor. The first thing the sign mode will do is to check if the contents of the password file match a user-supplied argument. Since we don’t know the password, this looks like a dead end.
The clean method on the other hand does not need the password. It iterates
over all files in the folder ./uploads
and checks them for occurences of the
string system(".*");
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
As you can see, the function reads at most 192 bytes at a time and writes them into a global buffer array of size 255*192 using an unsigned char as index variable. This is a double off-by-one error.
If we provide a file with 256 system("");
entries, the global cookie variable
will be overwritten. Also, if the argument to system is longer then 192 bytes,
there will won’t be a null byte at the end of that entry. We can use these
vulnerabilities in the log_result
function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
If we wrote more than 192 bytes into found[i]
, snprintf will append the
contents of found[i+1]
as well which will overflow the local buffer. Since
the overflow happens in a loop, we can even write data which includes null
bytes by writing to the buffer multiple times, making the string shorter
in each write.
For example, if we want to write AAAA\x01\x00\x01\x00
, we will write
AAAAAA\x01
first and afterwards, overwrite the beginning with AAAA\x01\x00
.
The overflow protection is already bypassed as well, since we control the
cookie variable as described before.
Finally, since the binary is not position-independent, we can simply call puts
from the PLT, using a pop rdi
gadget before that, to load the first parameter
and print the flag from memory.
The final exploit works as follows:
- write system(“AAAAAAA..”); 256 times to ./upload/pwn, which will overwrite the cookie variable
- overwrite the saved return address with a ROP chain: “pop rdi” gadget; &key; puts@plt; exit@plt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|