POSITRON SECURITY LLC
                    <http://www.positronsecurity.com/>


                        Security Advisory #2009-000
          Multiple Vulnerabilities in MapServer v5.2.1 and v4.10.3



Author:  Joe Testa <jt _at_sign_ positronsecurity_dot_com>
Date:    March 30th, 2009
URL:     <http://www.positronsecurity.com/advisories/2009-000.html>



I. Executive Summary

    MapServer [1] is a popular open-source, multi-platform program for
creating interactive map applications.  It was originally developed by the
University of Minnesota with support from the U.S. National Aeronautics
and Space Administration (NASA) [2].  It is currently supported by the
Open Source Geospatial Foundation [3].

    Several security vulnerabilities were identified in MapServer v5.2.1
and v4.10.3.  All users are urged to upgrade to v5.2.2 or v4.10.4 as soon
as possible to protect against attack.



II. Overview

    During an audit of the MapServer v5.2.1 source code, five (5)
vulnerabilities were identified ranging from low to medium/high severity.
They include stack and heap overflows, a relative path writing weakness,
a file content leakage, as well as a file existence leakage.  Furthermore,
after reporting these issues to the vendor, a second audit by the
project maintainer not only determined that v4.10.3 was also affected,
but that four (4) additional stack overflows existed in the code as well.



III. Detailed Description


A. Stack-based Buffer Overflow (CVE-2009-0839)
   Severity: Medium/High

    A buffer overflow that could allow for the execution of arbitrary
code exists in the "mapserv" CGI program.  In mapserv.c are the following
lines of code:


406:   strncpy(mapserv->Id, mapserv->request->ParamValues[i], IDSIZE);

1112:  int main(int argc, char *argv[]) {
1114:    char buffer[1024], *value=NULL;

1783:    sprintf(buffer, "%s%s%s%s", mapserv->map->web.imagepath, \
         mapserv->map->name, mapserv->Id, MS_QUERY_EXTENSION);

1826:  }


    Notice that no size checking is done at line 1783 on the buffer named
"buffer", defined at line 1114.  It is filled with three variables and one
static string.  The first variable, "mapserv->map->web.imagepath", is
assigned the value of the IMAGEPATH attribute inside the *.map file stored
on the server.  The second, "mapserv->map->name", is taken from the NAME
attribute inside the same map file.  The third variable, "mapserv->Id", is
read from user input at line 406, though it is restricted to IDSIZE (128)
bytes.  Thus, a buffer overflow can be achieved by creating a map file on
the server with overly long IMAGEPATH and/or NAME attributes; their values
will be stored past the end of "buffer" and will overwrite saved register
values.  If the following specially-crafted map file ("bof.map") is stored
on the server (either by creating it directly, or tricking a legitimate
user into placing it onto the file system):

    MAP
      NAME {"A" x 1072}GGGG
      STATUS ON
      SIZE 100 100
      EXTENT 0 0 1 1

      WEB
        IMAGEPATH "/tmp/"
        TEMPLATE "/tmp/template.html"
      END
    END

... and if the following request is made:

    <http://site/cgi-bin/mapserv?map=/tmp/bof.map&mode=query&
     queryfile=/tmp/queryfile.qf&savequery=1&id=HHHHIIIIJJJJKKKK<

