CUPS < 2.0.3 - Multiple Vulnerabilities









Modern exploit mitigations draw attackers into a game of diminishing marginal returns. With each additional mitigation added, a subset of software bugs become unexploitable, and others become difficult to exploit, requiring application or even bug-specific knowledge that cannot be reused. The practical effect of exploit mitigations against any given bug or class of bugs is the subject of great debate amongst security researchers.

Despite mitigations, skilled and determined attackers alike remain undeterred. They cope by finding more bugs, and by crafting increasingly complex exploit chains. Attackers treat these exploits as closely-guarded, increasingly valuable secrets, and it's rare to see publicly-available full-fledged exploit chains. This visibility problem contributes to an attacker's advantage in the short term, but hinders broader innovation.

In this blog post, I describe an exploit chain for several bugs I discovered in CUPS, an open-source printing suite. I start by analyzing a relatively-subtle bug in CUPS string handling (CVE-2015-1158), an exploit primitive. I discuss key design and implementation choices that contributed to this bug. I then discuss how to build an exploit using the primitive. Next, I describe a second implementation error (CVE-2015-1159) that compounds the effect of the first, exposing otherwise unreachable instances of CUPS. Finally, I discuss the specific features and configuration options of CUPS that either helped or hindered exploitation.

By publishing this analysis, I hope to encourage transparent discourse on the state of exploits and mitigations, and inspire other researchers to do the same.


Cupsd uses reference-counted strings with global scope. When parsing a print job request, cupsd can be forced to over-decrement the reference count for a string from the request. As a result, an attacker can prematurely free an arbitrary string of global scope. I use this to dismantle ACL's protecting privileged operations, upload a replacement configuration file, then run arbitrary code.

The reference count over-decrement is exploitable in default configurations, and does not require any special permissions other than the basic ability to print. A cross-site scripting bug in the CUPS templating engine allows this bug to be exploited when a user browses the web. The XSS is reachable in the default configuration for Linux instances of CUPS, and allows an attacker to bypass default configuration settings that bind the CUPS scheduler to the 'localhost' or loopback interface.

Exploitation is near-deterministic, and does not require complex memory-corruption 'acrobatics'. Reliability is not affected by traditional exploit mitigations.

Improper Teardown - Reference Count Over-Decrement (CVE-2015-1158)

When freeing localized multi-value attributes, the reference count on the language string is over-decremented when creating a print job whose 'job-originating-host-name' attribute has more than one value. In 'add_job()', cupsd incorrectly frees the 'language' field for all strings in a group, instead of using 'ipp_free_values()'.


      * Free old strings…       ←  Even 'old' strings need to be freed.

       for (i = 0; i < attr->num_values; i ++)
        attr->values[i].string.text = NULL;
        if (attr->values[i].string.language)           ←  for all values in an attribute
    _cupsStrFree(attr->values[i].string.language);     ←  free the 'language' string
    attr->values[i].string.language = NULL;

In this case, 'language' field comes from the value of the 'attributes-natural-language' attribute in the request.

To specifically target a string and free it, we send a 'IPP_CREATE_JOB' or 'IPP_PRINT_JOB' request with a multi-value 'job-originating-host-name' attribute. The number of 'job-originating-host-name' values controls how many times the reference count is decremented. For a 10-value attribute, the reference count for 'language' is increased once, but decremented 10 times.

The over-decrement prematurely frees the heap block for the target string. The actual block address will be quickly re-used by subsequent allocations.

Dangling pointers to the block remain, but the content they point to changes when blocks are freed or reused. This is the basic exploit primitive upon which we build.

A Reflected XSS in the Web Interface (CVE-2015-1159)

The template engine is only vaguely context-aware, and only supports HTML. Template parsing and variable substitution and escaping are handled in 'cgi_copy()'.

The template engine has 2 special cases for 'href' attributes from HTML links. The first case 'cgi_puturi()' is unused in current templates, but the second case ends up being interesting.

The code is found in 'cgi_puts()', and escapes the following reserved HTML characters:

 These are replaced with their HTML entity equivalents ('<' etc...).

The function contains a curious special case to deal with HTML links in variable values. Here is a code snippet, from cgi-bin/template.c:650:

    if (*s == '<')
      * Pass <A HREF="url"> and </A>, otherwise quote it...

       if (!_cups_strncasecmp(s, "<A HREF=\"", 9))
        fputs("<A HREF=\"", out);
  s += 9;

   while (*s && *s != '\"')
          if (*s == '&')
            fputs("&", out);
      putc(*s, out);

     s ++;

         if (*s)
    s ++;

   fputs("\">", out);

For variable values containing '<a href="', all subsequent characters before a closing double-quote are subject to less restrictive escaping, where only the '&' character is escaped. The characters <>', and a closing " would normally be escaped, but are echoed unaltered in this context.

Note that the data being escaped here is client-supplied input, the variable value from the CGI argument. This code may have been intended to deal with links passed as CGI arguments. However, the template engine's limited context-awareness becomes an issue.

Take this example from templates/help-header.tmp:19:

 <P CLASS="l0"><A HREF="/help/{QUERY??QUERY={QUERY}:}">All Documents</A></P>

In this case, the CGI argument 'QUERY' is already contained inside a 'href' attribute of a link. If 'QUERY' starts with '<a href="', the double-quote will close the 'href' attribute opened in the static portion of the template. The remainder of the 'QUERY' variable will be interpreted as HTML tags.

Requesting the following URI will demonstrate this reflected XSS:

The 'QUERY' parametre is included in the page twice, leading to multiple unbalanced double-quotes. As such, the open comment string '<!--' is used to yield a HTML page that parses without errors.

Upstream Fixes

Apple Fix (April 16, 2015):

Official CUPS fix for downstream vendors (June 8, 2015):

Project Zero Bug

For those interested, the sample exploit can be found here:

Disclosure Timeline

March 20th, 2015 - Initial notification to Apple
April 16th, 2015 - Apple ships fix in Mac OS X 10.10.3
June 8th, 2015 - CUPS ships official fix in CUPS 2.0.3
June 18th, 2015 - Disclosure + 90 days
June 19th, 2015 - P0 publication

Attack Surface Reduction in CUPS 2.0.3+

CUPS 2.0.3 and 2.1 beta contains several prescient implementation changes to limit the risk and impact of future similar bugs:

Configuration value strings are now logically separated from the string pool, allocated by strdup() instead.
LD_* and DYLD_* environment variables are blocked when CUPS is running as root.
The localhost listener is removed when 'WebInterface' is disabled (2.1 beta only).


Thanks to Ben Hawkes, Stephan Somogyi, and Mike Sweet for their comments and edits.


No one prints anything anymore anyways.