Skip to content

Instantly share code, notes, and snippets.

@ky28059
Last active October 31, 2024 04:32
Show Gist options
  • Save ky28059/7420e657c833bbe586c56a77aabf223d to your computer and use it in GitHub Desktop.
Save ky28059/7420e657c833bbe586c56a77aabf223d to your computer and use it in GitHub Desktop.

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

String.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:

function conv(s) {
    const chars = ('0' + s).split('');
    let ret = '';

    for (let i = 0; i < chars.length; i += 2) {
        let byte = chars[i];
        if (i + 1 < chars.length) byte += chars[i + 1];

        let num = 64 + parseInt(byte, 16);
        ret += String.fromCharCode(num);
    }

    return ret;
}

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):

function conv(s) {
    const rest = ('0' + s).split('');
    let ret = '';

    for (let i = 0; i < rest.length; i += 2) {
        let group = rest[i];
        if (i + 1 < rest.length) group += rest[i + 1];

        let num = 64 + parseInt(group, 16);
        if (num > 128) num -= 128;
        ret += String.fromCharCode(num);
    }

    return ret;
}

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:

function deconv(s) {
    let ret = '1';
    for (const c of s) {
        let diff = c.charCodeAt(0) - 64;
        if (diff < 0) diff += 128;

        let str = diff.toString(16);
        if (str.length < 2) str = '0' + str;
        ret += str;
    }
    return ret;
}

Then, we can run

image

to encode

A" OR 1=1#

list the entire table, and get the flag:

image

flag{SQL-1nj3ct10n-thrU-x0r}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment