In this article, I'll discuss some common web-application security flaws and then demonstrate how to detect them. In the process of auditing web applications for security flaws, I will also present a PHP script that will act as a web proxy server, allowing us to intercept and alter HTTP requests between the web browser and the target web server. As we will see, this PHP script will aid us tremendously in testing for security flaws.
Let's start by looking at some common web-application security flaws that can be tested by using a custom web proxy server. The most common security flaw revolves around the sanitization of input, or "input validation."
Consider a login.php script that authenticates access to a web portal. Suppose this script accepts two parameters, username and password, via the HTTP POST method. Embedded in the source code of this script may be an actual authentication check, as follows:
$sqlquery="SELECT * FROM USERS WHERE username='$username'
AND password='$password'";
if(function_to_perform_query($sqlquery))
{
//authenticated!
}
else
{
//wrong username or password. error!
}
Looks pretty simple, doesn't it? Suppose a user were to input admin in the username field of the login form, and the following in the password field:
a' or 'a'='a
Now, the $sqlquery variable will hold the following value:
SELECT * FROM USERS WHERE username='admin' AND password='a' or 'a'='a'
When the above query executes, the user will be allowed to login as admin. The reason for this is pretty obvious. The SQL query will always return true because of the additional or 'a'='a' clause, which is of course always true: a will always be equal to a.
This act of injecting SQL queries into web apps that do not perform proper sanitization of HTTP parameters is known as "SQL injection."
Most programming languages support a function call that allows for the execution of external programs. Many poorly designed web applications sometimes fail to perform input validation on parameters that are passed into calls, such as system(). For example:
system("/bin/echo $i");
If $i were a parameter accepted from the user, then a malicious user may enter the following value:
`cat /etc/passwd`
for the value of $i. This would cause the preceding system call to execute the following:
/bin/echo `cat /etc/passwd`
Thus displaying the /etc/passwd file instead of a static value of $i.
Sanitizing user input can help to prevent input validation vulnerabilities. Web app developers should watch out for the following characters:
` . ; \ / @ & | % ~ < > " $ ( ) { } [ ] * ! '
In addition, to have PHP escape your GET and POST parameter input strings automatically, edit your php.ini file to set the value of magic_quotes_gpc to On. This has further implications, so please review the PHP manual appropriately.
Since HTTP is not a stateful protocol, the web application must provide any session management it needs. Many sessions use client-side cookies to maintain state. A client-side cookie is a piece of information requested by the web application to be stored on the end user's hard disk. Since these cookies are stored on user's disks, users or other programs can alter them, so they're untrustworthy. Poorly designed web applications often trust client-side cookies, allowing potential compromises of the web application's authentication mechanism. Consider a cookie with the following data:
lang=en-us; user=joe; time=10:10 EST;
A malicious user may attempt to alter this cookie and place
admin or root in the user field and have access to a
higher privileged account.
A web application may also maintain state by embedding session information in the URL:
http://www.blahwebserver.com/authenticated.cgi?user=john&sessionid=12345678
Often, web apps provide session ids sequentially. This would allow the user
john to brute-force session values of lower than 12345678 to
hijack sessions of users that have logged on before him.
These are static elements contained in web forms. These values are usually not displayed by the client, but are merely used to pass information back and forth between different web forms via POST requests. Here is an example of an HTML code that defines a hidden element:
<INPUT NAME="shippingcharges" TYPE=HIDDEN VALUE="5.25">
The problem in this example is that a malicious user may alter the HTML and
submit any value he or she wishes. Often, web applications are designed to
trust client-side input fully. So, in our example, a malicious user may use a
web proxy server (such as the one we will see shortly) to alter the value of
shippingcharges that is submitted to the purchase form. Imagine
what would happen if a user were to submit a value such as -100.
Assuming that the web application blindly accepts this value, the user might
end up with a credit on his credit card!
NOTE: The above is only a list of some web-application security flaws that can be tested using a custom proxy server. It should by no means be considered an exhaustive list of all possible web-application security vulnerabilities.
As we have seen, web applications should properly validate user input. In order to test a web application that relies on GET requests to accept data, all you have to do is alter parameter values by changing the values in the URL. This is because, in the case of GET requests, the input parameter values are embedded in the URL itself:
http://www.blahwebserver.com/script.cgi?login=admin&display=true
However, in the case of POST requests, things get a tad bit complicated. Unlike a GET request, the data submitted by a POST request is not displayed in the URL. Here is an example of a POST request sent by the web browser to the web server:
POST /script.cgi HTTP/1.0\r\n
Host: www.blahwebserver.com\r\n
Content-Length: 24\r\n
\r\n
login=admin&display=true
Of-course, you may submit a POST request like the one above by using a telnet client to connect to the web server and typing the request manually, but this
is tedious. To make life easier, we will write a web proxy that will help us
alter POST requests as they are submitted by the web browser. See proxy.php for its source code. In addition to intercepting POST request data, you may want to intercept and alter cookie values present in
the HTTP header. Therefore, if needed, just remove this if
condition in proxy.php and you will be able to alter every request
sent by the browser:
if($ispost==1)
You will need the PHP command-line interpreter to run
proxy.php. Run the script like so:
[bash]$ php -q proxy.php
NOTE: If you get an error about undefined socket functions, your PHP
installation probably does not support PHP's latest socket API. You will need to
recompile your PHP installation by running ./configure with the
following flag:
--enable-sockets
Now, configure your web browser's proxy settings to point to IP address
127.0.0.1 and port 8000. Once you do this, you should be able to browse the web
as usual. Whenever your browser submits a POST request, the
proxy.php script will detect it, allowing you to change the POST
request being submitted in your favorite editor.
Let's explore how proxy.php works. In order to perform as a
server, it binds to a port using the socket_bind and
socket_listen calls within the PHP API. Once this is done, the
script continuously loops for incoming connections, answering each incoming
connection with the socket_accept call, which blocks until a client
connects. Note that our implementation does not provide us with a
multi-threaded proxy server. For the purposes within our scope of testing, it
should suffice.
When a web browser needs to request a resource via a POST request, it will connect to our proxy server and submit a request, such as:
POST http://www.blahwebserver.com/login.php HTTP/1.1\r\n
Host: www.blahwebserver.com\r\n
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O;
en-US; rv:1.5) Gecko/20031007\r\n
Accept: text/xml,application/xml,application/xhtml+xml,text
/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif
;q=0.2,*/*;q=0.1\r\n
Accept-Language: en-us,en;q=0.5\r\n
Accept-Encoding: gzip,deflate\r\n
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n
Keep-Alive: 300\r\n
Proxy-Connection: keep-alive\r\n
Referer: http://www.blahwebserver.com/login.php\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 25\r\n
\r\n
login=admin&pass=p455w0rd
As soon as it receives such a request, proxy.php will perform
the following steps:
Verify if it is a GET or POST request. A GET request ends with the
\r\n\r\n characters. However, if it is a POST request, as in the
example above, the Content-Length field will specify the string
length of the parameters being passed. proxy.php detects this and
will therefore expect 25 characters (login=admin&pass=p455w0rd) to follow after the HTTP header
that ends after the sequence \r\n\r\n.
The request is stored in a temporary file in the /tmp
directory. If the request is of type POST, an editor specified by the
$editor variable is spawned. This allows the user to edit the POST
request on the fly. Once the user saves this file by exiting out of the editor -- and assuming the user has altered the request to perform SQL Injection
by sending login=admin&pass=a' or 'a'='a as the POST data -- the request will be changed to:
POST /login.php HTTP/1.0\r\n
Host: www.blahwebserver.com\r\n
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O;
en-US; rv:1.5) Gecko/20031007\r\n
Accept: text/xml,application/xml,application/xhtml+xml,text
/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif
;q=0.2,*/*;q=0.1\r\n
Accept-Language: en-us,en;q=0.5\r\n
Accept-Encoding: gzip,deflate\r\n
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n
Referer: http://www.blahwebserver.com/login.php\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 29\r\n
\r\n
login=admin&pass=a' or 'a'='a
Note that the proxy changes HTTP/1.1 to HTTP/1.0
since we don't want to be bothered with keep-alive requests that
are a part of the HTTP/1.1 specification. It removes the
Proxy-Connection line as well. Also notice that the proxy
recalculates the value of Content-Length before sending it out to the
web server.
When the request is sent to the web server, proxy.php waits
for it to respond with the results and then immediately writes the response to
the socket connection between the web browser and the proxy. After this is
done, the socket connection is closed, and the while loop takes the proxy back
to listening for more connections from the web browser.
There we are. Our own web proxy server that helps us modify POST requests on-the-fly!
The proxy.php script doesn't handle HTTPS but it can be added by
supporting HTTP's CONNECT requests. Perhaps I will alter
proxy.php and write about it at a later time. In the meantime, those
who don't want to be bothered with writing their own proxy server may want to
take a look at SPIKE Proxy and Paros Proxy.
Nitesh Dhanjani is a well known security researcher, author, and speaker. Dhanjani has been invited to talk at various information security events such as the Black Hat Briefings, RSA, Hack in the Box, Microsoft Blue Hat, and OSCON.
Return to the PHP DevCenter.
Copyright © 2009 O'Reilly Media, Inc.