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.