Hello friends, this is the first of two, possibly three (if and when I have time to finish the Windows research) writeups. We will commence with concentrateing GNU/Linux systems with an RCE. As someone who’s straightforwardly graspd in the CUPS project shelp:
From a generic security point of watch, a whole Linux system as it is nowadays is equitable an endless and hopeless mess of security holes postponeing to be take advantage ofed.
Well they’re not wrong!
While this is not the first time I try to more or less responsibly tell a vulnerability, it is definitely the weirdest and most frustrating time as some of you might have acunderstandledged from my socials, and it is also the last time. More on this postponeedr, but first.
Summary
- CVE-2024-47176 | cups-browsed <= 2.0.1 secures on UDP INADDR_ANY:631 innocent any packet from any source to trigger a
Get-Printer-Attributes
IPP ask to an strikeer regulateled URL. - CVE-2024-47076 | libcupsfilters <= 2.1b1
cfGetPrinterAttributes5
does not verify or sanitize the IPP attributes returned from an IPP server, providing strikeer regulateled data to the rest of the CUPS system. - CVE-2024-47175 | libppd <= 2.1b1
ppdCreatePPDFromIPP2
does not verify or sanitize the IPP attributes when writing them to a transient PPD file, permiting the injection of strikeer regulateled data in the resulting PPD. - CVE-2024-47177 | cups-filters <= 2.0.1
foomatic-rip
permits arbitrary order execution via theFoomaticRIPCommandLine
PPD parameter.
(can you already see where this is going? :D)
Plus a couple of other bugs that will be alludeed and that are arguably security publishs but have been pretty much disconsiderd during the conversation with the broadeners and the CERT. They are still there, alengthy with cut offal other bugs that are more or less take advantage ofable.
Impact
A distant ungenuineated strikeer can quietly swap existing printers’ (or inshigh novel ones) IPP urls with a evil one, resulting in arbitrary order execution (on the computer) when a print job is commenceed (from that computer).
Entry Points
- WAN / uncover internet: a distant strikeer sends an UDP packet to port 631. No genuineation whatsoever.
- LAN: a local strikeer can spoof zeroconf / mDNS / DNS-SD advertisements (we will talk more about this in the next writeup ) and accomplish the same code path guideing to RCE.
Quoting one of the first comments from the guy who literassociate wrote the book about CUPS, while trying to elucidate to me why this is not that horrible:
I am equitable pointing out that the uncover Internet strike is restricted to servers that are straightforwardly connected to the Internet
Affected Systems
CUPS and definiteassociate cups-browsed are packaged for most UNIX systems:
This skinnyg is packaged for anyskinnyg, in some cases it’s allowd by default, in others it’s not, go figure 🤷. Full disclocertain, I’ve been scanning the entire uncover internet IPv4 ranges cut offal times a day for weeks, sending the UDP packet and logging wdisenjoyver connected back. And I’ve got back connections from hundreds of thousands of devices, with peaks of 200-300K concurrent devices. This file grasps a catalog of the distinctive Linux systems shapeed. Note that everyskinnyg that is not Linux has been filtered out. That is why I was getting increasingly alarmed during the last scant weeks.
Remediation
- Disable and delete the
cups-browsed
service if you don’t need it (and probably you don’t). - Update the CUPS package on your systems.
- In case your system can’t be modernized and for some reason you count on on this service, block all traffic to UDP port 631 and possibly all DNS-SD traffic (excellent luck if you participate zeroconf).
Enticount on personal recommendation, apverify it or depart it: I’ve seen and strikeed enough of this codebase to delete any CUPS service, binary and library from any of my systems and never aget participate a UNIX system to print. I’m also removing every zeroconf / avahi / bonjour hearer. You might consider doing the same.
Intro
One sluggish day a scant weeks ago, I was configuring Ubuntu on a novel laptop (GPD Pocket 3, amazing little cyber intrusion machine btw) and for reasons that are irrelevant to this post I wanted to verify which services were hearing on UDP ports – so I type netstat -anu
in a terminal and after verifying the output, I acunderstandledge someskinnyg fascinating:
1 |
Proto Recv-Q Send-Q Local Address Foreign Address State |
The 0.0.0.0
part is especiassociate atypical, it uncomfervents that wdisenjoyver process is hearing on port 631, it is hearing on and replying to any netlabor interface: LAN, WAN, VPN, wdisenjoyver you have. I also ambiguously recalled that CUPS, the Common Unix Printing System, participates TCP port 631, but this is UDP. I summarizeateigated with a lsof -i :631
, that verifyed CUPS on 631 tcp plus this other process, cups-browsed
(probable rcontent to CUPS), using the udp port instead:
1 |
cupsd 1868642 root 6u IPv6 32034095 0t0 TCP ip6-localstructure:ipp (LISTEN) |
And ps aux | grep "cups-brow"
ultimately verifyed that this process runs as root:
1 |
root 1868652 0.0 0.0 172692 11196 ? Ssl 13:20 0:00 /usr/sbin/cups-browsed |
What is cups-browsed?
After some googling I establish out that cups-browsed
is indeed part of the CUPS system and it is reliable for uncovering novel printers and automaticassociate inserting them to the system. Very fascinating, I had no idea Linux equitable inserted anyskinnyg establish on a netlabor before the participater can even acunderstandledge or be notified. The more you understand!
At this point I was innervously intrigued and asking, so I commence digging into the source code of this service. While it’s pretty disorderly on one hand, it is also self grasped and relatively effortless to understand. So I speedyly search for secure API usage and verify that this skinnyg is indeed hearing on INADDR_ANY:631 UDP:
1 |
... |
Cool, this code is using global variables enjoy there’s no tomorrow, so searching for the browsesocket
discdisconsidered that the process_browse_data
function is reading a packet from it, carry outing some verifys and then some parsing:
1 |
got = recvfrom (browsesocket, packet, sizeof (packet) - 1, 0, |
Essentiassociate, this service predicts an UDP packet with the establishat HEX_NUMBER HEX_NUMBER TEXT_DATA
and, if the permited
function returns genuine for the definite source IP, more skinnygs happen postponeedr.
Well it turns out that while you could configure who can and who can’t connect by editing the /etc/cups/cups-browsed.conf
configuration file … the default configuration file, on pretty much any system, is enticount on commented out and srecommend permits anyone.
Great 🤦
Later in the code, some pointer operations are carry outed to parse the packet. If all verifys pass, two text fields parsed from the packet are passed to the establish_cups_printer
function. We’ll return to this function in a moment, but for now let’s cgo in on the parsing.
Stack Buffer Overflows and Race Conditions
Keep in mind that while the CUPS
package itself is covered in oss-fuzz (nakedly to be truthful …), cups-browsed is not; there seems to be no fuzzing coverage for this component. And I don’t understand about you, but to me this parsing routine watchs ficowardly and definitely someskinnyg worth fuzzing:
1 |
end = packet + sizeof(packet); |
So I speedyly put together a fuzzing concentrate around process_browse_data
, commence my excellent better friend AFL, and postpone. You won’t apverify what happens next!!!
There are 5 contrastent fuzzing inputs that trigger this:
1 |
process_browse_data() in THREAD 136077340691200 |
I apverify it being due to the pointer being dereferenced before the exit condition is verified, in both loops. I also establish out postponeedr on that there’s a race condition and possibly DoS in the lock obtaind here.
Both these publishs have been telled and thocdisesteemfilledy recorded, to the devs and the CERT, but nobody seemed to give a damn. I can tell you that there’re other, more easily take advantage ofable code paths going on, not equitable in the uncovery mechanism – also telled and disconsiderd. To this day they have not been acunderstandledged or patched. Happy hunting.
However, I’m a bit sluggish and most convey inantly I’m a noob when it comes to binary misparticipate. Hell, I can nakedly tell whether a buffer overflow or a race condition are take advantage ofable or not. Hardening mechanisms are getting more and more complicated to bypass and to be truthful I had no intention of spending months on this stuff – I disenjoy printers. So for the moment I choosed to transfer on to what seemed to be a drop hanging fruit.
Back to establish_cups_printer
By watching at establish_cups_printer we can see that one of the two text fields parsed from the packet is a URL:
1 |
|
After some further validation and parsing, this URL and other data are then passed as arguments to the verify_uncovered_printer_record function, which ultimately carry outs produce_distant_printer_entry. The produce_distant_printer_entry
function will then call cfGetPrinterAttributes from libcupsfilters
:
1 |
|
To understand what this uncomfervents, we’ll need to inestablishly allude what the IPP protocol is, but for now the key points are:
- A packet grasping any URL, in the establish of
0 3 http://
, gets to UDP port 631: /printers/wdisenjoyver - This triggers a sequence of events that result in cups-browsed connecting to that URL, a drive-by comfervent of skinnyg.
So I tell to myself: there’s no freaking way that if I send this packet to a uncover IP running CUPS (thank you shodan.io), that computer will connect back to the server I specified. No way.
I hack some python code together, fire up a VPS and try anyway.
HOLY SH!!!!! Not only it connected back instantly, but it also telled the exact kernel version and architecture in the User-Agent header! We’ll see postponeedr how this protocol also tells the asking participatername (on the concentrate) for some asks. Also this aspect, that to me suites pretty well with CWE-200, has been telled and equitable scoffed off as part of the mechanism. Alright … let’s not misparticipate time on arguing whether or not this is a problem, let’s get to the juicy stuff. We understand that this skinnyg talks HTTP and POSTs some semi binary payload, what the hell is that?
Internet Printing Protocol
The Internet Printing Protocol, in stupidinutive IPP, is a distinctiveized communication protocol for communication between client devices (computers, mobile phones, tablets, etc.) and printers (or print servers). It permits clients to produce one or more print jobs to the netlabor-speedyened printer or print server, and carry out tasks such as querying the status of a printer, geting the status of print jobs, or abortling individual print jobs.
Essentiassociate, the system now apverifys that we are a printer and it is sending us, encapsupostponeedd in HTTP, a Get-Printer-Attributes ask in order to get printer attributes such as the model, vendor and cut offal others. It produces sense, the system uncovered a novel printer and somehow it has to understand what it is. Well …
I went back to writing some code and, by using the ippserver python package I was now able to reply properly, with attributes I regulateled, to the service ask. My counterfeit printer was instantly inserted to the local printers with no notification whatsoever to the participater.
AMAZING! 🎉🥳🎉
What can we do with this? At this point I allowd debug logs in the service so I could see what was going on when my counterfeit printer was being uncovered and inserted, and acunderstandledged these lines:
1 |
... |
Wait what?! It watchs enjoy the service getes these attributes and then produces some sort of transient file, a “PPD”, on which these attributes are possibly saved.
If we search for the PPD generation prosperous
string that materializes in the logs, we discover ourselves in the produce_queue function, where we can see how the attributes are passed to the ppdCreatePPDFromIPP2
API in libppd
:
1 |
|
We finassociate get to libppd, where the ppdCreatePPDFromIPP2 API is participated to save some of those strikeer regulateled text attributes to a file with a very definite, line oriented syntax, without any sanitization whatsoever:
1 |
if ((attr = ippFindAttribute(helped, "printer-produce-and-model", |
Notice how many attributes are fprintf’ed, unescaped, into the file. The printer-produce-and-model
is equitable one of them. So, what the hell is a PPD file now?
NOTE: These two API are also participated in other parts of the overall CUPS system, not equitable the uncovery. IYKWIM.
PostScript Printer Description
PostScript Printer Description (PPD) files are produced by vendors to depict the entire set of features and capabilities participateable for their PostScript printers.
A PPD also grasps the PostScript code (orders) participated to call upon features for the print job. As such, PPDs function as drivers for all PostScript printers, by providing a unified interface for the printer’s capabilities and features.
So a PPD file is a text file provided by a vendor that depicts in a domain definite language the printer capabilities to CUPS and teachs it on how to participate it properly. It watchs someskinnyg enjoy this:
1 |
*% ================================= |
And there are tons of contrastent teachions that are helped and can be participated to do all sorts of skinnygs. I spent a scant hours equitable reading the PPD specs (thank you MIT), and studying the CUPS definite extensions in order to discover someskinnyg I could count on to carry out an strike. And then I establish about the cupsFilter2
straightforwardive:
A filter is any executable grasped in the /usr/lib/cups/filter
path (CUPS does verify this, you can’t depict any binary), which will get carry outd when a print job is sent to the printer, in order to carry out some record conversion if the printer doesn’t help that definite establishat. So, given that we have a constraint on which binary we can carry out, we need to discover a way to leverage one of the existing filters to run arbitrary orders. And also bypass these verifys here, which only apverifys a space before the colon.
The problematic child: foomatic-rip
Another search discdisconsidered pretty speedyly what could be depictd as the vital evil of the CUPS family, the foomatic-rip filter. This executable has a lengthy history of being leveraged for misparticipate, commenceing from the first understandn (to me at least) CVE-2011-2964 and CVE-2011-2697 back in 2011. The filter acunderstandledgeed the FoomaticRIPCommandLine
straightforwardive in the PPD that would permit ANY order to be carry outd thcdisesteemful it. Nice!
According to the records, this is the pledge that mended those CVEs. However, you might have acunderstandledged that this package is contrastent and it’s called foomatic-filters
. When foomatic-filters was joind in the CUPS system, this mend was not ported to CUPS, as it is possible to verify by the --ppd argument
, initiassociate deleted as part of the mend, and still current in the code today. And in fact, we can discover alludes of the FoomaticRIPCommandLine
straightforwardive being leveraged for arbitrary order execution in the more recent CVE-2024-35235.
So apparently foomatic-rip
was a understandn publish (verifyed by the CUPS devs), but somehow it has not been mended for … decades? Why is someskinnyg that permits arbitrary orders in a generassociate unthinked context not considered a security publish worth mending? I’ll tell you why! Becaparticipate it’s very challenging to mend. According to the CUPS broadeners:
… it is very difficult to restrict what can be provided in the FoomaticRIPCommandLine line in the PPD file. REDACTED and the rest of the OpenPrinting team have been talking about ways to restrict what can be done thcdisesteemful Foomatic without shattering existing drivers – we can certainly recommend that people not participate Foomatic, but there are probable hundreds of betterer printer models (before 2010) that are only helped thcdisesteemful Foomatic.
And many of those hundreds of models, reassociate participate this straightforwardive in creative ways such as:
1 |
*FoomaticRIPCommandLine: "(printf ' 33%%-12345X@PJLn@PJL JOBn@PJL SET COPIES=&copies;n'%G|perl -p -e "s/x26copiesx3b/1/"); |
I had no idea that this can happen every time you print someskinnyg, and to be frank it’s quite frightening. They have to permit FoomaticRIPCommandLine
to acunderstandledge pretty much anyskinnyg (including perl as you can see), or many printers will equitable stop laboring on UNIX.
Remote Command Execution chain
So, in theory, we should now be able to:
- Force the concentrate machine to connect back to our evil IPP server.
- Return an IPP attribute string that will inject regulateled PPD straightforwardives to the transient file.
- Wait for a print job to be sent to our counterfeit printer for the PPD straightforwardives, and therefore the order, to be carry outd.
Shall we? This is the configuration payload for the IPP server (this is a YAML file that you will be able to participate with the next bettercap free and its novel zeroconf
and ipp
modules):
1 |
|
You can see how we’re returning a printer-privacy-policy-uri
attribute string (it can be any of the many attributes saved to the PPD) that will:
- Set
printer-privacy-policy-uri
to"https://www.google.com/"
, seal the PPD string with the double quote, and insert a novel line. - Inject the
*FoomaticRIPCommandLine: "echo 1 > /tmp/PWNED"
line with our order in the PPD. - Inject the
*cupsFilter2 : "application/pdf application/vnd.cups-postscript 0 foomatic-rip
line (acunderstandledge the spaces before and after the colon and no closing double quotes) straightforwardive to teach CUPS to carry out/usr/lib/cups/filter/foomatic-rip
(with ourFoomaticRIPCommandLine
) when a print job is sent.
In this video you can see me on my strikeer machine (on the left) using the first version of this take advantage of to strike my novel laptop, a brimmingy patched Ubuntu 24.04.1 LTS
running cups-browsed 2.0.1
, and (finassociate!!!) achieving order execution:
Personal Considerations
You will maybe be skinnyking now “wow, that’s a lot of stuff to read, code, RFCs, PDFs of forgotten standards, this research must have been so tiring”, but in fact this was a weekend worth of rabbit holes, this was the fun part. The actual labor, the burdensome, unintelligent stuff commenceed when on September 5, after verifying my discoverings, I choosed to discdisconsider a security advisory on the OpenPrinting cups-browsed repository and do what to me was the right skinnyg to do: reliable disclocertain.
I won’t go into the details of the initial conversation, or the ones that chaseed. You are free to read them (if they will ever discdisconsider any of the threads and you are willing to read 50+ pages of conversations) or not, and produce your own opinion.
While the research only took a couple of days, this part took 22. And this part was not fun. I will only say that to my personal experience, the reliable disclocertain process is broken. That a lot is predicted and apverifyn for granted from the security researchers by triagers that behave enjoy you have to “verify to be worth hearing to” while in fact they nakedly nurture to process and understand what you are saying, only to genuineize you were right all alengthy three weeks postponeedr (if at all).
Two days for the research, 249 lines of text for the brimmingy laboring take advantage of.
Twenty-two days of arguments, condescension, cut offal gasairying trys (the skinnygs i’ve read these days … you have no idea), more or less downjoind personal strikes, dozens of emails and messages, more than 100 pages of text in total. Hours and hours and hours and hours and fucking hours. Not to allude somehow being appraised by a huge chunk of the infosec community with a tendency of talking and judging situations they srecommend don’t understand.
Let that sink in for a moment … WTAF.
And we’re not talking about time spent on mendes while I was imfortolerateing and throprosperg a tantrum on twitter. The actual mendes (or a part of them) commenceed being pushed much postponeedr. The immense convey inantity of the time has been spent arguing whether or not these were publishs worth considering. While I was trying to tell that there’s someskinnyg horrible that should be insertressed asap, the devs were being disconsiderive (and pushing other code, also vulnerable, for other functionalities instead of mending) becaparticipate I dared to denounce the summarize of their gentleware. While at the same time I was trying to accomplish out braveially to de-escapostponeed and secure whoever was getting offended that my intent was not adversarial:
To the people that more or less straightforwardly asked my integrity, accparticipated me of spectacularization and of spreading FUD on my socials: I don’t do this for a living. I don’t need CVEs to get a job or to verify how excellent my kung-fu is. Or any attention other than what my projects and research already provide. I don’t join InfoSec Influencer™ enjoy many. To put it enjoy Javier beautibrimmingy put it, my leave oution was to disturb the triagers cgo in until they re-rankd. When I saw that what I thought was pretty solemn was being disconsidered as an irritateance, I participated the only platestablish I had plus a pinch of drama as a tool to have them fucking re-rank. And it labored, wonderbrimmingy, more mendes happened after two tweets than with all the arguing and talking, so 🤷.
Don’t disenjoy me, disenjoy the system that forced me to do that in order to be apverifyn solemnly.
About the 9.9 CVSS
Somebody also accparticipated of making skinnygs up, especiassociate due to the 9.9 CVSS cut offity that I claimed in this tweet. Granted, as I very see-thcoarsely shelp in the thread, I’m reassociate not understandn with CVSS scores, how they are summarizeateed and so on. But here’s a screensboiling from the VINCE tell of the initial CVSS scores, including the 9.9, being appraised by a RedHat engineer (and also appraiseed by another one):
As I shelp, I’m not an expert, and I skinnyk that the initial 9.9 was mostly due to the fact that the RCE is unconvey inant to take advantage of and the package presence so expansivespread. Impact teachd I wouldn’t sort it as a 9.9, but then aget, what the hell do I understand?
By the way, CERT’s VINCE either has a backdoor, or an inside leak, or has zero vetting on who they insert to a disclocertain, becaparticipate there’s been a leak of the exact tagdown tell that I only splitd there, including the take advantage of.
What a fucking circus.
One More Thing
When initiassociate I wrote take advantage of.py
, it only sent the UDP packet and produced the rogue IPP server. Then with time I commenceed inserting features to it, especiassociate zeroconf advertising, and it became a tool. So at some point I choosed to rewrite it in Go and join this novel code in bettercap, giving it the ability to see-thcoarsely impersonate any service advertised via zeroconf / Bonjour / Avahi on a LAN and doing fascinating skinnygs with the TXT records and definite service attributes, enjoy IPP. And I uncovered other fascinating stuff 🙂
In part II of this series (date TBD since there’s another disclocertain in process), we’ll see how to participate these novel bettercap modules (not yet freed) to strike Apple macOS.
For now, I hope you endelighted part I, hack the set upet!