Securing Websites With ASP

by guinsu

Many readers of this magazine are probably people like myself: web developers and programmers who write web applications and are concerned about the security of those applications at the code level.

What I will describe in this article are some techniques I have used recently that can help make sites more secure and keep information from being seen by the wrong people.  This primarily focuses on database driven sites that are popular at e-commerce or corporate locations.  Most of my experience has been with Microsoft IIS using Active Server Pages/VBScript and SQL.  However this is relevant to any server environment that uses SQL and supports session objects (more on that later).

Make Sure Only Valid Users Can Get In

1.)  Use SSL.  This is probably the key item in not only making a site secure but keeping your boss/clients happy.  When you tell someone that their site has SSL, they immediately assume it is secure and everything is great.  Obviously SSL is not enough.  If you slap SSL down on a site that anyone can get to - who cares - they can still look at whatever they want.  However if you put a simple login form as the default document in an SSL secured directory and also make sure all information transfers are secured by SSL, you have eliminated most, if not all, of the dangers of someone eavesdropping on the transfers in any way.

2.)  Use the session object to store authentication information.  The session object is a global object that exists in ASP.  It is also used in other environments, such as Java Servlets/JSP and I'm sure Perl and PHP have an equivalent.  The session is a global information object given to each user on the site.  Every user of your site gets their own unique session object that stays with them for their entire visit to your site.

How is this implemented?  With cookies.  When a user first connects to your site, the server sends a cookie with a long alphanumeric string that is supposedly guaranteed to be unique for each user of your site.  If the user does not have cookies enabled, sessions will not work.  Sessions are not passed around from page to page - all session information and the mapping of session IDs to the session data is done on the server.

Any sensitive data you put in the session stays on the server.  It is not sent in the cookie to the browser.  One problem besides cookies being disabled is that sessions are not shared across server clusters.  So if you have a high volume site that can dynamically switch users around amongst two or more servers, you cannot use the session object.  The information could potentially be lost if the router sends a user to another server.  Also, the session will time out if the user is idle for a certain amount of time (usually 20 minutes), so information in the session will not be retained for any long length of time.  It also goes away when the web server is stopped.

The way you put information in a session object is simple:

Session("User_ID")= 12345

You can create items in the session on the fly without declaring them and pull them out just as easily:

Temp_str=Session("First_Name")

One thing I have seen mentioned often is not to overload the session object with too much information in ASP.  Apparently this is very inefficient for the server and drags down performance.  All documentation I have seen for Java Servlets, however, actively encourages the use of the session object.  So this could just be an inefficiency of IIS.

Now that I have covered the groundwork of the session, here is how it can be put to use.  A user submits a form on a login page with a username and password.  Then a verification page compares those values to the values stored in the database.  If a user is determined to be a valid user we have a line like this:

Session("Authenticated")="TRUE"

Next we make an ASP file called check_logged_in.asp (or something like that) with contents like this:

Sub check_logged_in()
If
  Session("Authenticated") <> "TRUE"
then
  Response.redirect("login.htm")
End If
End Sub

Include this file (with <!- #include file="check_logged_in.asp" ->) on every page.

Then at the top of the page, before any other content or headers, call check_logged_in().  This way even if someone knows the URL of a page inside your site, they cannot see it.  They will be bounced right out to the login page.  Some issues with this include the fact that every page must now be an ASP page.

For a database intensive site this is no problem - nearly all of your content will be dynamic.  However if you are serving up mostly static pages but still need people to log in, this could hurt your performance.  Also, if you use Visual InterDev 6 with its Design Time Controls you must be careful that your check_logged_in() call comes before blocks of code that VI puts in, specifically the VI scripting object model code.  What happens otherwise is that the VI code starts writing headers to the browser and when you try to redirect, you'll get an error.

Making Sure Valid Users Can See Only Their Information

Once people are logged in, they are assumed to be safe and everything is O.K., right?  Well, obviously you didn't read the title of this section, so go back and do that now.

O.K., now that we are all caught up... once people are in your site there is no reason to assume they will not poke around and try to get into anything they can.  After all, you might run a site (such as Hotmail or similar) that anyone can sign up for; you really have no idea who is using your site.  Or corporate users might try to a get into their competitors' data.

There are a few things we can do to stop this:

1.)  Validate all forms on the server.  Now JavaScript is a great way to validate forms and is much less of a hassle than trying to deal with this on the server.  The user gets instant feedback and your error checking code was cake to write.  However, nothing stops a user from finding the URL of your CGI or your ASP page that accepts the form and just passing all the data in the URL (if it was a GET request form).  You could switch all of your forms to post, which would defeat a lot of people.  But what if users use the back button a lot?  They would get hassled by all sorts of expired page messages.  Or what if you need to actually load the results of the form in another frame, using JavaScript to set the href of that frame like this:

