|
Introduction
While programming a Web Application, it is very common to split operations between pages. We usually are faced with a page that offers various options (a menu) and a page that processes the request, issues a query against a database, and then shows the results.
In this case, one of the most common ways to pass parameters from an ASP page to another is via the QueryString. This method is very easy to implement, and sometimes it is the only viable method to accomplish certain tasks. Unfortunately, the method comes with some risks. What if someone writes a request directly in the address bar? Could he/she be able to discover some secret information? This article will show how this could be possible and how to avoid malicious requests.
Part I - An Easy Example
First of all, let's imagine a very easy application: a menu with some options and a response page that acts consequently by interpreting the request, querying a db, and then showing the result.
Menu.asp
The menu will show 3 common choices: Customer Data, Orders and Invoices.
<%
Response.Write "List" & "<br>"
Response.Write "<a href='Response.asp?list=1&custcode=123'>My Data</a><br>"
Response.Write "<a href='Response.asp?list=2&custcode=123'>My Orders</a><br>"
Response.Write "<a href='Response.asp?list=3&custcode=123'>My Invoices</a><br>"
%>
For simplicity, all choices point to the same Response.asp page, where the result of the query will be displayed. Also note that the customer code (custcode=123 in this case) is indicated in clear text. Although this isn't a good practice, I'll deliberately leave it clear to explain the article.
Response.Asp
The response page will interrogate the database and show the result.
<%
' declare, instantiate and open connection (Conn)
' declare and instantiate recordset (Rs)
SELECT CASE Request.QueryString("list")
CASE 1
strSQL = "SELECT * FROM tableCustomers WHERE custcode=" & _
Request.QueryString("custcode")
CASE 2
strSQL = "SELECT * FROM tableOrders WHERE custcode=" & _
Request.QueryString("custcode")
CASE 3
strSQL = "SELECT * FROM tableInvoices WHERE custcode=" & _
Request.QueryString("custcode")
END SELECT
Rs.Open strSQL, Conn
Do While Not Rs.Eof
' Show Records
Rs.MoveNext
Loop
' Close Rs & Conn
%>
This method has a major disadvantage. The user can change the arguments passed by entering different values in the address bar.
For example, he/she could write an URL like this:
http://www.mysite.com/Response.asp?list=4&custcode=789
In the above example, the user changed both the Type and CustCode parameter. In the first case, the Response.Asp page will return an error - an annoying issue, but not so dangerous. In the second case -- which is the worst one -- the user could read values he must not have access to.
Furthermore, the Response.asp page is stored in the browser's History, so anyone can read and reproduce it.
How can we avoid this? The idea is to pass through a middle page that does some conversions. The middle page will convert QueryString parameters into Session values. Of course, we will have to make some changes to the code above. After the modification, the code will be:
Menu.Asp
The menu will be modified as follows:
<%
Response.Write "List" & "<br>"
Response.Write "<a href='Prepare.asp?list=1&custcode=123'>My Data</a><br>"
Response.Write "<a href='Prepare.asp?list=2&custcode=123'>My Orders</a><br>"
Response.Write "<a href='Prepare.asp?list=3&custcode=123'>My Invoices</a><br>"
%>
As you can see, the changes (in bold) were made with a very little effort, so we do not have to redesign the whole application.
Prepare.asp
This is the middle page that gets the values in the previous page's form, transforms them into Session values (hidden to the user view), and then redirects the process to the query page.
Session("list")=Request.QueryString("list")
Session("custcode")=Request.QueryString("custcode")
Response.Redirect "Response.asp"
Response.asp
The response page will be modified as follows:
<%
' declare, instantiate and open connection (Conn)
' declare and instantiate recordset (Rs)
SELECT CASE Session("list")
CASE 1
strSQL = "SELECT * FROM tableCustomers WHERE custcode=" & _
Session("custcode")
CASE 2
strSQL = "SELECT * FROM tableOrders WHERE custcode=" & _
Session("custcode")
CASE 3
strSQL = "SELECT * FROM tableInvoices WHERE custcode=" & _
Session("custcode")
CASE ELSE
Response.Redirect("Menu.asp")
END SELECT
Rs.Open strSQL, Conn
Do While Not Rs.Eof
' Show Records
Rs.MoveNext
Loop
' Close Rs & Conn
%>
Also in this case, the changes (in bold) were made with very little effort.
This method presents many advantages:
- The address bar will only report the page name -- a cleaner way
- The user cannot modify the parameters passed to Response.asp, since it doesn't read the QueryString parameters. It only reads some Session values that are not able to be set by the user
- The browser's History doesn't store the parameterized Prepare.asp page, so it is difficult to reproduce it.
I said "difficult", not impossible. Why? First of all, anyone can read it in the browser's status bar (unless you use some Javascript to hide it). Furthermore, a skilled user can still read the HTML source of Menu.asp and locate the lines where the page recalls the Prepare.asp page, and then reproduce the call.
Even in this case, there is a solution, which will be explained in the second part.
Part II - Block Them Outside
In the first part of this article we discovered how to hide the Querystring from the address bar, and thus introducing some sort of protection against its unauthorized use. But, at the same time, we admitted that a skilled user would have no difficulties breaking this protection. Even worse, an ASP programmer could write a script that calls the page with several parameter combinations to grab the contents of your database.
So, we must find a way to avoid this.
The basic idea is to block those requests that come from outside our Web site. Does a server variable exist that reports the origin of a reqeust? Yes, it is HTTP_REFERER, which uses the following syntax: Request.ServerVariables("HTTP_REFERER").
Let's see the description of this variable (from Microsoft's site):
HTTP_REFERER: Returns a string that contains the URL of the page that referred the request to the current page using an HTML <A> tag.
Ok. This is the variable that we need. Please note another (very useful) behavior: when you request the page by typing the URL in the address bar, HTTP_REFERER is empty, even if you typed the whole exact URL!
At this point it is easy to implement a further protection scheme, which is to compare the requester's address with our Web site's address:
IF INSTR(Request("HTTP_REFERER"), "http://www.mysite.com/") <> 1 Then
Response.Redirect "Menu.asp"
End If
IF INSTR(Request("HTTP_REFERER"), "http://10.10.10.212/") <> 1 Then
Response.Redirect "Menu.asp"
End If
Which pages do we need to protect (in our example)? At the least, the Prepare.asp page should be protected, since it is the page that reads the QueryString. We do not need to protect Request.asp, since that page relies upon the contents of the Session variables, not the QueryString. Also, remember that an external requester cannot pass Session variables to our application.
Prepare.asp
Here's how to modify the page (changes are in bold).
IF INSTR(Request("HTTP_REFERER"), "http://www.mysite.com/") < 1 Then
Response.Redirect "Menu.asp"
End If
IF INSTR(Request("HTTP_REFERER"), "http://10.10.10.212/") < 1 Then
Response.Redirect "Menu.asp"
End If
Session("list")=Request.QueryString("list")
Session("custcode")=Request.QueryString("custcode")
Response.Redirect "Response.asp"
In our small example, this could be enough to block unauthorized requests. But Web applications usually are far more complex. We should think to implement some function, as part of an include file, to report in each page.
The function will be:
Function FromSite()
IF INSTR(Request("HTTP_REFERER"), "http://www.mysite.com/") = 1 Then FromSite = True: Exit Function
IF INSTR(Request("HTTP_REFERER"), "http://10.10.10.212/") = 1 Then FromSite = True: Exit Function
FromSite = False
End Function
Then we can call and use the function this way:
If Not FromSite Then
Response.Redirect "Menu.asp"
End If
You can easily add programming gadgets, including but not limited to: alert messages, access log, alert mail to the admin, and so on.
Conclusion
A Web application may be comfortable to build and implement thanks to smooth technologies such as ASP. But do not forget that they are public; they're "by nature" exposed to attacks. In this article we've learned how to block requests that aren't originated by the Web site by issuing a virtual firewall against external, not authorized, and potentially dangerous requests.
Check out a demo
About the Author
Enrico Di Cesare has more than 20 years of programming experience. After a short experience in COBOL, he worked with all flavors of Basic. Starting withVB3, he began to develop Windows-based applications. Then he embraced ASP from its earlier version, developing business Internet/Intranet solutions. He currently owns a firm based in Italy, which specializes in consulting and design and development of desktop/client-server/internet applications using VB and ASP.
You can reach him by email at mailto:edicesare@dicesare.com
|