The Impossible User (crypto)

PUBLISHED ON 22/03/2020 — EDITED ON 01/04/2020 — 247CTF, INFOSEC


This is my write-up of a Cryptography challenge The Impossible User on the CTF site


This encryption service will encrypt almost any plaintext. Can you abuse the implementation to actually encrypt every plaintext?


from Crypto.Cipher import AES
from flask import Flask, request
from secret import flag, aes_key, secret_key

app = Flask(__name__)
app.config['SECRET_KEY'] = secret_key
app.config['DEBUG'] = False
flag_user = 'impossible_flag_user'

class AESCipher():
    def __init__(self):
        self.key = aes_key
        self.cipher =, AES.MODE_ECB)
        self.pad = lambda s: s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size)
        self.unpad = lambda s: s[:-ord(s[len(s) - 1:])]

    def encrypt(self, plaintext):
        return self.cipher.encrypt(self.pad(plaintext)).encode('hex')

    def decrypt(self, encrypted):
        return self.unpad(self.cipher.decrypt(encrypted.decode('hex')))

def main():
    return "
" % open(__file__).read()

def encrypt():
        user = request.args.get('user').decode('hex')
        if user == flag_user:
            return 'No cheating!'
        return AESCipher().encrypt(user)
        return 'Something went wrong!'

def get_flag():
        if AESCipher().decrypt(request.args.get('user')) == flag_user:
            return flag
            return 'Invalid user!'
        return 'Something went wrong!'

if __name__ == "__main__":

What needs to be done

We need to use the encrypt() function to encrypt hex value of the string impossible_flag_user and use the encoded output with function get_flag() to obtain the flag. Unfortunatly we can see that the program will not let us encrypt hex value of impossible_flag_user string directly - if user == flag_user: return 'No cheating!'. There has to be another way!

ECB - Electronic Code Block

The simplest of the encryption modes is the Electronic Codebook (ECB) mode (named after conventional physical codebooks[9]). The message is divided into blocks, and each block is encrypted separately.

The disadvantage of this method is a lack of diffusion. Because ECB encrypts identical plaintext blocks into identical ciphertext blocks, it does not hide data patterns well. In some senses, it doesn’t provide serious message confidentiality, and it is not recommended for use in cryptographic protocols at all.

ECB mode can also make protocols without integrity protection even more susceptible to replay attacks, since each block gets decrypted in exactly the same way


Source: ECB on Wikipedia


Lets convert the string we need to encode to get the flag impossible_flag_user to hex:

>>> "impossible_flag_user".encode("utf-8").hex()

By the way, you can go the other way in python with:

>>> print(bytes.fromhex('696d706f737369626c655f666c61675f75736572'))

So if we try to encrypt this hex:

$ curl\?user=696d706f737369626c655f666c61675f75736572
No cheating!%

We fail.

So we need to find a way to encrypt this hex.

Block length in ECB

AA == 1010 1010, 8b (bits) = 1B (byte)

$ curl\?user=AA
$ curl curl\?user=$(python -c "print ('AA' * 1)")
$ curl\?user=$(python -c "print ('AA' * 15)")
$ curl\?user=$(python -c "print ('AA' * 16)")

We are looking when will the size of the cypher text increase, on that point we have found our block size:

'AA' * 16 = 16 byte block length.

As the string we need ‘impossible_flag_user’ in hex (696d706f737369626c655f666c61675f75736572) is 20 bytes long, the encryption will pad it and we know that it will take two blocks, so 32 bytes.

We need to fill that two blocks with our own padding data, and the encrypted data in the third block will be the encrypted value of hex string ‘impossible_flag_user’.

$ curl\?user=$(python -c "print ('AA' * 32)")696d706f737369626c655f666c61675f75736572
$ curl\?user=$(python -c "print ('BB' * 32)")696d706f737369626c655f666c61675f75736572
$ curl\?user=$(python -c "print ('CC' * 32)")696d706f737369626c655f666c61675f75736572
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|696d706f737369626c655f666c61675f75736572                        |
| 32b                                                            | 32b                                                            |

The last 32 bytes are the encrypted value of hex string ‘impossible_flag_user’ that we need.

We can see that only the first 32 bytes are changing and the last 32 stay the same, so our theory is okay. We can now copy the last 32 bytes and fill them in the get_flag() function to obtain the flag:

$ curl\?user=939454b054b7379b0709a270b894025c707ece4f0913868ec5df07d131b0822d

See Also