Introduction
I started this blog a few months ago with this post about a race condition I found. I refuse to contribute to the internet’s blog graveyard, so here we are.
Anyway, this led to a similar blog post, this time on Blip’s blog and eventually a conference talk at ØxOPOSɆC, which at the time of writing has not happened yet. Our team’s manager had the cool idea of building a simple demo web application I could use at the talk (thanks João! :D).
I took the web app and turned it into a small challenge based on my initial finding. Then I decided to go a step further and build an entire virtual machine around it.
The challenge
I’ll try not to spoil it too much (yet, walkthrough below!), but it is a pretty simple system from an architectural point of view. There’s a web application and an SSH server running. After hacking into the vroomnerable user, you get to escalate your privileges to root by exploiting another race condition - this second part is where it can get a bit tricky.
I confess the machine is a bit CTF-y, it’s not supposed to simulate a real situation or environment. They are just challenges presented in a straight forward way, with no rabbit holes and no need to read between the lines.
Here is the link to download the .ova file. I promise it’s not malware :)
It’s the first time I’m building a vulnerable VM, so I’m not expecting it to be perfect. But come on, give it a go! Write a walkthrough or give me your feedback. Don’t forget to tag me!
Walkthrough
Setting up
If you’re using VirtualBox, importing the machine should be pretty straight forward after downloading the .ova file. Simple press File –> Import appliance and select the downloaded file. It should come pre-configured (I recommend setting your machine and the vulnerable machine to “NAT network”, that never results in any network problems for me).
You boot both machines (your Kali and vroomnerable) and if everything goes according to plan, they should be on the same network. Time to find out the victim’s IP address. The attacker’s IP is 10.0.2.4/24, so vroomnerable should be somewhere in the 10.0.2.0/24 range.
To be sure which of the results is the target machine, you can compare the MAC addresses found to the VM’s MAC address in the network settings.
Enumeration
This is a pretty basic nmap scan, but we don’t need more than this. There’s an SSH server and a web app running on port 8000.
This seems like an impossible challenge at first, since you cannot set a higher active value, only lower ones. After performing all your enumeration steps, you should find a hint. Yes, it’s a shamless plug to my blog (again 🙄).
User flag
The exploit for this first challenge is exactly what’s in the initial blog post, so I won’t go deep into that. This time, it only took me 6 tries - starting at the values 49/150, down to 44/150.
These credentials work for SSH, so here is the user flag.
The source code is in the home folder. This is artificially vulnerable due to the log_operation() function. The larger the value in the for loop, the longer the thread will wait before setting a value. wait_set() sets a PENDING value, while set() sets an ACTIVE value. When sending a lower value, the else branch is taken and the artificial delay starts. That’s the race condition window and when the second value (150) should change the y variable before the first thread completes.
Root flag
This is where it can get tricky. At first, this is the only thing we have to escalate our privileges.
Some files are being deleted and some files are being executed. The numbers are timestamps. After some enumeration, you’ll find an empty folder at /opt/race/ - this is where the deleting and executing is happening.
There’s another race going on here - deleting vs executing. Let’s start by building an exploit, throwing it inside /opt/race and running sudo /root/leclerc. Hopefully the exploit (setting the SUID bit for /bin/bash) wins the race and is executed.
I guess we came in second. The goal here is to extend the deleting window just enough so that all the executions can happen inside it. Please allow my artistic (aka paint) skills to demonstrate that.
However, this can only happen if deleting files takes longer than executing files. Let’s put on our scientific goggles and do an experiment. Here’s what we’re going to do:
1) Copy the exploit 10 times to /opt/race/;
2) Run sudo /root/leclerc;
3) Collect the length of the execution and deletion windows;
4) Repeat steps 1-3 with gradually more files.
In practice…
Here are the results of the experiment:
Files | Started deleting files | Finished deleting files | Deletion Δ | Started Executing files | Finished executing files | Execution Δ |
---|---|---|---|---|---|---|
1 | 1710590071065 | 1710590071066 | 1 | 1710590071065 | 1710590071067 | 2 |
10 | 1710591111140 | 1710591111142 | 2 | 1710591111140 | 1710591111148 | 8 |
100 | 1710591215249 | 1710591215266 | 17 | 1710591215249 | 1710591215335 | 86 |
1’000 | 1710591235234 | 1710591235399 | 165 | 1710591235234 | 1710591235851 | 617 |
10’000 | 1710591263709 | 1710591265325 | 1’616 | 1710591263709 | 1710591265644 | 1’935 |
30’000 | 1710592360610 | 1710592365380 | 4’770 | 1710592360610 | 1710592365814 | 5’204 |
60’000 | 1710592621064 | 1710592631090 | 10’025 | 1710592621065 | 1710592631257 | 10’192 |
90’000 | 1710592938159 | 1710592953692 | 15’533 | 1710592938159 | 1710592953983 | 15’824 |
200’000 | 1710594998488 | 1710595035416 | 36’928 | 1710594998488 | 1710595035422 | 36’934 |
300’000 | 1710595954811 | 1710596010562 | 55’751 | 1710595954811 | 1710596010571 | 55’760 |
My guess would be that for a very large number of files, the execution delta will gradually be closer and closer to the deletion delta, and eventually lower than it. I didn’t try with more than 300k files as the copy takes a long time, but that was enough so that the exploit worked.
I left the source code in the /root/ folder, inside the file called lecler.c
The main function simply starts two threads almost simultaneously - one deleting files and another one executing files, both in the /opt/race/ directory.
The vulnerability is in the delete_files() function. I added a very short sleep, which after a bunch of loops should accumulate and create a considerable delay. 9 microseconds was the value that would delay the program just enough to be able to exploit it with at least a couple of hundred thousand files.
My paint masterpiece above isn’t 100% in line with the experiment results. But I still managed to execute the exploit.
I need answers
I almost ended the post there, but I decided to give it another go since I’m not very confident on these results. Let’s clear things up.
I booted a fresh vroomnerable and rooted it again. I increased the usleep(9) to usleep(10000) in the source code, compiled it again and repeated the experiment. The deletion window should increase a lot along with the number of files. The exploit should be successful with less files too.
Here are the results with a larger sleep value.
Files | Started deleting files | Finished deleting files | Deletion Δ | Started Executing files | Finished executing files | Execution Δ |
---|---|---|---|---|---|---|
10 | 1710610616066 | 1710610616297 | 231 | 1710610616066 | 1710610616087 | 21 |
100 | 1710610721735 | 1710610722818 | 1’083 | 1710610721735 | 1710610721913 | 178 |
1’000 | 1710610769770 | 1710610781409 | 11’639 | 1710610769770 | 1710610771492 | 1’722 |
The exploit worked with just 10 files. And the deletion delta increases a lot more than the execution delta, this is exactly what I was trying to achieve initially.
So everything is clear now, the sleep function had a very small value. I was just lucky enough that somewhen (what a cool word) during the execution, thread 2 made the overtake and executed one of the files that hadn’t been deleted yet. Or maybe 9 microseconds is such a small value that the timestamps are not accurate enough.
Who knows, the windows were too small and the results too inconsistent to draw any accurate conclusions… Actually, that’s what made it extra fun.