The biggest challenge in making your web site 100- percent secure is how to maintain your application state.
The traditional desktop application "know" who is using it as soon as the user passes the authentication - whether on operating system level or inside the application itself. The application may be sure that it is the same user who is interfacing with it. A web application, however, may only assume. A standard web application is stateless. meaning You usually don’t know who is calling your page and from where unless the page has some direct authentication or you are saving your variables on the user machine through cookies.
ASP gives us much more flexibility. With ASP Application and Session objects your web application has the ability to maintain its state from page to page, so once the user enters his password on page 1 and his user ID is stored as a variable inside your Session object,
<%
Session("UserId") = 4521
%>
on page 10, you will be able to examine your UserId variable and be sure this is still the same user. Let’s assume the Session mechanism is completely reliable.
Now imagine that your application provides your users some sort of confidential market information. After authentication on pages 1 through 10 you ask the user what sort of information he requires and finally provide it on page 11. What happens if your user tries to save time by going directly to page 11 by typing this location in his browser address box? On page 11 you check his Session(“UserId”) and its still 4521! How would you know if the information submitted on page 11 is up to date? And will your page 11 work correctly if invoked this way, if any or all of parameters you expect at this page are not supplied?
Now let’s ask ourselves if it is really safe to use ASP Sessions. ASP documentation alerts you: “Session state is only maintained for browsers that support cookies.” This means that Sessions are not the safe way of maintaining your application’s state because the client browser may not support or accept cookies. To let each page know what was done before we have to transfer all the application data from one page to another.
There are methods of doing this. You may pass your parameters as a URL-encoded string or submit them using your form via the GET method, or you may use the Standard IN/OUT stream via POST when all data that needs to be transferred to the page is stored in the <input type="hidden" ...>
HTML tags:
<form method="POST">
<input type="hidden" name="param1" value="value1">
<input type="hidden" name="param2" value="value2">
<input type="submit" value="Submit">
</form>
POST has biggest advantage. It hides data from the user and "the evil person" who may want to compromise your security system as well. However, everyone might write his own HTML form, fill your "hidden" inputs with the data he likes, and submit it to your application:
<form method="POST">
<input type="hidden" name="param1" value="value2">
<input type="hidden" name="param2" value="something_else">
<input type="submit" value="Submit">
</form>
The GET method is more flexible since you may want to send some data to the page invoked through the URL link:
<a href="your_page.asp?param1=value1¶m2=value2">
But imagine, anyone can access your resource even easier just by typing this string in their browser address box:
http://your_site.com/your_page.asp?param1=value1¶m2=value2
Someone may also want to type it like this:
http://your_site.com/your_page.asp?param1=value2¶m2=something_else
Here’s what we need to do:
- We need to maintain our application state to be sure at page 10 that the user who authenticated at page 1 is still the same user. We would like to not use ASP Sessions and cookies in critical sections of our application.
- We need to have some reliable way of checking that all the pages (except default.asp) of our application are always called with the correct parameters and that they are really called from within the application itself.
Once problem 2 is solved, we will be able to accomplish task 1 successfully by transferring the application data from page to page.
How can we solve problem 2? The only way is to introduce some sort of authentication at the beginning of each page to check, again, that the page is called with correct parameters and that it is really called from within the application itself. Obviously, we can’t bother the user with UserId/Password inquiries so frequently, so we need to find something else that can answer our questions behind the scenes. Maybe the client’s browser can help.
Let’s check the arsenal. What is information can the client browser give us? We will write a very small utility program called list_env.asp that may help in many cases. All it does is list all the server environment variables so we can see what information we have. Here is the ASP code for list_env.asp:
<%@Language=VBSCRIPT%>
<HTML>
<BODY>
<%
For Each v in Request.ServerVariables
Response.Write("<p><b>" & v & "</b> = ")
Response.Write(Request.ServerVariables(v))
Next
%>
</BODY>
</HTML>
And here is a fragment from this application’s output when invoked from page1.asp through the link <a href="list_env.asp">List environment variables</a>:
APPL_PHYSICAL_PATH = C:\Inetpub\wwwroot\
LOCAL_ADDR = 127.0.0.1
PATH_INFO = /list_env.asp
PATH_TRANSLATED = C:\Inetpub\wwwroot\list_env.asp
QUERY_STRING =
REMOTE_ADDR = 127.0.0.1
REMOTE_HOST = 127.0.0.1
REQUEST_METHOD = GET
SCRIPT_NAME = /list_env.asp
SERVER_NAME = localhost
URL = /list_env.asp
HTTP_ACCEPT = image/gif, image/x-xbitmap, image/jpeg,
image/pjpeg, application/vnd.ms-
excel, application/msword, application/vnd.ms-powerpoint, */*
HTTP_ACCEPT_LANGUAGE = en-us
HTTP_REFERER = http://localhost/page1.asp
HTTP_USER_AGENT = Mozilla/4.0 (compatible; MSIE 4.01; Windows NT)
HTTP_COOKIE = GENERALRESTAURANTS=LOG=Login11&PWD=22; GUID;
ASPSESSIONIDGGQQGQYX=ICCNNJJDIHBCADHHDHIHLDNG;
ASPSESSIONIDQQQGGGYN=EJCPOJJDOGCIOGFCBDOMEIAD; …
HTTP_ACCEPT_ENCODING = gzip, deflate
You may want to store the user IP address (REMOTE_HOST variable) somewhere on your system and verify at the beginning of each page whether it is the same user. (See 15 Seconds article by Alain Trotter in the Nov. 4, 1998 issue, “ASP Authentication Using IP Address”). But this still doesn’t guarantee the user will not call your page by typing something in the address box. It also may not work with some network configurations where the user IP address may change.
HTTP_REFERER, however, will give you all the information you need. By checking the HTTP_REFERER value at the beginning of each page you can assure that the page is really called from within the application itself and, therefore, is called with correct parameters!
Editors Note
The "HTTP_REFERER" environment variable is set by the server
before the ASP script is processed. The contents of this field are obtained
by reading the HTTP header passed in to the server by the client browser.
However, according to the HTTP/1.0 specifications and the HTTP/1.1
specifications, the "HTTP-Referer" field in the HTTP header is optional:
"Note: Because the source of a link may be private information or
may reveal an otherwise private information source, it is strongly
recommended that the user be able to select whether or not the Referer field
is sent. For example, a browser client could have a toggle switch for
browsing openly/anonymously, which would respectively enable/disable the
sending of Referer and From information."
If the client does not send the "HTTP-Referer" field in its header, the
authentication advocated in your article will fail -- thus locking out
authorized users. More to the point, since the "HTTP-Referer" field is
generated by the client browser and sent in with the HTTP request, it is
untrustworthy. Any system cracker worth who knows what they're doing will
be able to fake their "HTTP-Referer" field just as easily as they can fake
cookies and forms. Advocating that a website's security rely on the
"HTTP-Referer" field, cookies, "HIDDEN" form fields, HTTP/1.x's "Basic
authentication" or IP address verification is foolish in the extreme, since
none of those techniques are bulletproof -- they can all be circumvented.
For more details, I would refer you to the following RFCs:
Special Thanks to Sam Clippinger
To make transferring data between the pages of your application safe and reliable, consider checking the HTTP_REFERER environment variable at the beginning of each page. This can be done in just two steps:
- First write the simple ASP include file; and name it referrer.asp*:
lt;%
Dim sResult
sResult = InStr( 1, Request.ServerVariables("HTTP_REFERER"), _
"//your_host_name/your_app_path/", 1)
if sResult = 0 or sResult = Null then
Response.Redirect("error.htm")
End If
%>
Note
You shouldn’t give your include files an .inc extension because anyone will be able to access their source by simply typing the file location in the browser’s address box. When the extension is changed to .asp the ASP processor interprets the file and no output will be done in most cases. For details see Active Server Developer’s Journal, February 1998 “Making #INCLUDE files "hack-proof" by Jeremy Broyle.
The above code is just checking whether the user really comes from your site.
2. Include this check in all of your pages that take parameters:
<%@ LANGUAGE = VBSCRIPT%>
<%Option Explicit%>
<!-- #INCLUDE file="referer.asp" -->
<% ‘Your ASP code goes here %>
This assures that your site will have only the number of entry points that you want it to have.
These simple steps will make your site behave like a single application. When the user authenticates at page 1, you will be sure that it is still the same user (more precisely - the same client machine) at page 10, because there will be no way to submit a UserId anywhere else besides within your web application.