CUPS Local Privilege Escalation and Sandbox Escapes
Gotham Digital Science has discovered multiple vulnerabilities in Apple’s CUPS print system affecting macOS 10.13.4 and earlier and multiple Linux distributions. All information in this post has been shared with Apple and other affected vendors prior to publication as part of the coordinated disclosure process. All code is excerpted from Apple’s open source CUPS repository located at https://github.com/apple/cups.
The vulnerabilities allow for local privilege escalation to root (CVE-2018-4180), multiple sandbox escapes (CVE-2018-4182 and CVE-2018-4183), and unsandboxed root-level local file reads (CVE-2018-4181). A related AppArmor-specific sandbox escape (CVE-2018-6553) was also discovered affecting Linux distributions such as Debian and Ubuntu. When chained together, these vulnerabilities allow an unprivileged local attacker to escalate to unsandboxed root privileges on affected systems.
Affected Linux systems include those that allow non-root users to modify cupsd.conf such as Debian and Ubuntu. Redhat and related distributions are generally not vulnerable by default. Consult distribution-specific documentation and security advisories for more information.
The vulnerabilities were patched in macOS 10.13.5, and patches are currently available for Debian and Ubuntu systems. GDS would like to thank Apple, Debian, and Canonical for working to patch the vulnerabilities, and CERT for assisting in vendor coordination.
Credits:
CVE-2018-4180 – Dan Bastone
CVE-2018-4182 – Dan Bastone
CVE-2018-4183 – Dan Bastone and Eric Rafaloff
CVE-2018-6553 – Dan Bastone
CVE-2018-4181 – Eric Rafaloff and John Dunlap
Timeline:
02/21/2018 – Initial disclosure to Apple
02/26/2018 – Initial disclosure to Debian and Canonical (CVE-2018-6553)
03/02/2018 – Issues confirmed by Apple
05/04/2018 – Apple requests delay of public disclosure due to downstream vendor coordination with CERT
06/01/2018 – Apple releases fixes in macOS 10.13.5
06/05/2018 – Apple publishes patches on their public Github repository
07/11/2018 – Public disclosure
Apple Security Advisory (updated 7/11/18):
https://support.apple.com/HT208849
Linux Vendor Advisories:
https://www.debian.org/security/2018/dsa-4243
https://usn.ubuntu.com/3713-1/
Patches:
https://github.com/apple/cups/commit/d47f6aec436e0e9df6554436e391471097686ecc
This post describes the privilege escalation and sandbox escape vulnerabilities and their fixes. Exploit code is currently being withheld, and will be released at a later date. Details of the root-level local file read issue (CVE-2018-4181) will be released in a follow-up blog post.
Local Privilege Escalation to Root Due to Insecure Environment Variable Handling – CVE-2018-4180
Overview:
Affected versions of CUPS allow for the SetEnv and PassEnv directives to be specified in the cupsd.conf file, which is editable by non-root users using the cupsctl binary. This allows attacker-controlled environment variables to be passed to CUPS backends, some of which are run as root. By passing malicious values in environment variables to affected backends, it is possible to execute an attacker-supplied binary as root, subject to sandbox restrictions.
Details:
Multiple vulnerable code paths exist for this issue, one of which is shown below. The environment variable is used to construct a filename on lines 804 and 807 that is executed on line 819.
cups/backend/dnssd.c: 800 /* 801 * Get the filename of the backend... 802 */ 803 804 if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL) 805 cups_serverbin = CUPS_SERVERBIN; 806 807 snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme); […] 817 fprintf(stderr, "DEBUG: Executing backend \"%s\"...\n", filename); 818 819 execv(filename, argv);
Fix:
The issue was remediated by moving the SetEnv and PassEnv configuration directives from cupsd.conf to cups-files.conf, which is only editable by root. Additionally, sensitive environment variables that may have security implications have been restricted and can no longer be set using these directives. This effectively prevents all known exploit vectors.
macOS cups-exec Sandbox Bypass Due to Insecure Error Handling – CVE-2018-4182
Overview:
It is possible to cause cups-exec to execute backends without a sandbox profile by causing cupsdCreateProfile() to fail. An attacker that has obtained sandboxed root access can accomplish this by setting the CUPS temporary directory to immutable using chflags, which will prevent the profile from being written to disk.
Chaining this vulnerability with CVE-2018-4180 results in unsandboxed root code execution.
Details:
When /var/spool/cups/tmp is set to immutable, the following sequence will fail, resulting in DefaultProfile being set to NULL in cupsdStartServer(). This error is ignored, and execution continues.
DefaultProfile = NULL cupsdCreateProfile() ^ cupsTempFile2() | cupsTempFd() | open("/var/spool/cups/tmp/...") = -1 [Operation not permitted]
When cupsdStartProcess() is later called to execute a backend, the NULL default profile is passed as an argument:
scheduler/client.c:
3819 if (cupsdStartProcess(command, argv, envp, infile, fds[1], CGIPipes[1],
3820 -1, -1, root, DefaultProfile, NULL, &pid) < 0)
The process is then executed unsandboxed, because the profile is NULL.
scheduler/process.c: 455 int /* O - Process ID or 0 */ 456 cupsdStartProcess( 457 const char *command, /* I - Full path to command */ 458 char *argv[], /* I - Command-line arguments */ 459 char *envp[], /* I - Environment */ 460 int infd, /* I - Standard input file descriptor */ 461 int outfd, /* I - Standard output file descriptor */ 462 int errfd, /* I - Standard error file descriptor */ 463 int backfd, /* I - Backchannel file descriptor */ 464 int sidefd, /* I - Sidechannel file descriptor */ 465 int root, /* I - Run as root? */ 466 void *profile, /* I - Security profile to use */ 467 cupsd_job_t *job, /* I - Job associated with process */ 468 int *pid) /* O - Process ID */ 469 { [...] 545 /* 546 * Use helper program when we have a sandbox profile... 547 */ 548 549 #if !USE_POSIX_SPAWN 550 if (profile) 551 #endif /* !USE_POSIX_SPAWN */ 552 { 553 snprintf(cups_exec, sizeof(cups_exec), "%s/daemon/cups-exec", ServerBin); 554 snprintf(user_str, sizeof(user_str), "%d", user); 555 snprintf(group_str, sizeof(group_str), "%d", Group); 556 snprintf(nice_str, sizeof(nice_str), "%d", FilterNice); 557 558 real_argv[0] = cups_exec; 559 real_argv[1] = (char *)"-g"; 560 real_argv[2] = group_str; 561 real_argv[3] = (char *)"-n"; 562 real_argv[4] = nice_str; 563 real_argv[5] = (char *)"-u"; 564 real_argv[6] = user_str; 565 real_argv[7] = profile ? profile : "none"; 566 real_argv[8] = (char *)command;
The following debug output shows execution of exploits for CVE-2018-4180 and CVE-2018-4182.
CUPS_SERVERBIN is set to the attacker-controlled directory by the exploit:
d [18/Feb/2018:21:50:21 -0500] cupsdSetEnv: CUPS_SERVERBIN=/tmp/exploit [...] D [18/Feb/2018:21:50:21 -0500] [Job 69] envp[1]="CUPS_SERVERBIN=/tmp/exploit"
dnssd is executed as root with a valid sandbox profile:
cupsdStartProcess(command="/usr/libexec/cups/backend/dnssd", argv=0x7ffee67eac30, envp=0x7ffee67ece90, infd=-1, outfd=-1, errfd=15, backfd=17, sidefd=19, root=1, profile=0x7f9339d1acb0, job=0x7f9339e203d0(69), pid=0x7f9339e20538) = 2463
dnssd then executes its sub-backend from the attacker-controlled CUPS_SERVERBIN containing the exploit payload. On this initial execution, the write to /exploit.txt will fail, and the payload will set the CUPS temp directory to immutable.
D [18/Feb/2018:21:50:21 -0500] [Job 69] Executing backend \"/tmp/exploit/backend/dnssd\"... D [18/Feb/2018:21:50:21 -0500] [Job 69] /tmp/exploit/backend/dnssd: line 2: /exploit.txt: Operation not permitted
The payload then triggers the exploit again:
D [18/Feb/2018:21:50:21 -0500] [Job 70] envp[1]="CUPS_SERVERBIN=/tmp/exploit"
This time, the sandbox profile is prevented from being written:
d [18/Feb/2018:21:50:21 -0500] cupsdCreateProfile(job_id=70, allow_networking=0) = NULL E [18/Feb/2018:21:50:21 -0500] Unable to create security profile: Operation not permitted d [18/Feb/2018:21:50:21 -0500] cupsdCreateProfile(job_id=70, allow_networking=1) = NULL E [18/Feb/2018:21:50:21 -0500] Unable to create security profile: Operation not permitted
This causes cupsdStartProcess to be called with a NULL profile argument, executing dnssd as root outside the sandbox:
d [18/Feb/2018:21:50:21 -0500] cupsdStartProcess(command="/usr/libexec/cups/backend/dnssd", argv=0x7ffee67fa7a0, envp=0x7ffee67fca00, infd=-1, outfd=-1, errfd=14, backfd=16, sidefd=18, root=1, profile=0x0, job=0x7f9339f12480(70), pid=0x7f9339f125e8) = 2469
Finally, the sub-backend executes outside the sandbox, writes to /exploit.txt, and exits successfully:
D [18/Feb/2018:21:50:21 -0500] [Job 70] Executing backend \"/tmp/exploit/backend/dnssd\"... D [18/Feb/2018:21:50:21 -0500] [Job 70] PID 2469 (/usr/libexec/cups/backend/dnssd) exited with no errors.
Fix:
The issue was remediated through the addition of error-handling code and sanity checks that prevent backends from executing outside of a sandbox profile.
macOS cups-exec Sandbox Bypass Due to Profile Misconfiguration – CVE-2018-4183
Overview:
The sandbox profile dynamically generated by cupsdCreateProfile() unintentionally allows write access to /etc/cups. This can be used by an attacker that has obtained sandboxed root access to alter /etc/cups/cups-files.conf, leading to unsandboxed root code execution.
Details:
The issue is caused by the fact that both ServerRoot and StateDir are set to /etc/cups. The sandbox profile first denies write access to ServerRoot, but subsequently allows write access to StateDir. This is shown in cupsdCreateProfile() below.
cups/scheduler/process.c: 142 cupsFilePrintf(fp, 143 "(deny file-write*\n" 144 " (regex" 145 " #\"^%s$\"" /* ServerRoot */ 146 " #\"^%s/\"" /* ServerRoot/... */ 147 " #\"^/private/etc$\"" 148 " #\"^/private/etc/\"" [...] 194 cupsFilePrintf(fp, 195 "(allow file-write* file-read-data file-read-metadata\n" 196 " (regex" 197 " #\"^%s$\"" /* TempDir */ 198 " #\"^%s/\"" /* TempDir/... */ 199 " #\"^%s$\"" /* CacheDir */ 200 " #\"^%s/\"" /* CacheDir/... */ 201 " #\"^%s$\"" /* StateDir */ 202 " #\"^%s/\"" /* StateDir/... */ 203 "))\n", 204 temp, temp, cache, cache, state, state);
This results in the following conflicting sandbox profile directives, ultimately allowing write access to /etc/cups:
(deny file-write* (regex #"^/private/etc/cups$" #"^/private/etc/cups/" #"^/private/etc$" #"^/private/etc/" #"^/usr/local/etc$" #"^/usr/local/etc/" #"^/Library$" #"^/Library/" #"^/System$" #"^/System/")) (allow file-write* file-read-data file-read-metadata (regex #"^/private/var/spool/cups/tmp$" #"^/private/var/spool/cups/tmp/" #"^/private/var/spool/cups/cache$" #"^/private/var/spool/cups/cache/" #"^/private/etc/cups$" #"^/private/etc/cups/"))
Fix:
The sandbox profile was corrected to disallow writes to /etc/cups by removing the StateDir entries.
AppArmor cupsd Sandbox Bypass Due to Use of Hard Links – CVE-2018-6553
Overview:
It is possible to bypass the AppArmor cupsd sandbox by invoking the dnssd backend using an alternate name that has been hard linked to dnssd. Both Debian and Ubuntu use AppArmor and shipped the mdns backend in this manner, in contrast to macOS and other systems that use symbolic links. Invoking the mdns backend causes the AppArmor profile to treat the backend as 3rd party, removing sandbox restrictions.
Details:
The cups backend directory and an excerpt of the cups .deb post-installation script shows the use of hard links.
ls -l /usr/lib/cups/backend/{dnssd,mdns} -rwxr--r-- 3 root root 18424 Aug 22 17:26 /usr/lib/cups/backend/dnssd -rwxr--r-- 3 root root 18424 Aug 22 17:26 /usr/lib/cups/backend/mdns $ stat /usr/lib/cups/backend/{dnssd,mdns} File: '/usr/lib/cups/backend/dnssd' Size: 18424 Blocks: 40 IO Block: 4096 regular file Device: 801h/2049d Inode: 12615 Links: 3 Access: (0744/-rwxr--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2018-02-18 13:08:11.761022386 -0800 Modify: 2017-08-22 17:26:53.000000000 -0700 Change: 2018-02-14 11:32:56.356309575 -0800 Birth: - File: '/usr/lib/cups/backend/mdns' Size: 18424 Blocks: 40 IO Block: 4096 regular file Device: 801h/2049d Inode: 12615 Links: 3 Access: (0744/-rwxr--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2018-02-18 13:08:11.761022386 -0800 Modify: 2017-08-22 17:26:53.000000000 -0700 Change: 2018-02-14 11:32:56.356309575 -0800 Birth: - DEBIAN/postinst: (from cups_2.2.1-8_amd64.deb) 71 if [ "$module" = "dnssd" ]; then 72 ln /usr/lib/cups/backend/dnssd /usr/lib/cups/backend/mdns 73 fi
The AppArmor sandbox profile explicitly whitelists known backends, but neglects to include mdns. Because mdns is hard linked to dnssd, it matches the rule on line 95. If symbolic links were used instead, line 81 would match and the backend would be sandboxed as intended.
/etc/apparmor.d/usr.sbin.cupsd: 79 # backends which come with CUPS can be confined 80 /usr/lib/cups/backend/bluetooth ixr, 81 /usr/lib/cups/backend/dnssd ixr, 82 /usr/lib/cups/backend/http ixr, 83 /usr/lib/cups/backend/ipp ixr, 84 /usr/lib/cups/backend/lpd ixr, 85 /usr/lib/cups/backend/parallel ixr, 86 /usr/lib/cups/backend/serial ixr, 87 /usr/lib/cups/backend/snmp ixr, 88 /usr/lib/cups/backend/socket ixr, 89 /usr/lib/cups/backend/usb ixr, 90 # we treat cups-pdf specially, since it needs to write into /home 91 # and thus needs extra paranoia 92 /usr/lib/cups/backend/cups-pdf Px, 93 # third party backends get no restrictions as they often need high 94 # privileges and this is beyond our control 95 /usr/lib/cups/backend/* Cx -> third_party,
Fix:
The AppArmor profile was updated to explicitly sandbox the mdns backend.
*** This is a Security Bloggers Network syndicated blog from Blog authored by Dan Bastone. Read the original post at: http://feedproxy.google.com/~r/GdsSecurityBlog/~3/8AT_mPgedgM/cups-local-privilege-escalation-and-sandbox-escapes.html