[Crypto] Baby Web Crypto


Simple web challenge with some crypto.


Link to the challenge



For this challenge we’re given a URL to a site that only shows us a robot image. index

It’s pretty clear that we should now visit robots.txt.

1
2
User-Agent: ctf-players
Disallow: /secret-flag-site.html


On the “secret flag site” we’re presented with a simple form asking us for a password. form

In the source code of the page we see a input field and some JavaScript code that handles the logic.

1
2
3
    <h3>enter code to get flag</h3>
    <input type="text" pattern="\d*" maxlength="4" id="code" />
    <button>get flag</button>
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
const enc = (t) => new TextEncoder().encode(t);
const dec = (t) => new TextDecoder().decode(t);

async function getKey(code) {
    const digest = await crypto.subtle.digest("SHA-512", enc(code));
    const array = new Uint8Array(digest);
    return crypto.subtle.importKey("raw", array.slice(0, 16), "AES-CBC", true, ["encrypt", "decrypt"]);
}

async function decode(code) {
    const key = await getKey(code);
    const message = new Uint8Array([
        33, 148, 65, 177, 82, 219, 127, 213, 218,
        107, 19, 190, 78, 99, 28, 243, 246, 178,
        189, 217, 76, 107, 19, 165, 230, 50, 180,
        196, 24, 177, 59, 139,
    ]);

    let decrypted;
    try {
        decrypted = await crypto.subtle.decrypt({
            name: "AES-CBC",
            iv: new Uint8Array(16),
        }, key, message);
    } catch (err) {
        return "invalid code!";
    }

    const flag = dec(decrypted);
    if (!flag.startsWith("CTF{")) {
        return "invalid code!";
    }
    return flag;
}

const button = document.querySelector("button");
const codeInput = document.querySelector("#code");
const result = document.querySelector("#result");

async function handler() {
    const code = codeInput.value;
    const flag = await decode(code);
    result.textContent = flag;
}

button.addEventListener("click", handler);
codeInput.addEventListener("keydown", (ev) => {
    if (ev.key === "Enter") {
        handler();
    }
});


The input field has a pattern and maxlength defined which limits the inputs to the logic. It boils down to this: “\d{1,4}”

The logic portion of the page fetches the input value and attempts to decrypt the array with the sha512 hash of the input as the key.

We can write a simple python script to bruteforce all 4 digit combinations and attempt to decrypt the ciphertext.

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
from Crypto.Cipher import AES
import hashlib

ct = bytes([
    33, 148, 65, 177, 82, 219, 127, 213,
    218, 107, 19, 190, 78, 99, 28, 243,
    246, 178, 189, 217, 76, 107, 19, 165,
    230, 50, 180, 196, 24, 177, 59, 139,
])


def decrypt(key):
    try:
        key = hashlib.sha512(key.encode()).digest()[:16]
        cipher = AES.new(key, AES.MODE_CBC, b'\x00' * 16)
        return cipher.decrypt(ct)
    except:
        return false

for i in range(0, 10000):
    res = decrypt(str(i).ljust(4, '0'))
    if res != False and b'CTF{' in res:
        print(f'Key: {i}; Plaintext: {res}')
        break


After a short while the script will return us the key used and the decrypted value:

1
Key: 5826; Plaintext: b'CTF{53cR3t_w3b_F14G}\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'



Flag: CTF{53cR3t_w3b_F14G}