parent.frame.otherwindow.location.href="view_data?id=5"  (Or similar, I can't remember the exact syntax)

So in the interests of making the site easy to use and flexible, you'll probably need to use GET requests sometimes.  Plus someone could write their own software to send whatever they wanted through HTTP POST.

On the server you'll need a few checks to make sure everything is O.K.  Here are a few:

a.)  Check the referring page - if the information didn't come from the right page, reject it and give an error.  In ASP the code to get the referrer is:

Request.ServerVariables("HTTP_REFERER")

If someone is really determined, a program could easily fake this.  However as far as I know, browsers never lie about referrers.  This also will not work if your pages are linked by many other pages - the list of possible referrers to check could get out of hand.

b.)  Make sure every variable that you expect is there.  If anything is missing it could be a problem.  At the least it will probably cause an ASP error, which looks ugly.  Look out for these and give your own error page when this happens.

c.)  Check the types and data in all variables.  Like I mentioned before, don't rely on JavaScript.  JavaScript is there more as a convenience to the user so they do not have to reload the page and wait in order to find an error.  You still need to have a second check just in case.

2.)  Make your SQL statements secure.  If you are accessing a database, 99 percent of the time you will use SQL to do this.  One thing a user can do is pass data through the parameters to a page that was the correct type and hence would pass the tests in the last section.  But it could be incorrect data.  For instance, you run a web-based mail site.  Bob goes to view his mail and goes to a page with this URL:

http://bogusmailserver.com/view_mail?user_id=647

So he decides to try other ID numbers in the URL and presto, he gets to read someone else's mail.

This is because the SQL statement just took the parameter and grabbed all the mail from the database that belonged to that ID number.  In this case the user_id might have been better stored in the session, and since it is just one int for each user, it would not hurt performance that much.

But here is another example.  Say you have a database of salesmen and their clients and the URL looks like this:

http://example.com/view_customer_data?sales_id=123&cust_id=4324

And say all your SQL did was lookup that customer ID and return the data, like this:

SELECT * FROM CUST_DATA WHERE customer_id=@cust_id

Therefore you are vulnerable to someone typing in any other customer ID in the URL.  A better way would be to correlate the salesman ID and the customer ID:

SELECT * FROM CUST_DATA WHERE customer_id=@cust_id AND salesman_id=@sales_id

If the information that related salesmen to customers is in another table, then you should use a JOIN to combine the two.  Now you may say that a user could easily just play with salesman ID's and customer ID's until he found one that worked, so why not put the salesman ID in the session?  Well, what if you aren't logged in as the salesman but as his manager, and you've got 100 salesmen under you.

Putting them all in the session is a big headache on many levels.  In that case you would need a way to match up managers with their salesmen, and then with their customers.  This would take the form of another table and then your SQL statement would need to include the manager information joined with the other two items.

The basic point of this explanation is don't rely on parameters passed solely by GET request and HTTP POST to do SQL queries, you should always correlate them with data held in the session object.  Otherwise you leave yourself open to people looking at others' data, whether it's email, sales info, or your private medical records.

One other note about SQL queries - there was an article "NT Web Technology Vulnerabilities" in Phrack Issue #54 that exposed some potentially serious issues with SQL server 6.5 and users being able to pass their own SQL queries in parameters.  Find the article and make sure your app is not vulnerable to this.

In closing, I hope this has been an informative and helpful article for the programmers out there.

I know I blew over some of the SQL stuff, but it is too big of a topic to go into here.  For more information, check out this page (or the 10,000 mirrors of it on the web): w3.one.net/~jhoffman/sqltut.htm

Also, I am sure I missed a few holes that I am just not aware of.  So do not take this as the end all and be all of securing sites in code.

Return to $2600 Index