Published 2026-06-01 · 11 min read

CUPS exposed on your LAN — the September 2024 CVE chain

If you ever set up CUPS to share a printer in your homelab, you might be exposing port 631 in a way you forgot about. In September 2024 Simone Margaritelli disclosed a four-CVE chain that turns an unauthenticated UDP packet to that port into shell execution as the printing user — no exploit primitives, no memory corruption, just configuration that the printing stack was always designed to trust. The mainstream distros patched it inside a week, but the population of homelab hosts still running cups-browsed bound to 0.0.0.0:631, patched or not, is enormous. This post explains what the chain actually does, who's exposed, and how to find out whether your LAN is one of them.

What CUPS and cups-browsed actually do

CUPS — the Common UNIX Printing System — is the print server that ships with essentially every Linux desktop and most Linux servers. It handles spooling, conversion between document formats, queue management, and the actual conversation with a printer over IPP, USB or socket. On a desktop it is what makes "File → Print" work; on a server, it is often installed because something else (Samba, GNOME, a desktop environment that pulled it in as a dependency) wanted it.

cups-browsed is a separate daemon, packaged as part of cups-filters on most distributions. Its job is to discover network printers automatically so you do not have to type their addresses by hand. It does this by listening for browse traffic — historically over the CUPS protocol, more recently over IPP and mDNS — and registering any printer it hears about with the local CUPS daemon. When you plug a new printer into your LAN and it appears in your print dialog seconds later with no configuration on your part, cups-browsed is the reason.

That auto-discovery is implemented as a UDP listener on port 631. The default in many distribution packages binds the socket to all interfaces, which means: anything that can route a UDP packet to your host on port 631 can talk to cups-browsed. On a desktop laptop tethered to a single trusted Wi-Fi network, that is mostly fine. On a server sitting on a flat LAN with a dozen other devices including whatever guest IoT firmware you bought last year, the trust assumption is doing more work than it looks.

The four-CVE chain explained

The chain consists of four CVEs disclosed together. None of them is individually catastrophic, but composed in sequence they walk from "an unauthenticated packet on UDP 631" to "shell execution as the printing user". I find it useful to step through them as a story rather than a list, because each link reduces the attacker's required capability by one step.

Step one — CVE-2024-47176: cups-browsed trusts the source of a browse packet

The browse protocol allows a printer to announce itself by sending a small UDP message. cups-browsed receives it, parses out the announced printer URI, and contacts that URI to fetch the printer's full IPP description. The bug is in the first line of that sequence: the daemon performs no source validation. It will happily contact a URI provided by a packet from any IP address, including one belonging to the attacker.

Already at this step, you have a server-side request forgery primitive. The attacker can make your CUPS host reach out to an arbitrary HTTP, HTTPS or IPP URL and parse the response. That on its own would be a CVE worth fixing. The chain makes it much worse.

Step two — CVE-2024-47076: libcupsfilters does not sanitise the returned IPP attributes

The HTTP response from the attacker's "printer" contains IPP attributes — a list of key/value pairs describing the device. libcupsfilters parses those attributes and stores them, but does not validate that the strings are constrained to sensible characters or formats. Whatever the attacker writes ends up in the printer's runtime configuration.

Step three — CVE-2024-47175: libppd writes them verbatim into a PPD file

To actually present the printer to applications, libppd generates a PostScript Printer Description file from those attributes. The file is a plain-text format that supports configuration directives, including some that downstream filters interpret as commands. libppd writes the attacker-supplied strings into the PPD without escaping. The malicious payload is now living on disk in a file that the printing pipeline will dutifully evaluate.

Step four — CVE-2024-47177: foomatic-rip executes the FoomaticRIPCommandLine directive

foomatic-rip is a printer filter that converts between document formats. PPD files can specify a FoomaticRIPCommandLine directive — a shell template for how to drive a particular printer. By design, the directive is a shell command. The vulnerability is that foomatic-rip evaluates it without considering that the PPD might have been authored by someone other than a trusted printer driver author.

When a user prints to the newly-registered "printer", foomatic-rip reads the PPD, expands the directive, and runs it. The directive is the attacker's payload. The attacker now has shell as whichever user submitted the print job. In some default configurations, the print pipeline runs as the lp user; in others, it runs as the user who issued the print. Either way, it is not the boundary you wanted between an unauthenticated UDP packet and your filesystem.

Why this matters more in homelabs than enterprises

The enterprise risk profile for this chain is mostly bounded. Corporate workstations sit behind segmented networks. Print servers are usually centralised, locked down, and managed by people who patched in October 2024. Endpoint security tools catch unusual foomatic-rip child processes. The population of enterprise hosts running unpatched cups-browsed exposed to a network with hostile devices on it is small.

The homelab risk profile is dramatically different. The canonical homelab story for CUPS goes something like: "I needed to share an old USB printer with the family, I installed CUPS on the home server, I followed a tutorial that said to set Listen *:631 and Browsing On, the printer worked, the printer eventually died, the configuration stayed." Five years later, the server is still running, the printer is gone, and cups-browsed is still bound to every interface listening for an announcement that will never come — except from someone who wants to send one.

Compounding factors specific to homelabs:

How to check if you're exposed

Three layers of check, ordered from cheapest to most authoritative.

1. Local socket inspection

On each Linux host, ask the kernel directly what cups-browsed is bound to:

