PHP DevCenter
oreilly.comSafari Books Online.Conferences.

advertisement


Web App Security Testing with a Custom Proxy Server

by Nitesh Dhanjani
01/22/2004

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."

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.

Sessions

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.

Hidden HTML Elements

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.

Our Own Custom Proxy Server

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!

Conclusion

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.



Valuable Online Certification Training

Online Certification for Your Career
Earn a Certificate for Professional Development from the University of Illinois Office of Continuing Education upon completion of each online certificate program.

PHP/SQL Programming Certificate — The PHP/SQL Programming Certificate series is comprised of four courses covering beginning to advanced PHP programming, beginning to advanced database programming using the SQL language, database theory, and integrated Web 2.0 programming using PHP and SQL on the Unix/Linux mySQL platform.

Enroll today!


Sponsored by: