Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CVE-2008-0047: cgiCompileSearch buffer overflow #2729

michaelrsweet opened this issue Feb 28, 2008 · 4 comments

CVE-2008-0047: cgiCompileSearch buffer overflow #2729

michaelrsweet opened this issue Feb 28, 2008 · 4 comments


Copy link

@michaelrsweet michaelrsweet commented Feb 28, 2008

Version: 1.3.6 User: mike

gpg: encrypted with 2048-bit ELG-E key, ID 5F6A6F2E, created 2004-02-03
"iDEFENSE Vendor Disclosure"
gpg: encrypted with 2048-bit RSA key, ID DAF1AB3E, created 2007-05-09
"Apple Product Security"
iDefense Security Advisory XX.XX.XX


The Common UNIX Printing System, more commonly referred to as CUPS,
provides a standard printer interface for various Unix based operating
systems. For more information, see the vendor's website found at the
following link.


Remote exploitation of a heap based buffer overflow vulnerability in
CUPS, as included in various vendors' operating system distributions,
could allow an attacker to execute arbitrary code with the privileges
of the affected service.

When configured to share printers, CUPS listens on TCP port 631 for
requests. This interface provides access to several CGI applications.
These applications are used to administer CUPS, and to provide
information about print jobs. These applications all use a common
search function called cgiCompileSearch(). This function takes a user
provide search expression, and compiles it into a regular expression.
When parsing the string, insufficient space is allocated for a buffer
that stores the compiled result. By passing a malformed search request,
an attacker can trigger a heap based buffer overflow.


Exploitation of this vulnerability results in the execution of arbitrary
code with the privileges of the affected service. Depending on the
underlying operating system and distribution, CUPS may run as the lp,
daemon, or a different user.

In order to exploit this vulnerability remotely, the targeted host must
be sharing a printer(s) on the network. If a printer is not being
shared, CUPS only listens on the localhost interface, and the scope of
this vulnerability would be limited to local privilege escalation.


iDefense has confirmed the existence of this vulnerability in CUPS
version 1.3.5. Previous versions may also be affected.


Disabling printer sharing will prevent this vulnerability from being
exploited remotely. However, local users will still be able to elevate
their privileges to the user CUPS runs as.


iDefense is currently working with the vendor to address this issue.
Since there is no vendor fix at this time, please handle this
information with sensitivity.


A Mitre Corp. Common Vulnerabilities and Exposures (CVE) number has not
been assigned yet.


XX/XX/XXXX Initial vendor notification


This vulnerability was reported to iDefense by regenrecht.

Get paid for vulnerability research

Free tools, research and upcoming events


Copyright ? 2008 iDefense, Inc.

Permission is granted for the redistribution of this alert
electronically. It may not be edited in any way without the express
written consent of iDefense. If you wish to reprint the whole or any
part of this alert in any other medium other than electronically,
please e-mail for permission.

Disclaimer: The information in the advisory is believed to be accurate
at the time of publishing based on currently available information. Use
of the information constitutes acceptance for use in an AS IS condition.
There are no warranties with regard to this information. Neither the
author nor the publisher accepts any liability for any direct,
indirect, or consequential loss or damage arising from use of, or
reliance on, this information.gpg: Signature made Tue Feb 26 14:58:29 2008 PST using DSA key ID 8C487C19

Copy link
Collaborator Author

@michaelrsweet michaelrsweet commented Feb 28, 2008 User: mike

Additional information from reporter:

--- Topic ---
CUPS remote code execution.

--- Name of product ---
Common UNIX Printing System

According to "[CUPS] is the standard printing system in
Mac OS X and most Linux distributions".

--- Version tested ---
CUPS v1.3.5 (current stable release) under Linux 2.6.23/IA32 with glibc 2.7

--- Description ---
Because of heap based buffer size mismanagement it is possible to precisely
overwrite adjacent memory chunks and change program execution flow. Buggy
code is located in function cgiCompileSearch() from cgi-bin/search.c. It is
being used for parsing user provided (via CUPS Web Interface) data in
printers.cgi, classes.cgi and help.cgi.