ss -ulnp | grep :631
ss -tlnp | grep :631
systemctl status cups-browsed 2>/dev/null | head

Anything bound to 0.0.0.0:631 or [::]:631 is exposed to its LAN. Bound to 127.0.0.1:631 only — fine. No output at all — cups-browsed is not running, also fine. Pay attention to UDP specifically; the browsing daemon listens there, while the standard CUPS TCP listener on the same port number is a different concern.

2. Port scan from off-host

A local ss check can be fooled by netfilter rules that the operator does not realise are in place. A more honest view comes from another machine on the same LAN running:

nmap -sU -p 631 192.168.1.0/24
# or, faster, for a single host
nmap -sU -p 631 your-homelab-host

UDP scans are slower than TCP — nmap has to wait for the absence of a reply — but a single subnet sweep is manageable, and the output tells you what an attacker on the same LAN actually sees.

3. mDNS browse to enumerate cups-browsed instances

For homelabs running Avahi alongside CUPS, the daemon advertises itself over mDNS as well. From any machine that can do mDNS:

avahi-browse -rt _ipp._tcp
avahi-browse -rt _ipps._tcp
# macOS equivalent
dns-sd -B _ipp._tcp

Anything that shows up is announcing itself as an IPP service and is a candidate for both the attacker and your own inventory.

Run all three. The combination of "local socket plus external scan plus mDNS enumeration" catches the cases each individual method misses. For a broader view that goes beyond CUPS, the exposed admin surfaces guide walks through the same exercise for ~70 other services that homelabs commonly leave open.

Mitigations

Three actions, in priority order. Do all of them; the order reflects which one buys you the most safety per minute of work, not which one is enough on its own.

1. Patch. Install the vendor security update from late September or October 2024. On Debian and Ubuntu that is apt update && apt upgrade cups-browsed cups-filters. On RHEL-family distros it is dnf upgrade cups-filters. All four chain CVEs are addressed in the same package update; you do not need to track them individually. The Noxen per-distro pages — Debian 12, Ubuntu 24.04, Rocky 9 and the others — pin the exact fixed package version that the daily feed has tracked.

2. Disable if you do not need it. If the host is not actively printing or sharing a printer, the right move is to remove the daemon from the runtime set entirely:

sudo systemctl stop cups-browsed
sudo systemctl disable cups-browsed
sudo systemctl mask cups-browsed

The mask step prevents the unit from being started by dependency, which matters because several other services have cups-browsed in their Wants= list and will try to revive it on their next start.

3. Firewall UDP 631 inbound. Even on a host where you do want network printing, restrict who can talk to it. nftables or ufw rules limiting UDP 631 to the specific subnet that contains your trusted devices cuts off everything else — including the IoT segment that you cannot patch. If your router supports VLANs or guest network isolation, put untrusted devices on their own segment and block UDP 631 between segments.

The pattern of "patch, disable, firewall" generalises to most exposed-service CVEs, not just this one. The before you expose a service to the web post walks through the same decision tree for any new daemon you are about to bring online.

What scanners catch vs. miss

It is worth being honest about what version-based CVE scanning can and cannot tell you. A scanner that consults a package manifest — including Noxen — will reliably flag a host running an unpatched cups-browsed with CVE-2024-47176 attached. That is the easy half.

The harder half is the configuration that left cups-browsed listening on a hostile LAN in the first place. A fully patched cups-browsed bound to 0.0.0.0:631 on a flat network is still a printer management surface that most homelabs do not need to expose to the IoT segment. It might not be exploitable for this specific chain after the patch, but it is the kind of surface that attackers prefer for whatever the next CVE turns out to be.

This is why Noxen treats cups-browsed as both a CVE-matched package and an entry in the admin-surface catalogue. The version-matched finding clears when you patch; the admin-surface finding clears only when you either stop listening on untrusted interfaces or document that exposure as intentional. Both signals are useful, and they are asking different questions.

The mDNS-over-untrusted-network bug class

CUPS-browsed is one entry in a larger pattern: services that were designed in an era when "the LAN" meant a trusted corporate Ethernet and now run on networks that include arbitrary IoT devices, smart TVs, friends-of-the-family laptops and the occasional reflashed router. Several of the services on a typical homelab assume that anyone who can route a packet to them is allowed to talk to them.

Examples that share the bug class:

The general advice generalises better than any single CVE: do not treat your LAN as a trust boundary. Put devices you do not fully control on a separate segment. Bind services to the smallest set of interfaces they actually need. Patch the daemons that have to listen on shared segments. The 30-minute homelab security baseline turns that mindset into a checklist you can do in a single sitting.

Where Noxen fits in

Noxen scans your homelab fleet over SSH, captures the package manifest on each host, and matches it against a daily-refreshed CVE feed. A host running an unpatched cups-browsed shows up in the next scan with CVE-2024-47176 (and its three siblings) attached, alongside an admin-surface finding for the cups-browsed listener itself. That second signal is the one that keeps catching the issue even after you patch — as long as the daemon is bound to an untrusted interface, Noxen keeps flagging it. $79 one-time covers the Mac app and the scheduled-scan agent that re-runs nightly.

Scan your Linux fleet from your Mac

Noxen runs nightly agentless audits over SSH and shows only what changed since the last scan — new CVEs, config drift, newly exposed admin services. Mac-native control plane, no SaaS round-trip.

Buy Noxen — $79 one-time Download free trial