Wednesday, July 11, 2018 at 7:25PM

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\"...

", 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*

" 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

" 196 " (regex" 197 " #\"^%s$\"" /* TempDir */ 198 " #\"^%s/\"" /* TempDir/... */ 199 " #\"^%s$\"" /* CacheDir */ 200 " #\"^%s/\"" /* CacheDir/... */ 201 " #\"^%s$\"" /* StateDir */ 202 " #\"^%s/\"" /* StateDir/... */ 203 "))

", 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.