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:
- Default-enabled on many distributions. Several homelab-oriented distros and OS images install the CUPS browsing stack as a dependency of the desktop environment, even when the host has no display attached. Ubuntu Server with a desktop seed, Raspberry Pi OS Desktop and a number of NAS-style appliances all qualify.
- Hypervisors and NAS units. Proxmox, TrueNAS and similar appliances run on Linux and often inherit CUPS through dependency chains, even though nobody ever intended to print from a hypervisor.
- Flat LANs. Few homelabs run multiple VLANs. Anything on the network can reach UDP 631 on anything else on the network — including the smart TV running unpatched firmware and the IoT bulb whose vendor went out of business in 2022.
- "It still works" inertia. Once a config is printing successfully, the cost of touching it again is non-trivial. Most homelab CUPS installs have not been audited since the day they were set up.
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:
- Avahi / mDNSResponder. Network discovery for any service that wants to announce itself. The protocol itself does not authenticate, and bugs in the parsing of announcement records have produced their own CVEs over the years.
- Home Assistant's mDNS exposure for HomeKit. Useful for discovering devices, but it advertises the host on every interface unless you constrain it.
- Pi-hole's DNS listener. If you put Pi-hole on your LAN and let untrusted devices use it, you have an unauthenticated DNS server taking input from arbitrary clients. The Pi-hole security checklist walks through the configuration that keeps that boundary explicit.
- SMB / Samba advertisement. Less of a present-day RCE risk than it used to be, but historically the protocol that taught everyone what "happens to advertise itself on the LAN" can lead to.
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.