Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier
fr to en Georges Tarbouriech
en to en Lorne Bailey
Christophe Blaess is an independent aeronautics engineer. He is a Linux fan and does much of his work on this system. He coordinates the translation of the man pages as published by the Linux Documentation Project.
Christophe Grenier is a 5th year student at the ESIEA, where he works as a sysadmin too. He has a passion for computer security.
Frédéric Raynal has been using Linux for many years because it doesn't pollute, it doesn't use hormones, MSG or animal bone meal... only sweat and tricks.
When a client asks for a HTML file, the server sends the
requested page (or an error message). The browser interprets the
HTML code to format and display the file. For instance, typing the
http://www.linuxdoc.org/HOWTO/ HOWTO-INDEX/howtos.html
URL (Uniform Request Locator), the client connects to the
www.linuxdoc.org
server and asks for the
/HOWTO/HOWTO-INDEX/howtos.html
page (called URI -
Uniform Resource Identifiers), using the HTTP
protocol. If the page exists, the server sends the requested file.
With this static model, if the file is present on the
server, it is sent "as is" to the client, otherwise an error
message is sent (the well known 404 - Not Found).
Unfortunately, this doesn't allow interactivity with the user, making features such as e-business, e-reservation for holidays or e-whatever impossible.
Fortunately, there are solutions to dynamically generate HTML pages. CGI (Common Gateway Interface) scripts are one of them. In this case, the URI to access web pages is built in a slightly different way :
http://<server><pathToScript>[?[param_1=val_1][...] [¶m_n=val_n]]
QUERY_STRING
environment variable. In this context, a CGI script is nothing but
an executable file. It uses the stdin
(standard input)
or the environment variable QUERY_STRING
to get the
arguments passed to it. After executing the code, the result is
displayed on the stdout
(standard output) and then,
redirected to the web client. Almost every programming language can
be used to write a CGI script (compiled C program, Perl,
shell-scripts...). For example, let's search what the HOWTOs from
www.linuxdoc.org
know about ssh :
http://www.linuxdoc.org/cgi-bin/ldpsrch.cgi?
svr=http%3A%2F%2Fwww.linuxdoc.org&srch=ssh&db=1& scope=0&rpt=20
www.linuxdoc.org
;/cgi-bin/ldpsrch.cgi
;?
is the beginning of a long list of arguments
:
srv=http%3A%2F%2Fwww.linuxdoc.org
is the
server where the request comes from;srch=ssh
contains the request itself;db=1
means the request only concerns
HOWTOs;scope=0
means the request concerns the
document's content and not only its title;rpt=20
limits to 20 the number of displayed
answers.Often, arguments names and values are explicit enough to understand their meaning. Furthermore, the content of the page displaying the answers is rather significant.
Now you know that the bright side of CGI scripts is the user's ability to pass in arguments... but the dark side is that a badly written script opens a security hole.
You probably noticed the strange characters used by your
preferred browser or present within the previous request. Those
characters are encoded with the ISO 8859-1 charset (have
a look at >man iso_8859_1
). The table 1 provides with the meaning of some of
these codes. Let's mention some IIS4.0 and IIS5.0 servers have a
very dangerous vulnerability called unicode bug based on
the extended unicode representation of "/" and "\". .
SSI Server Side
Include
"Server Side Include
is a
part of a web server's functionality. It allows integrating instructions into web
pages, either to include a file "as is", or to execute a command
(shell or CGI script).
In the Apache configuration file httpd.conf
, the
"AddHandler server-parsed .shtml
" instruction
activates this mechanism. Often, to avoid the distinction between
.html
and .shtml
, one can add the
.html
extension. Of course, this slows down the
server... This can be controlled at directories level with the
instructions :
Options Includes
activates every SSI ;OptionsIncludesNoExec
prohibits exec
cmd
and exec cgi
.In the attached guestbook.cgi
script, the text provided by
the user is included into an HTML file, without '<' and ' >'
character conversion into < and > HTML code.
A curious person could submit one of the following
instructions :
<!--#printenv -->
(mind the space after
printenv
)<!--#exec cmd="cat /etc/passwd"-->
guestbook.cgi?email=pappy&texte=%3c%21--%23printenv%20--%3e
DOCUMENT_ROOT=/home/web/sites/www8080 HTTP_ACCEPT=image/gif, image/jpeg, image/pjpeg, image/png, */* HTTP_ACCEPT_CHARSET=iso-8859-1,*,utf-8 HTTP_ACCEPT_ENCODING=gzip HTTP_ACCEPT_LANGUAGE=en, fr HTTP_CONNECTION=Keep-Alive HTTP_HOST=www.esiea.fr:8080 HTTP_PRAGMA=no-cache HTTP_REFERER=http://www.esiea.fr:8080/~grenier/cgi/guestbook.cgi? email=&texte=%3C%21--%23include+file%3D%22guestbook.cgi%22--%3E HTTP_USER_AGENT=Mozilla/4.76 [fr] (X11; U; Linux 2.2.16 i686) PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin REMOTE_ADDR=194.57.201.103 REMOTE_HOST=nef.esiea.fr REMOTE_PORT=3672 SCRIPT_FILENAME=/mnt/c/nef/grenier/public_html/cgi/guestbook.html SERVER_ADDR=194.57.201.103 [email protected] SERVER_NAME=www.esiea.fr SERVER_PORT=8080 SERVER_SIGNATURE=<ADDRESS>Apache/1.3.14 Server www.esiea.fr Port 8080</ADDRESS> SERVER_SOFTWARE=Apache/1.3.14 (Unix) (Red-Hat/Linux) PHP/3.0.18 GATEWAY_INTERFACE=CGI/1.1 SERVER_PROTOCOL=HTTP/1.0 REQUEST_METHOD=GET QUERY_STRING= REQUEST_URI=/~grenier/cgi/guestbook.html SCRIPT_NAME=/~grenier/cgi/guestbook.html DATE_LOCAL=Tuesday, 27-Feb-2001 15:33:56 CET DATE_GMT=Tuesday, 27-Feb-2001 14:33:56 GMT LAST_MODIFIED=Tuesday, 27-Feb-2001 15:28:05 CET DOCUMENT_URI=/~grenier/cgi/guestbook.shtml DOCUMENT_PATH_INFO= USER_NAME=grenier DOCUMENT_NAME=guestbook.shtml
The exec
instruction, provides you almost with a
shell equivalent :
guestbook.cgi?email=ppy&texte=%3c%21--%23exec%20cmd="cat%20/etc/passwd"%20--%3e
Don't try "<!--#include
file="/etc/passwd"-->
", the path is relative to the
directory where you can find the HTML file and can't contain
"..
". The Apache error_log
file, then
contains a message indicating an access attempt to a prohibited
file. The user can see the message [an error occurred while
processing this directive]
in the HTML page.
SSI are not often needed so it is better to deactivate it on the server. However the cause of the problem is the combination of the broken guestbook application and the SSI.
In this section, we present security holes related to CGI scripts written with Perl. To keep things clear, we don't provide the examples full code but only the parts required to understand where the problem is.
Each of our scripts is built according the following template :
#!/usr/bin/perl -wT BEGIN { $ENV{PATH} = '/usr/bin:/bin' } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer =:-) print "Content-type: text/html\n\n"; print "<HTML>\n<HEAD>"; print "<TITLE>Remote Command</TITLE></HEAD>\n"; &ReadParse(\%input); # now use $input e.g like this: # print "<p>$input{filename}</p>\n"; # #################################### # # Start of problem description # # #################################### # # ################################## # # End of problem description # # ################################## # form: print "<form action=\"$ENV{'SCRIPT_NAME'}\">\n"; print "<input type=texte name=filename>\n </form>\n"; print "</BODY>\n"; print "</HTML>\n"; exit(0); # first arg must be a reference to a hash. # The hash will be filled with data. sub ReadParse($) { my $in=shift; my ($i, $key, $val); my $in_first; my @in_second; # Read in text if ($ENV{'REQUEST_METHOD'} eq "GET") { $in_first = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'}); }else{ die "ERROR: unknown request method\n"; } @in_second = split(/&/,$in_first); foreach $i (0 .. $#in_second) { # Convert plus's to spaces $in_second[$i] =~ s/\+/ /g; # Split into key and value. ($key, $val) = split(/=/,$in_second[$i],2); # Convert %XX from hex numbers to alphanumeric $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Associate key and value # \0 is the multiple separator $$in{$key} .= "\0" if (defined($$in{$key})); $$in{$key} .= $val; } return length($#in_second); }
More on the arguments passed to Perl (-wT
) later.
We begin cleaning up the $ENV
and $PATH
environment variables and we send the HTML header (this is
something part of the html protocl between browser and server. You
can't see it in the webpage displayed on the browser side). The
ReadParse()
function reads the arguments passed to the
script. This can be done more easily with modules, but this way you
can see the whole code. Next, we present the examples. Last, we
finish with the HTML file.
Perl considers every character in the same way, what differs from C functions, for instance. For Perl, the null character to end a string is a character like any other one. So what ?
Let's add the following code to our script to create
showhtml.cgi
:
# showhtml.cgi my $filename= $input{filename}.".html"; print "<BODY>File : $filename<BR>"; if (-e $filename) { open(FILE,"$filename") || goto form; print <FILE>; }
The ReadParse()
function gets the only argument :
the name of the file to display. To prevent some "rude guest"
from reading more than the HTML files, we add the
".html
" extension at the end of the filename. But,
remember, the null byte is a character like any other one...
Thus, if our request is
showhtml.cgi?filename=%2Fetc%2Fpasswd%00
the file is
called my $filename = "/etc/passwd\0.html"
and ours
astounded eyes gaze at something not being HTML.
What happens ? The strace
command shows how Perl
opens a file:
/tmp >>cat >open.pl << EOF > #!/usr/bin/perl > open(FILE, "/etc/passwd\0.html"); > EOF /tmp >>chmod 0700 open.pl /tmp >>strace ./open.pl 2>&1 | grep open execve("./open.pl", ["./open.pl"], [/* 24 vars */]) = 0 ... open("./open.pl", O_RDONLY) = 3 read(3, "#!/usr/bin/perl\n\nopen(FILE, \"/et"..., 4096) = 51 open("/etc/passwd", O_RDONLY) = 3
The last open()
presented by strace
corresponds to the system call, written in C. We can see, the
.html
extension disappeared, and this allowd us to
open /etc/passwd.
This problem is solved with a single regular expression which removes all null bytes:
s/\0//g;
Here is a script without any protection. It displays a given file from the directory tree /home/httpd/ :
#pipe1.cgi my $filename= "/home/httpd/".$input{filename}; print "<BODY>File : $filename<BR>"; open(FILE,"$filename") || goto form; print <FILE>;
Don't laugh at this example ! I have seen such scripts.
The first exploit is obvious :
pipe1.cgi?filename=..%2F..%2F..%2Fetc%2FpasswdOne need only go up the tree to access any file. But there is another much more interesting posibility: to execute the command of your choice. In Perl, the
open(FILE, "/bin/ls")
command opens the
"/bin/ls
" binary file... but
open(FILE, "/bin/ls |")
executes the
specified command. Adding a single pipe |
changes the
behavior of open()
. Another problem comes from the fact that the existence of the
file is not tested, which allows us to execute any command but also
to pass any arguments :
pipe1.cgi?filename=..%2F..%2F..%2Fbin%2Fcat%20%2fetc%2fpasswd%20|
displays the password file content.
Testing the existence of the file to open gives less freedom :
#pipe2.cgi my $filename= "/home/httpd/".$input{filename}; print "<BODY>File : $filename<BR>"; if (-e $filename) { open(FILE,"$filename") || goto form; print <FILE> } else { print "-e failed: no file\n"; }The previous example doesn't work anymore. The "
-e
"
test fails since it can't find the "../../../bin/cat
/etc/passwd |
" file. Let's try now the /bin/ls
command. The behavior
will be the same as before. That is, if we try, for instance, to
list the /etc
directory content, "-e
"
tests the existence of the "../../../bin/ls /etc |
file, but it doesn't exist either. As soon as we don't provide the
name of a "ghost" file, we won't get anything interesting :(
However, there is still a "way out", even if the result is not
so good. The /bin/ls
file exists (well, in most of the
systems), but if open()
is called with this filename,
the command won't be executed but the binary will be displayed. We
must then find a way to put a pipe '|
' at the end
of the name, without it to be used during the check done by
"-e
". We already know the solution : the null byte. If
we send "../../../bin/ls\0|
" as name, the existence
check succeeds since it only considers
"../../../bin/ls
", but open()
can see the
pipe and then executes the command. Thus, the URI providing the
current directory content is :
pipe2.cgi?filename=../../../bin/ls%00|
The finger.cgi script executes the finger
instruction on our machine :
#finger.cgi print "<BODY>"; $login = $input{'login'}; $login =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g; print "Login $login<BR>\n"; print "Finger<BR>\n"; $CMD= "/usr/bin/finger $login|"; open(FILE,"$CMD") || goto form; print <FILE>
This script, (at least) takes a useful precaution : it takes care of
some strange characters to prevent them from being interpreted with
a shell by placing a '\
' in front. Thus, the semicolon
is changed to "\;
" by the regular expression. But the
list doesn't contain every important character. Among others, the
line feed '\n
' is missing.
In your preferred shell command line, you validate an
instruction typing the RETURN
or ENTER
key that sends a '\n
' character. In Perl, you can do
the same. We already saw the open()
instruction
allowed us to execute a command as soon as the line ended with
a pipe '|
'.
To simulate this behavior we to add a carriage-return and an instruction after the login sent to the finger command :
finger.cgi?login=kmaster%0Acat%20/etc/passwd
Other characters are quite interesting to execute various instructions in a row :
;
: it ends the first instruction and goes
to the next one;&&
: if the first instruction
succeeds (i.e. returns 0 in a shell), then the next one
is executed;||
: if the first instruction fails
(i.e. returns a no null value in a shell), then the next
one is executed.The previous finger.cgi
script avoides problems
with some strange characters. Thus, the URI
<finger.cgi?login=kmaster;cat%20/etc/passwd
doesn't
work when the semicolon is escaped. However, one character is not
protected : the backslash '\
'.
Let's take, for instance, a script that prevents us from going
up the tree by using the regular expression s/\.\.//g
to get rid of "..
". It doesn't matter! Shells can
manage various numbers of '/
' at once (just try cat
///etc//////passwd
to get convinced).
For example, in the above pipe2.cgi
script, the
$filename
variable is initialized from the
"/home/httpd/
" prefix. Using the previous regular
expression could seem efficient to prevent from going up through
the directories. Of course, this expression protects from
"..
", but what happens if we protect the
'.
' character ? That is, the regular expression
doesn't match if the filename is .\./.\./etc/passwd
.
Let's mention, this works very well with system()
(or
` ... `
), but open()
or "-e
"
fails.
Let's go back to the finger.cgi
script. Using the
semicolon, the
finger.cgi?login=kmaster;cat%20/etc/passwd
URI doesn't
give the expected result since the semicolon is escaped by the
regular expression. That is, the shell receives the instruction
:
/usr/bin/finger kmaster\;cat /etc/passwdThe following errors are found in the web server logs :
finger: kmaster;cat: no such user. finger: /etc/passwd: no such user.These messages are identical to those you can get when typing this line in a shell. The problem comes from the fact the protected '
;
' considers this character as belonging to the
string "kmaster;cat
" . We want to separate both instructions, the one from the script
and the one we want to use. We must then protect the
';
' : <A
HREF="finger.cgi?login=kmaster\;cat%20/etc/passwd">
finger.cgi?login=kmaster\;cat%20/etc/passwd</A>
. The
"\;
string, is then changed by the script into
"\\;
", and next, sent to the shell. This last reads
:
/usr/bin/finger kmaster\\;cat /etc/passwdThe shell splits this into two different instructions :
/usr/bin/finger kmaster\
which probably will
fail... but we don't care ;-)cat /etc/passwd
which displays the password
file.\
' must
be escaped, too. Sometimes, the parameter is "protected" using quotes. We have
changed the previous finger.cgi
script to
protect the $login
variable that way.
However, if the quotes are not escaped, it's useless. Even one added in your request will fail. This happens because the first quote sent closes the opening one from the script. Next, you write the command, and a second quote opens the last (closing) quote from the script.
The finger2.cgi script illustrates this :
#finger2.cgi print "<BODY>"; $login = $input{'login'}; $login =~ s/\0//g; $login =~ s/([<>\*\|`&\$!#\(\)\[\]\{\}:'\n])/\\$1/g; print "Login $login<BR>\n"; print "Finger<BR>\n"; #New (in)efficient super protection : $CMD= "/usr/bin/finger \"$login\"|"; open(FILE,"$CMD") || goto form; while(<FILE>) { print; }
The URI to execute the command then becomes :
finger2.cgi?login=kmaster%22%3Bcat%20%2Fetc%2Fpasswd%3B%22The shell receives the command
/usr/bin/finger "$login";cat
/etc/passwd""
and the quotes are not a problem anymore. So, it's important, if you wish to protect the parameters with quotes, to escape them as for the semicolon or the backslash already mentioned.
When programming in Perl, use the w
option or
"use warnings;
" (Perl 5.6.0 and later), it informs you
about potential problems, such as uninitialized variables or
obsolete expressions/functions.
The T
option ( taint mode) provides higher
security. This mode activates various tests. The most important
concerns a possible tainting of variables. Variables are
either clean or tainted. Data coming from outside the program is
considered as tainted as long as it hasn't been cleaned up. Such a
tainted variable is then unable to assign values to things that are
used outside the program (calls to other shell comands).
In taint mode, the command line arguments, the environment
variables, some system call results (readdir()
,
readlink()
, readdir()
, ...) and the data
coming from files, are considered suspicious and thus
tainted.
To clean up a variable, you must filter it through a regular
expression. Obviously, using .*
is useless. The goal
is to force you to take care of provided arguments. Always use a
regular expression that is as specific as possible.
Nevertheless, this mode doesn't protect from everything : the
tainting of arguments passed to system()
or
exec()
as a list variable is not checked. You must
then be very careful if one of your scripts uses these functions.
The exec "sh", '-c', $arg;
instruction
is considered as secure, whether $arg
is tainted or
not :(
It's also recommended to add "use strict;" at the beginning of
your programs. This forces you to declare variables; some people
will find that annoying but it's mandatory if you use
mod-perl
.
Thus, your Perl CGI scripts must begin with :
#!/usr/bin/perl -wT use strict; use CGI;or with Perl 5.6.0 :
#!/usr/bin/perl -T use warnings; use strict; use CGI;
open()
Many programmers open a file simply using
open(FILE,"$filename") || ...
. We already saw the
risks of such code. To reduce the risk, specify the
open mode :
open(FILE,"<$filename") || ...
for read
only;open(FILE,">$filename") || ...
for write
onlyBefore accessing a file, it's recommended to check if the file exists. This doesn't prevent the race conditions types of problems presented in the previous article, but avoids some traps such as commands with arguments.
if ( -e $filename ) { ... }
Starting from Perl 5.6, there's a new syntax for
open()
: open(FILEHANDLE,MODE,LIST)
. With
the '<' mode, the file is open for reading; with the '>'
mode, the file is truncated or created if needed, and open for
writing. This becomes interesting for modes communicating with
other processes. If the mode is '|-' or '-|', the LIST argument is
interpreted as a command and is respectively found before or after
the pipe.
Before Perl 5.6 and open()
with three arguments,
some people used the sysopen()
command.
There are two methods : either you specify the forbidden characters, or you explicitely define the allowed characters using regular expressions. The example programs should have convinced you that it's quite easy to forget to filter potentially dangerous characters, that's why the second method is recommended.
Practically, here is what to do : first, check the request only holds allowed characters. Next, escape the characters considered as dangerous among the allowed ones.
#!/usr/bin/perl -wT # filtre.pl # The $safe and $danger variables respectively define # the characters without risk and the risky ones. # Add or remove some to change the filter. # Only $input containing characters included in the # definitions are valid. use strict; my $input = shift; my $safe = '\w\d'; my $danger = '&`\'\\|"*?~<>^(){}\$\n\r\[\]'; #Note: # '/', space and tab are not part of the definitions on purpose if ($input =~ m/^[$safe$danger]+$/g) { $input =~ s/([$danger]+)/\\$1/g; } else { die "Bad input chars in $input\n"; } print "input = [$input]\n";
This script defines two character sets :
$safe
contains the ones considered as not risky
(here, only numbers and letters);$danger
contains the characters to be escaped
since they are allowed but potentially dangerous.I don't want to be controversial, but I think it's better to
write scripts in PHP rather than in Perl. More exactly, as a system
administrator, I prefer my users to write scripts in PHP language
rather than in Perl. Someone programming insecurely
in PHP will be as dangerous as Perl, so why prefer PHP ?
If you have some programming problems with PHP, you can activate the
Safe mode (safe_mode=on
) or deactivate functions
(disable_functions=...
). This mode prevents
accessing files not belonging to the user, changing
environment variables unless explicitely allowed, executing
commands, etc.
By default, the Apache banner informs us about the PHP being used.
$ telnet localhost 80 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. HEAD / HTTP/1.0 HTTP/1.1 200 OK Date: Tue, 03 Apr 2001 11:22:41 GMT Server: Apache/1.3.14 (Unix) (Red-Hat/Linux) mod_ssl/2.7.1 OpenSSL/0.9.5a PHP/4.0.4pl1 mod_perl/1.24 Connection: close Content-Type: text/html Connection closed by foreign host.Write
expose_PHP = Off
into
/etc/php.ini
to hide the information :
Server: Apache/1.3.14 (Unix) (Red-Hat/Linux) mod_ssl/2.7.1 OpenSSL/0.9.5a mod_perl/1.24
The /etc/php.ini
file (PHP4) and
/etc/httpd/php3.ini
have many parameters that can help
harden the system. For instance, the
"magic_quotes_gpc
" option adds quotes on the arguments
received by the GET
, POST
methods and via
cookies; this avoids a number of problems found in our Perl
examples.
This article is probably the most easily understood
among the articles in this series.
It shows vulnerabilities exploited
every day on the web. There are many others, often related to bad
programming (for instance, a script sending a mail, taking
the From:
field as an argument, provides a good site for
spamming). Examples are too numerous. As soon as a script is on a
web site, you can bet at least one person will try to use it the
wrong way.
This article ends the series about secure programming. We hope we helped you discover the main security holes found in too many applications, and that you will take into account the "security" parameter when designing and programming your applications. Security problems are often neglected because of the limited scope of the development (internal use, private network use, temporary model, etc.). Nevertheless, a module originally designed for only very restricted use can become the base for a much bigger application and then changes later on will be much more expensive.
URI Encoding (ISO 8859-1) | Character |
%00 | \0 (end of string) |
%0a | \n (carriage return) |
%20 | space |
%21 | ! |
%22 | " |
%23 | # |
%26 | & (ampersand) |
%2f | / |
%3b | ; |
%3c | < |
%3e | > |
man perlsec
: Perl man page about
security;#!/usr/bin/perl -w # guestbook.cgi BEGIN { $ENV{PATH} = '/usr/bin:/bin' } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer =:-) print "Content-type: text/html\n\n"; print "<HTML>\n<HEAD><TITLE>Buggy Guestbook</TITLE></HEAD>\n"; &ReadParse(\%input); my $email= $input{email}; my $texte= $input{texte}; $texte =~ s/\n/<BR>/g; print "<BODY><A HREF=\"guestbook.html\"> GuestBook </A><BR><form action=\"$ENV{'SCRIPT_NAME'}\">\n Email: <input type=texte name=email><BR>\n Texte:<BR>\n<textarea name=\"texte\" rows=15 cols=70> </textarea><BR><input type=submit value=\"Go!\"> </form>\n"; print "</BODY>\n"; print "</HTML>"; open (FILE,">>guestbook.html") || die ("Cannot write\n"); print FILE "Email: $email<BR>\n"; print FILE "Texte: $texte<BR>\n"; print FILE "<HR>\n"; close(FILE); exit(0); sub ReadParse { my $in =shift; my ($i, $key, $val); my $in_first; my @in_second; # Read in text if ($ENV{'REQUEST_METHOD'} eq "GET") { $in_first = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'}); }else{ die "ERROR: unknown request method\n"; } @in_second = split(/&/,$in_first); foreach $i (0 .. $#in_second) { # Convert plus's to spaces $in_second[$i] =~ s/\+/ /g; # Split into key and value. ($key, $val) = split(/=/,$in_second[$i],2); # Convert %XX from hex numbers to alphanumeric $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Associate key and value $$in{$key} .= "\0" if (defined($$in{$key})); $$in{$key} .= $val; } return length($#in_second); }