... then the following crash occurs on a CentOS v5.2/x86 platform:

    Program received signal SIGSEGV, Segmentation fault.
    0x0804fdca in main ()
    (gdb) disassemble main
    [...]
    0x0804fd9e <main+2318>:	call   0x804bee0 <sprintf@plt>
    0x0804fda3 <main+2323>:	mov    %edi,0x4(%esp)
    0x0804fda7 <main+2327>:	mov    (%esi),%eax
    0x0804fda9 <main+2329>:	mov    0x10(%eax),%eax
    0x0804fdac <main+2332>:	mov    %eax,(%esp)
    0x0804fdaf <main+2335>:	call   0x8074aa0 <msSaveQuery>
    0x0804fdb4 <main+2340>:	test   %eax,%eax
    0x0804fdb6 <main+2342>:	je     0x804fb02 <main+1650>
    0x0804fdbc <main+2348>:	add    $0x4e8,%esp
    0x0804fdc2 <main+2354>:	pop    %ecx
    0x0804fdc3 <main+2355>:	pop    %ebx
    0x0804fdc4 <main+2356>:	pop    %esi
    0x0804fdc5 <main+2357>:	pop    %edi
    0x0804fdc6 <main+2358>:	pop    %ebp
    0x0804fdc7 <main+2359>:	lea    0xfffffffc(%ecx),%esp
    0x0804fdca <main+2362>:	ret    
    [...]
    (gdb) i r
    eax            0x1	1
    ecx            0x47474747	1195853639
    edx            0x0	0
    ebx            0x48484848	1212696648
    esp            0x47474743	0x47474743
    ebp            0x4b4b4b4b	0x4b4b4b4b
    esi            0x49494949	1229539657
    edi            0x4a4a4a4a	1246382666
    eip            0x804fdca	0x804fdca <main+2362>
    [...]

Because the ECX register can be controlled (0x47 is the ASCII code for the
letter "G"), the attacker can control the ESP register through the
"lea 0xfffffffc(%ecx),%esp" instruction at 0x0804fdc7.  The attacker can
execute code in mapserv's process space by setting the ESP register to an
address that holds a reference to code and letting the "ret" instruction
execute at 0x0804fdca; this will assign the EIP register an
attacker-supplied value.

    This overflow may be triggered by user input as well.  Note that the
"mapserv->Id" character array is defined as IDSIZE bytes long and that the
strncpy() call at mapserv.c:406 uses IDSIZE too.  Since strncpy(3) does
not null-terminate the destination string if the source string is greater
than its size argument, an attacker can set the "id" CGI variable to 128
characters, causing the sprintf() call at mapserv.c:1783 to continue
writing bytes into "buffer" from heap memory (as the "mapserv" variable is
created with malloc(3)) until a zero byte is found.  While this method of
triggering the overflow does not require a corrupt map, it does require
the attacker to manipulate heap memory into a favorable state.  The
difficulty of this task has not been measured.



B.  Heap-based Buffer Underflow (CVE-2009-0840)
    Severity: Medium

    By providing a specially-crafted POST request to the "mapserv" CGI
application, an out-of-bounds memory write can be triggered.
Specifically, by setting the "CONTENT_LENGTH" environment variable to -1,
the code will write a zero byte to "data[ -1 ]", where "data" is a
character array allocated on the heap via malloc(3).

When the following is executed locally on the command line:

    jdog@thegibson:~$ REQUEST_METHOD=POST CONTENT_LENGTH=-1 \
    /path/to/mapserv