--- Impact ---
Remote code execution with privileges of uid=daemon, gid=lp.

--- Analysis ---
Please take a look at cgi-bin/search.c, function cgiCompileSearch() (starts
at line 35). It's quite a long piece of code, so I won't be pasting it here.
I'll be referring to line numbers from source code of version 1.3.5.

At first it seems that developers did a good job taking care of all possible
buffer size problems first by malloc'ing big buffer at line [68] and later
by increasing (if there's a need) it's size appropriately at line [165].

When you focus on [165], you'll see that apparently developers made a wrong
assumption that (already escaped, [213-216]) string 'lword' (set at previous
while() iteration at line [252]) extended by 'wlen * 2' (where 'wlen' is
length of currently processed search query element) will always be shorter or
equal to 'wlen * 4'. Obviously for causing overflow there are few problems
to consider (line [64] to begin with), but they can be solved and buffer 's'
(line [68]) can be overflow.

(Besides of that I'm afraid '2 * strlen(prefix) + 4' from [165] may be not
enough for storing 'prefix' copies at lines [197], [232] and [238], but
that's not really important having such a nice overflow already.)

For a better understanding imagine you send this kind of query:
"[...200 times '^'...] AND [...100 times '.'...]" (200+5+100 = 305 bytes)

At line [64] 'slen' becomes 3*305 = 915. As 915 < 1024, 'slen' is set to
1024 (line [66]). 1024-bytes long output buffer 's' is being malloc'ed at
line [68].

First we enter main while() loop at [80]. CGI program will look for the end
of the first word iterating over input string until space character is found
(or end of quote). Now we have length of first query word in 'wlen' - it is
200 (line [130]). Then we move on to [165], where 'wlen' is reassigned to
'(sptr - s) + 4 * wlen + 2 * strlen(prefix) + 4'. '(sptr - s)' is 0, because
there's nothing yet kept in buffer 's', '4 * wlen' is 800 and
'2 * strlen(prefix) + 4' is 8, because 'prefix' for now is "._". In total,
'wlen' becomes 0+4_200+2*2+4 = 808. As 808 < 1024 (line [167]) buffer 's'
won't be realloc'ed/resized. At [197] 'prefix' is appended to 's', then our
whole query word (the first one, those 200 '^') are appended as well [213],
but as '^' is among restricted characters it is being escaped, which results
in 400 (instead of 200) more chars in buffer 's'. Right now 's' is 2+400 =
402 bytes long. Because 'lword' is not yet assigned we enter if() branch at
[248], where our already escaped word (those 200 '^' that became 400 chars)
becomes 'lword'.

Next while() iteration parses " AND " (line [136]) and sets 'prefix' to ".*".

Last while() iteration sets 'wlen' at [130] to length of our second query
word, which is 100 (100 times '.'). Again, at line [165] 'wlen' will be
reassigned. This time it will become 402 (bytes already stored in 's') +
4_130 ('4 * wlen') + 8 ('2_ strlen(prefix) + 4'). In total: 930. Still,
930 < 1024, so no realloc() takes place. Line [197] - 'prefix' is appended
to buffer 's' ('s' becomes 404 bytes long). Lines [207-217] - escaping &
appending characters from second word of query (those 100 '.') - buffer 's'
stores 404+2_100 = 604 bytes. As 'prefix' is set to "." and 'lword' is valid
we enter if() branch at line [227]. Already escaped second word of query
becomes 'lword2' [230]. ".
|._" is appended to 's' at [232] - that makes 's'
604+5 = 609 chars long. 'lword2' is appended to 's' at [235]. Now 's'
contains 609+200 = 809 bytes. ".*" is appended at [238], 's' is 811 bytes
long. At line [241] previously set 'lword' finally gets appended to 's'
leading to buffer overflow: 811+400 = 1211. Taking into concern malloc()
alignment you'll get 187 bytes overrun.

--- Exploit ---
I'm attaching a working exploit that will connect to vulnerable CUPS service
and show the content of /etc/passwd from remote machine.

Exploit abuses unlink() macro from glibc-2.7/malloc/malloc.c:

#define unlink(P, BK, FD) {
FD = P->fd;
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P);
else {
if (!in_smallbin_range (P->size)
&& __builtin_expect (P->fd_nextsize != NULL, 0)) {
assert (P->fd_nextsize->bk_nextsize == P);
assert (P->bk_nextsize->fd_nextsize == P);
if (FD->fd_nextsize == NULL) {
} else {
P->fd_nextsize->bk_nextsize = P->bk_nextsize;
P->bk_nextsize->fd_nextsize = P->fd_nextsize;

One can see that by faking P->fd/bk and setting P->size to >= 512, address
pointed by P->fd/bk_nextsize->bk/fd_nextsize can be overwritten by arbitrary
data. That's because assert() checks will not be evaluated in non-debug
builds of glibc.

The following search query will be crafted:
"[fake malloc chunk][shellcode][overflow chunk]" AND [...251 times '.'...]

'overflow chunk' will overwrite next heap chunk header and cheat free() to
operate on 'fake malloc chunk'. 'fake malloc chunk' will abuse unlink()
macro. This way we will overwrite free() GOT entry with address of shellcode.

To make exploit work you'll need 2 memory addresses. First one ('got_addr')
is a free() entry from help.cgi GOT. It can be easily obtained by:

$ objdump -R /usr/lib/cups/cgi-bin/help.cgi | grep ' free'
08054344 R_386_JUMP_SLOT free

Second address ('s_addr') is returned by malloc(slen) at line [68] from
cgi-bin/search.c. If you don't have time for recompiling and/or playing with
debugger it's quite easy to catch it when help.cgi is started by cupsd. You
can use the following piece of bash script:

while true; do
PID=$(pidof help.cgi);
if [ "x$PID" != "x" ]; then
gdb - $PID;

And then, inside gdb:

(gdb) disas cgiCompileSearch
Look for the first occurrence of "call malloc@plt" and set bp there.
(gdb) b *(cgiCompileSearch+78)
(gdb) c
(gdb) ni
(gdb) i r eax

Under Linux, having /proc/sys/kernel/randomize_va_space = 1, this address
will stay the same until cupsd restart (which usually happens only at
reboot), so it can be brute forced without a problem. That's because not
cupsd will be crashing, but it's child process - help.cgi.

Also, after setting both addresses you'll have to make sure that variables
'overflow' and 'chunk' do not contain any of restricted characters:
NULL ^.[$()|*+?{. In case of this kind of problem you can try decreasing
MAX_SCODE_LEN and tweaking arg1/arg2 padding/align accordingly - that would
most probably cause malloc() to place buffer 's' at a different hopefully
restricted-chars-less address.

(You can also increase MAX_SCODE_LEN and make malloc() place 's' completely
elsewhere and/or feed CUPS with a really long shellcode, but this involves
few bigger changes in exploit code like modifying 'overflow' variable.)

--- Workaround ---
Restrict remote access to CUPS service.

Copy link
Collaborator Author

@michaelrsweet michaelrsweet commented Feb 28, 2008 User: mike

Patch attached which adds the "AND foo" space needed to the buffer size calculation.

Copy link
Collaborator Author

@michaelrsweet michaelrsweet commented Apr 1, 2008 User: mike

Fixed in Subversion repository.

Copy link
Collaborator Author

@michaelrsweet michaelrsweet commented Apr 1, 2008


Index: search.c

--- search.c (revision 7354)
+++ search.c (working copy)
@@ -167,7 +167,9 @@
* string + RE overhead...

  •  wlen = (sptr - s) + 4 \* wlen + 2 \* strlen(prefix) + 4;
  •  wlen = (sptr - s) + 2 \* 4 \* wlen + 2 \* strlen(prefix) + 11;
  •  if (lword)
  •    wlen += strlen(lword);

    if (wlen > slen)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
1 participant