... the following occurs:  execution will flow from main() in mapserv.c
and call function loadParams() in cgiutil.c at mapserv.c:1201.
loadParams() will then call readPostBody() (see below) at cgiutil.c:125.


    static char *readPostBody( cgiRequestObj *request ) 
    {
      char *data; 
      int data_max, data_len, chunk_size;

      msIO_needBinaryStdin();

      /* [...] */
      if( getenv("CONTENT_LENGTH") != NULL ) {
55:     data_max = atoi(getenv("CONTENT_LENGTH"));
56:     data = (char *) malloc(data_max+1);
        if( data == NULL ) {
          [...]
          exit( 1 );
        }

63:     if( (int) msIO_fread(data, 1, data_max, stdin) < data_max ) {
          [...]
          exit(1);
        }

69:     data[data_max] = '\0';
        return data;
      }


The "data_max" signed integer variable will be set to -1 at cgiutil.c:55.
The "data" character array is assigned the pointer returned from
"malloc( -1 + 1 )" on the following line (56).  Note that the Linux man
page for malloc(3) (dated 2007-09-15) says:

    "If [the argument] is 0, then malloc() returns either NULL,
     or a unique pointer value that can later be successfully
     passed to free()."

On an Ubuntu v8.04 system, malloc(0) is observed to return a non-null
pointer.  Thus, execution continues.  The msIO_fread() function call at
cgiutil.c:63 returns 0, so execution reaches cgiutil.c:69, which contains
"data[data_max] = '\0';".  Because "data_max" is set to -1, this causes
the program to write a zero byte outside the bounds of the "data" array
in heap memory.

    This can be triggered remotely any time MapServer is hosted on
a web server that does not sanitize the "CONTENT_LENGTH" field into a
non-negative value before passing it on to the CGI layer.  Apache v2.x is
known to perform this sanitization (it rejects the request before
executing the "mapserv" CGI binary with HTTP error 413: "Request Entity
Too Large", presumably because it interprets the "Content-Length" header
as an unsigned value), and thus protects MapServer from being exploited
in this way.  Because a comprehensive survey of web server software is
beyond the scope of this report, it is not known what web servers will
expose this vulnerability to a remote attacker.



C.  Relative File Path Writing (CVE-2009-0841)
    Severity: Medium/High

    The "mapserv" CGI application can be tricked into creating files in
arbitrary locations in the file system with arbitrary names.

    The pertinent lines of code follows:

    [mapserv.c]
1783:   sprintf(buffer, "%s%s%s%s", mapserv->map->web.imagepath, \
            mapserv->map->name, mapserv->Id, MS_QUERY_EXTENSION);
1784:   if((status = msSaveQuery(mapserv->map, buffer)) != MS_SUCCESS) \
            return status;

    [mapquery.c]
89:     stream = fopen(filename, "wb");
90:     if(!stream) {
91:         [...]
92:         return(MS_FAILURE);
93:     }

    As described in Section III(A), the "buffer" array in mapserv.c:1783
is filled with the contents of the IMAGEPATH and NAME attributes from the
map file, followed by user input ("mapserv->Id" corresponds to the "id"
CGI input variable).  When this code is reached, a file path based in
IMAGEPATH is built and msSaveQuery() is called.  The line at
mapquery.c:89 is soon reached in msSaveQuery(), which attempts to open
the file for writing in binary mode.  For example, if the IMAGEPATH is
set to "/var/images/", the NAME is set to "MYMAP", and the "id" parameter
is passed "area1", then the path "/var/images/MYMAParea1.qy" is opened
for writing.

    Unfortunately, no relative path checking is done.  An attacker can
set the "id" parameter to "/../../../tmp/oops", which results in a path
string of "/var/images/MYMAP/../../../tmp/oops.qy"; this causes the
program to reference "/tmp/oops.qy".  The following URL does this
(assuming that the IMAGEPATH and NAME parameters in {mapfile} have been
set accordingly):

    <http://site/cgi-bin/mapserv?map={mapfile}&mode=query&
     queryfile={queryfile}&savequery=1&id=/../../../tmp/oops>

    Note that under the Linux platform, the fopen(3) function fails if
there does not exist a readable NAME ("MYMAP") sub-directory inside
IMAGEPATH ("/var/images/").  An attacker would need access to create
directories in the IMAGEPATH in order to take advantage of this
weakness.  However, Windows programs compiled under Cygwin [4] do not
have this requirement; the fopen(3) call succeeds when no NAME ("MYMAP")
sub-directory exists.



D.  File Data Leakage (CVE-2009-0842)
    Severity: Medium

    The "mapserv" CGI program can be made to leak sensitive information
in files if an attacker has access to create symlinks anywhere in the file
system.  For example, consider the following sensitive file:

    root@thegibson:~# echo "passw0rd" > /etc/sekrut
    root@thegibson:~# chown www-data:www-data /etc/sekrut
    root@thegibson:~# chmod 0400 /etc/sekrut

Notice that the attacker does not have access to this file:

    attacker@thegibson:~$ cat /etc/sekrut 
    cat: /etc/sekrut: Permission denied

The attacker can recover the contents of this file by creating a symlink
to it in /tmp:

    attacker@thegibson:~$ ln -s /etc/sekrut /tmp/sekrut.map

The attacker then accesses <http://site/cgi-bin/mapserv?
map=/tmp/sekrut.map> and receives the following error message:

    msLoadMap(): Unknown identifier. Parsing error
    near (passw0rd):(line 1) 


    Furthermore, any *.map file in the file system can be parsed in this
manner without creating a symlink because MapServer (by default) only
checks that the file name ends in ".map" before routing the request to
the fopen(3) system call with mode "r" (see mapfile.c:4640).



E. File Existence Leakage (CVE-2009-0843)
   Severity: Low

    The "mapserv" CGI program can be made to divulge the existence of
files on the file system by examining the difference between error
messages returned by the program.  For example, if one goes to the
following URL against a UNIX system:

    <http://site/cgi-bin/mapserv?map=/tmp/test.map&mode=query&
     queryfile=/etc/passwd>

... then the following error message is returned:

    msLoadQuery(): General error message. Invalid layer index loaded from
    query file.

However, if a non-existent file is referenced in the "queryfile" argument
instead, such as:

    <http://site/cgi-bin/mapserv?map=/tmp/test.map&mode=query&
     queryfile=/does/not/exist>

... then this results in a different error message--one that explicitly
states that the file could not be accessed:

    msLoadQuery(): Unable to access file. (/does/not/exist)

Thus, an attacker can observe the resulting error messages to deduce if a
file exists or not.  This knowledge can be used to launch more
sophisticated attacks against the server.

    Note that the "map" parameter must be set to a valid map file already
on the server (though for a functional installation, this can be easily
determined by examining legitimate server requests during intended usage).



F. Additional Stack-based Buffer Overflows

    The project maintainer, Steve Lime, conducted his own audit after
issues A - E (above) were reported to him.  His audit identified four (4)
additional stack-based buffer overflows in maptemplate.c on lines 3851,
3867, 3883, and 3898.

    Finding the inputs required to trigger these overflows is left as
an exercise to the reader.



IV. Solution

    All discovered vulnerabilities were fixed in MapServer v5.2.2:
<http://download.osgeo.org/mapserver/mapserver-5.2.2.tar.gz>.  All users
are urged to upgrade to this version as soon as possible.

    Because of the changes necessary to rectify the security issues
discovered herein, users may need to alter their configuration in order
to upgrade successfully.  The vendor has made note of these alterations at
<http://lists.osgeo.org/pipermail/mapserver-users/2009-March/060600.html>.

    The vendor has also released version 4.10.4 to address the same
problems in the previous v4.x branch:
<http://download.osgeo.org/mapserver/mapserver-4.10.4.tar.gz>



V. Vendor Timeline

    Steve Lime, the project maintainer of MapServer, was highly
responsive and eager to resolve the issues as quickly as possible.

    March 10th, 2009:  Vendor (Open Source Geospatial Foundation)
                       contacted via <http://www.osgeo.org/feedback>.

    March 11th - 18th, 2009:  Private discussions held with Steve Lime
                              and evaluated proposed fixes.

    March 18th - 26th, 2009:  Vendor conducted internal code audit to
                              identify and fix additional
                              vulnerabilities.

    March 26th, 2009:  Vendor released v5.2.2 and v4.10.4 to solve all
                       discovered vulnerabilities.



VI. References

[1] "Welcome to MapServer - MapServer 5.2.1 documentation",
    <http://www.mapserver.org/>, Retrieved March 5, 2009.

[2] "MapServer - Wikipedia, the free encyclopedia",
    <http://en.wikipedia.org/wiki/MapServer>, Retrieved March 5, 2009.

[3] See "OSGeo.org | Your Open Source Compass" <http://www.osgeo.org/>,
    Retrieved March 10th, 2009.

[4] See "Cygwin Information and Installation", <http://www.cygwin.com/>,
    Retrieved March 5, 2009.



----

Key: <http://www.positronsecurity.com/keys/positron_security_2009.key.asc>
Fingerprint: F567 5BEF 3450 A521 C00D  2690 D7BD 2A5C 9644 9804

Copyright 2009, Positron Security LLC.  All rights reserved.