|
Introduction
This article explains how to control application access by validating the user’s login and password against a database. Once validated, the IP address of the machine they are using is checked at the top of every page. What a given user can or can't do (that is, the security levels) is now handled easily.
Note:
Whitfield Diffie and Martin Hellman invented the first public key exchange protocol at Stanford in 1976. Public key cryptography was launched with this seminal work. The Diffie-Hellman cryptosystem is currently used with the Digital Signature Standard (DSS). Diffie-Hellman is based on the "discrete logarithm" hard problem.
Overview
The security method presented here comes close to the simplicity of cookie security without the headaches of losing a cookie - or cookies being turned off by the user. The one thing that the user browser will always give you is its IP address. Otherwise, where would the Web server send its response? You verify the person with an ID and password against a predefined list in ASP or a database. I recommend verifying against a database and I demonstrate how to do this with code.
An IP address-based schema presented here assumes the IP address for a given machine remains constant. For most networks it is constant. For a few networks IP addressing is dynamic where the address will change for a machine with each start up, and sometimes even during operation. The security schema presented won't work if the IP address changes while the user machine is on; however, this is rare (i.e., I think AOL proxy servers do this for dial-in clients).
Once you verify that the user has permission to access the application, you save the IP address to the database with a time stamp. You have accomplished the key step - creating a traceable identifier (think of the IP address as a security badge) for a validated user that is accessible throughout the ASP application. Remember that date stamp? You can set an expiration date or a time span on a given traceable identifier. Check each requester's IP address using:
Request.ServerVariables("REMOTE_HOST")
Now check that IP address' time stamp. If it has lapsed beyond the limit you set (i.e., midnight) send him to the logon page for revalidation using Response.Redirect ("logon.asp"). I allow user validation to last only one day. Midnight is a fair compromise between necessary periodic user revalidation and user annoyance with high logon repetition. Below, notice that at midnight the difference between the datestamp associated with an IP address (actually all logons for that day), and the computer system time becomes 1 or TRUE for the IF-THEN test:
If Rs.EOF OR DATEDIFF("d",rs("DateTime"),NOW()) Then
Response.Redirect ("logon.asp")
ELSE
Authorization = TRUE
END IF
Think First, Code Second
Design your security from solid theory. This will prevent nightmares later. Your security tricks may be cute, but be certain they rest on lasting principles. The following ideas work together to form a solid foundation. Figure out the best way to take advantage of security theory. Analyze the situation to achieve the greatest positive change in security according to the Security Change Definition. It's best to start coding after taking some time to think. Presented first in this section is the fundamental definition. Then look at the two ideas that follow.
Security Change Definition
| Equation |
dS = Sf -
Si |
| Definition |
From this definition we see that the change in security is
positive if Sf is greater than Si and negative
if Sf is less than Si. For example, if a
system, say an ASP page, changes its security level from
Si = 1 unit to Sf = 6 units, its security
change dS = 5 units. |
Theory 1 : Data Accessibility Based Valuation Theory
| Definition |
The valuation of a given data set is
directly related to the number of times that data set is
accessed. |
| Equation |
Vd = FXa where:
- Vd = Data value
- F = Combined factors
- Xa = Number of times data is accessed
|
Example Calculation |
Vd = FXa
Vd = ($100)(.05)(100)
Vd = $500 where:
- Vd = $500 Data value
- F1 = $100 (engineer compensation factor for one hour work)
- F2 = 5% (Percentage of times engineer gets involved when she
looks at bug database report)
- Xa = 100 (sample number of times an engineer looks at bug
database report)
|
| Note |
The above example calculation valued a hypothetical bug database
at $500 for every 100 engineer reviews. It cost the company $500 for
those 100 views. However, this is only a small part of the overall
valuation of a given data store. Consider how much it would have
cost the company if that astute engineer did not fix those bugs.
|
Theory 2 : Data Stress Law
| Definition |
The tendency for a given data set to
change is directly related to the number of times that data set is
accessed. |
| Equation |
Cd = FXa where:
- Cd = Change in data
- F = Combined factors
- Xa = Number of times data is accessed
|
Example Calculation |
Cd = FXa
Cd = ($100)(.01)(100)(.00000001)(1000)
Cd = $.001 where:
- Cd = $.001 Data value due to unwanted changes
- F1 = $100 (engineer compensation factor for one hour work)
- F2 = 1% (Percentage of times data gets corrupted when that
data set gets updated such as mistyping an input field)
- F3 = .000001% (Percentage of times data gets corrupted when
that data set is only looked at)
- Xa = 1000 (sample number of times general user looks at bug
database report)
|
| Note |
The above example calculation used a corruption rate of .000001%
(Percentage of times data gets corrupted when that data set is only
looked at, say, with an SQL select statement). Most market leading
database vendors would argue strongly with a cadre of lawyers for a
lower number. They might be right, but it is fun to goad the
ultra-rich, don't you think? |
Analysis
How do you use the Data Accessibility Based Valuation Theory and the Data Stress Law to help analyze your security? You reduce the stress on your system's sensitive data by locking out everyone but a chosen few. This action reduces the number of times a given data set is accessed, reducing the possibility of change in data (read corruption). You can do this at the database level or at the ASP level. While I recommend doing it at the database level, it is easier to do it at the ASP level.
The Accessibility Based Valuation Theory uncovers the need to get data in the system only once and let many people see it many times: a one-to-many relationship. Use this theory to design your ASP application so that a data set is entered into the system once. The permission to do this should be kept to a short list of users. Enforcing this policy is where the security code comes in. Then, allow many folk to see it. For example, a development bug system I built allowed few people to enter bugs, fewer still to change data already in the system, but everyone to view and search the bug database.
Encryption Overview
The distance between the user and the Web server is a security risk. Someone in between the two can snoop. When the user submits her ID-password it must travel along the network wires. If a bad person intercepts the keys then he could use them to gain unauthorized access. The network loop represents a security hole – a snoophole.
The Secure Sockets Layer (SSL) is a technique where the browser and Web server encrypt information before sending it, and decrypt messages that they receive. Anyone snooping between them sees gibberish. There are also third party tools that do the same. The browser mashes and bashes data so no one trashes it. Only the Web server can unmash and unbash it.
How can you, an ASP guru, stop snoopers? Encryption! While I advocate SSL or third party security solutions, you can build your own encryption process with ASP. You secure data with mirroring encryption/decryption functions across the browser and Web server. Like SSL, you encrypt form information from the browser before sending form contents to your ASP pages that decrypt the data. In reverse, your ASP page could encrypt information before sending it to the browser and have the browser decrypt it with a script that decrypts the data upon load. While you need to use a strong routine, the following is a simple example (popular XOR algorithm) of what you could have in ASP. Passing a data and password pair the first time encrypts the data. Passing the encrypted data and same password pair the second time decrypts it. The elegant, although weak, XOR method has made the routine a popular starter for many to get acquainted with the encryption concept.
An ASP Castle - A Metaphor
Security is a block wall. A select group easily gets beyond it, but no one else. It is a way to let good people in and bad people out. The following ASP castle is sturdy, but, alas, the walls are scalable.
You build the walls from the user name, password and IP address blocks. An ASP guard verifies a unique- hopefully secret - user name and password combination. If verified, the ASP guard gives your guest a security badge with the IP address on it and allows passage. Once in, your guest can get into some rooms depending on his privileges, but not the others. Anyone caught without an approved badge is banished in a water clock second.
Some ASP builders use session blocks, but in that case, the ASP guards will not come to work unless you feed them cookies. Other builders use NT security boulders. They are strong, but once in the castle the guests can go into every room, even if you want to restrict their access to only certain rooms. This is because all of the NT security happens at the gate. Once past the gate, the ASP guards can't distinguish one guest from another.
Method Details
Here is the code to implement ASP security on your application without using NT security or session objects. Simply, you check the user ID and password combination against a table in the database. If there is a match, then you update the IP address and date stamp associated with that ID/Password combination. Now grant access to the user. From now on, check the requesting IP address against the database. If there is a match, grant access. If there is no match then redirect him to the logon page. The only other detail you need to observe is the datestamp attached to the IP address. If the request is made after midnight, force the user to logon again. They will have to do this once a day to preserve some validation. You can change this to any time period with the "datediff" function in the security check section of the code.
Security Check Include at Top of Each ASP Page Requiring Security
'include the following at top of each ASP page requiring security
<!-- #include virtual="/SCRIPTS/BUG/SECURITY.INC" -->
Security Include File
strFileName = Trim(Server.MapPath ("\scripts\bug")) & "\userinfo.mdb"
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open "driver={Microsoft Access Driver (*.mdb)};dbq=" & strFileName & ";"
Set rs = Server.CreateObject("ADODB.Recordset")
LOGIP=Request.ServerVariables("REMOTE_HOST") SQL = "SELECT * FROM tUsers " &_
"WHERE (((tUsers.UserIP) = '" & LOGIP & "')); " rs.Open sql, conn, 3, 3
If Rs.EOF OR DATEDIFF("d",rs("DateTime"),NOW()) Then
Response.Redirect ("logon.asp")
ELSE
Authorization = TRUE
userid = rs("userid")
END IF
Logon Page Code
' Form field check
id = Request.Form("Userid")
pwd = Request.Form("Password")
'if forms are filled then proceed
if len(id)>0 and len(pwd)>0 then
'---- CursorTypeEnum Value ----
Const adOpenKeyset = 1
'---- LockTypeEnum Values ----
Const adLockOptimistic = 3 '
---- CommandTypeEnum Values ----
Const adCmdText = &H0001
'get path of database
strFileName = Trim(Server.MapPath ("\scripts\bug")) & "\userinfo.mdb"
'setup database connection
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open "driver={Microsoft Access Driver (*.mdb)};dbq=" & strFileName & ";"
Set rs = Server.CreateObject("ADODB.Recordset")
SQL = "SELECT * FROM tUsers WHERE (((tUsers.Password) = '" & Pwd
SQL = SQL & "') AND ((tUsers.Userid) = '" & id & "')); "
rs.Open sql, Conn, adOpenKeyset, adLockOptimistic, adCmdText
' If the user is valid the recordset will have a record
' otherwise call up the form again.
If NOT Rs.EOF THEN
IF request("enter") = "Log Out" THEN
rs("DateTime")=now()-1
On Error Resume Next
rs.Update
msg = "You are logged out. You will need to log "
msg = msg & " in again to update or add data."
response.write msg
%>
<META HTTP-EQUIV="Refresh" CONTENT="3;URL=http:default.asp">
<%
response.end
END IF
NewPassword1 = request("NewPassword1")
NewPassword2 = request("NewPassword2")
IF LEN(NewPassword1) > 0 AND LEN(NewPassword2) > 0 THEN
IF LCASE(NewPassword1) = LCASE(NewPassword2) THEN
rs("Password") = LCASE(NewPassword1)
Password = LCASE(NewPassword1)
END IF
END IF
NewUserName1 = request("NewUserName1")
NewUserName2 = request("NewUserName2")
IF LEN(NewUserName1) > 0 AND LEN(NewUserName2) > 0 THEN
IF LCASE(NewUserName1) = LCASE(NewUserName2) THEN
rs("Userid") = LCASE(NewUserName1)
Userid = LCASE(NewUserName1)
END IF
END IF
LOGIP=Request.ServerVariables("REMOTE_HOST")
rs("Permission")=TRUE rs("UserIP")=LOGIP
rs("DateTime")=now()
On Error Resume Next
rs.Update
IF LEN(Userid) > 0 OR LEN(Password) > 0 THEN
IF LEN(Password) > 0 THEN
msg = "Your <B>Password</B> has been changed to: <B>"
msg = msg & Password & "</B><P>
" response.write msg
END IF
IF LEN(Userid) > 0 THEN
msg = "Your <B>User Name</B> has been changed to: <B>"
msg = msg & Userid & "</B><P>"
response.write msg
END IF
%>
<META HTTP-EQUIV="Refresh" CONTENT="3;URL=http:default.asp">
<%
response.end
END IF
response.redirect "default.asp"
END IF
rs.close 'close the records
conn.close 'close the connection
END IF
%>
<html>
<head> <title> Bug Manager Logon</title></head>
<BODY BGCOLOR="#31639C" TEXT="ffffff" LINK="#00008B" ALINK="#FF0000" VLINK="#8B008B">
<center><h1> Bug Manager</h1>
<h3>Registered Users</h3>
<form name="passwordform" action="/scripts/bug/logon.asp" method="POST">
<table width=415 border=0 cellspacing=0 cellpadding=0>
<tr>
<td>User Name:</td>
<td></td>
<td>Password:</td>
<td align="left">
<input type="submit" name="enter" value="Log In"></td>
</tr>
<tr> <td><input type="text" name="userid" size="16" maxlength="16"></td>
<td></td>
<td><input type="password" name="password" size="16" maxlength="16"></td>
<td align="left"><input type="submit" name="enter" value="Log Out"></td>
</tr>
</table>
</CENTER>
<HR>
<P>
You may change your login name or password at any time.
After correctly providing your current user name and password above,
type in your new user name or password below.
<CENTER>
<table width=415 border=0 cellspacing=0 cellpadding=0>
<tr>
<td>New Password:</td>
<td> </td>
<td>Confirm New Password:</td>
<td></td>
</tr>
<tr>
<td><input type="password" name="NewPassword1" size="16" maxlength="16"></td>
<td></td>
<td><input type="password" name="NewPassword2" size="16" maxlength="16"></td>
<td align="center" valign="top"></td>
</tr>
<tr>
<td>New User Name:</td>
<td></td>
<td>Confirm New User Name:</td>
<td></td>
</tr>
<tr>
<td><input type="text" name="NewUserName1" size="16" maxlength="16"></td>
<td></td>
<td><input type="text" name="NewUserName2" size="16" maxlength="16"></td>
<td align="center" valign="top"></td>
</tr>
</table>
</form>
</center>
</body>
</html>
Authorization Level Controls Program Flow
<%' checks password in session
IF NOT Authorization THEN response.redirect "/scripts/bug/logon.asp" %>
'continue if authorized and branch based on user id
' retrieved in security.inc include
SELECT CASE userid
CASE "jtronp"
'perform jtronp specific function
CASE "kholland"
'perform kholland specific function
CASE "lroberts"
'perform lroberts specific function
CASE ELSE
'perform everyone else specific function
END SELECT
%>
MS Access ID - Password Table Definition
<TABLE border=2 width=600>
<CAPTION align=top><BIG><A name=accessidtabledef>MS Access ID - Password
Table Definition </BIG></A></CAPTION>
<TBODY>
<TR >
<TD><B>Field Name</B></TD>
<TD><B>Data Type</B></TD>
<TD><B>Note</B></TD></TR>
<TR >
<TD>ID</TD>
<TD>AutoNumber</TD>
<TD>Always a good idea</TD>
<TR bgColor=#ffffe0>
<TD>Userid</TD>
<TD>Text (10)</TD>
<TD>Restrict size in HTML Input tag too</TD></TR>
<TR bgColor=#ffffe0>
<TD>Password</TD>
<TD>Text (10)</TD>
<TD>Restrict size in HTML Input tag too</TD></TR>
<TR bgColor=#ffffe0>
<TD>DateTime</TD>
<TD>Date/Time</TD>
<TD>Updated every logon</TD></TR>
<TR bgColor=#ffffe0>
<TD>UserIP</TD>
<TD>Text (20)</TD>
<TD>Most important data chunk</TD></TR>
<TR bgColor=#ffffe0>
<TD>SecurityLevel</TD>
<TD>Number</TD>
<TD>Use this to enforce level-feature policy</TD></TR>
<TR bgColor=#ffffe0>
<TD>Permission</TD>
<TD>Yes/No</TD>
<TD>Provides a way to turn some away</TD></TR></TBODY></TABLE></CENTER>
<P>
<CENTER>
<TABLE border=2 width=600>
<CAPTION align=top><BIG><A name=usertracertabledef>MS Access User Tracer
Table Definition </BIG></A></CAPTION>
<TBODY>
<TR bgColor=#ffbb00>
<TD><B>Field Name</B></TD>
<TD><B>Data Type</B></TD>
<TD><B>Note</B></TD></TR>
<TR bgColor=#ffffe0>
<TD>ID</TD>
<TD>AutoNumber</TD>
<TD>Always a good idea</TD>
<TR bgColor=#ffffe0>
<TD>Userid</TD>
<TD>Text (10)</TD>
<TD>Optional: helps in dynamic IP allocation networks</TD></TR>
<TR bgColor=#ffffe0>
<TD>DateTime</TD>
<TD>Date/Time</TD>
<TD>Set default to Now()</TD></TR>
<TR bgColor=#ffffe0>
<TD>UserIP</TD>
<TD>Text (20)</TD>
<TD>Most important data chunk</TD></TR>
<TR bgColor=#ffffe0>
<TD>URLFrom</TD>
<TD>Text (100)</TD>
<TD><% = Request.ServerVariables("HTTP_REFERER") %></TD></TR>
<TR bgColor=#ffffe0>
<TD>URLCurrent</TD>
<TD>Text (100)</TD>
<TD><% = Request.ServerVariables("PATH_INFO")
%></TD></TR></TBODY></TABLE></CENTER>
Equivalent SQL Server Script
/* Microsoft SQL Server - Scripting */
/* Database: SecureUser */
/* Creation Date 31/9/98 10:22:09 */
PRINT '----------------------------------'
PRINT 'Starting execution of [place script name here]'
PRINT '----------------------------------'
PRINT ''
GO
/* Recommend creating database with SQL Enterprise Manager */
/* If you want to do it with SQL use the top portion of the script */
print ''
print getdate()
print ''
print 'Creating database securitydb'
print ''
GO
use master GO
set nocount on
if not exists (select name from master.dbo.sysdatabases where name = 'securitydb')
create database securitydb on [Data device] = 6 log on [Log device] = 2
GO
use securitydb
GO
/* Check that we're in securitydb... */
if (db_name() <> 'securitydb')
raiserror('A problem was encountered accessing securitydb. Script terminating.',
20, 127) with log
GO
checkpoint
GO
dump tran securitydb with no_log
GO
print ''
print 'Created database securitydb'
print ''
GO
use track_master
/****** Object: Table dbo.Users Script Date: 31/9/98 10:22:09 ******/
if exists (select * from sysobjects where id = object_id('dbo.Users')
and sysstat & 0xf = 3)
drop table dbo.Users
GO
/****** Object: Table dbo.UserPath Script Date: 31/9/98 10:22:09 ******/
if exists (select * from sysobjects where id = object_id('dbo.UserPath')
and sysstat & 0xf = 3) drop table dbo.UserPath
GO
/****************************************************************************/
/* This table designed to identify users with */
/* their ID and password allowing an upgrade to the newer ones. */
/***************************************************************************/
/****** Object: Table dbo.Users Script Date: 31/9/98 10:22:09 ******/
CREATE TABLE dbo.Users (
ID smallint IDENTITY (1, 1) PRIMARY KEY CLUSTERED, /* Unique record identifier */
Userid char (10) NOT NULL , /* Aliase of user */
Password char (10) NOT NULL , /* Password of user */
UserIP char (20) NOT NULL, /* IP address of user machine */
SecurityLevel int NULL , /* Allows more robust access policy */
Permission tinyint NULL DEFAULT (1), /* This allows you to prevent access wholesale */
DateTime datetime NOT NULL, /* Time user logged on */
Note char (150) NULL /* often a good idea */
)
GO
ALTER TABLE
Users
ADD
CONSTRAINT CK_UserIP CHECK (UserIP LIKE
'[0-9][0-9][0-9].[0-9][0-9][0-9].[0-9][0-9][0-9].[0-9][0-9][0-9]'
)
print ''
print 'created CK_UserIP CHECK'
GO
ALTER TABLE Users
ADD
DEFAULT getdate() FOR DateTime
print ''
print 'Added DEFAULT getdate() FOR DateTime'
GO
/*
** Permission Check:
** The value can be:
**
** Permission status
** ====== ========
** 0 inactive
** 1 active */
/*********** Example UserID Constraint **************************/
/* CONSTRAINT CK_Userid CHECK (Userid LIKE */
/* '[A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9]') */
/****************************************************************/
/********** Example Insertion Statement *******************/
/* insert Users (Userid, Password, UserIP) */
/* values('atrottier', 'mysecret', '209.232.078.004') */
/**********************************************************/
/****** Object: Table dbo.UserPath Script Date: 31/9/98 10:22:09 ******/
CREATE TABLE dbo.UserPath (
ID smallint
IDENTITY (1, 1)
PRIMARY KEY CLUSTERED,
UserIP char (20) NOT NULL,
URLFrom char (100) NOT NULL ,
URLCurrent char (100) NULL,
DateTime datetime NOT NULL,
)
GO
print ''
print 'created table UserPath'
GO
ALTER TABLE UserPath
ADD
DEFAULT getdate() FOR DateTime
print ''
print 'Added DEFAULT getdate() FOR DateTime'
GO
/****** Can find out how many users logged on with: ******/
/* select Number_users = count(*) */
/* from Users */
/********************************************************/
/* Can play with expiration date with these two statements: */
/* select @dayofweek = (datepart(weekday,@now)+@firstday)%7 */
/* select @monthyear = substring(convert(varchar(12),getdate()),1,12) */
GRANT SELECT , INSERT , DELETE , UPDATE ON Users TO public
GO
GRANT SELECT , INSERT , DELETE , UPDATE ON UserPath TO public
GO
CHECKPOINT /* optional, but I do it to clean up loose ends */
GO
/* all done, so leave now */
print '' select 'Current Date: ' = getdate()
print ''
print 'Script Completed!'
print ''
GO
User Activity Tracker
You can record the movements of your users. In the above, the security server side include file code tracks the IP, page from which the user came and the current page. There are several approaches you can take. Keep it simple. The second table (UserPath) acts as an event log. Each event is actually a page hop. Each time the user hits a page, add a record of the hit to the table. You may decide to dump it to storage periodically if space becomes a problem. Use this table for usage analysis and to help clarify matters regarding who did what that inevitably occur.
Advantages of an IP based Security Schema
- You can set conditionals for what is displayed based on the user logon. If a user tries to go circumvent the security they will be sent to the logon page automatically.
- Logons last until a preset date-time (i.e., midnight) and last as long as the user machine keeps its IP address. (Some networks dole out IP addresses dynamically from a predefined pool, whileothers are permanently assigned.)
- The user can change his user id or password from the logon page at any time.
- The user can log on from any machine that has access to our intranet including other locations. There are no license issues.
- The user can log out at any time. After logging out he will have to log on, again, from any machine.
- Any user can search the bug database without logging on.
- Security is browser independent.
- The user can completely close and restart his browser or use a different browser on the same machine and still retain the ability to update and add bugs without needing to log on again.
- The user can log on from different machines at will, but the last machine the user logs on from is the only valid login.
About the Author
Mr. Alain Trottier is the Webmaster at AdForce, Inc. a leading provider of
centralized advertising management services. The Company serves well over one
billion ads each month to websites. He focuses on consolidating and organizing
the Company's data into a true knowledge system. The pace can cause head trauma,
but he likes it. While his wife and son out-smart him routinely, he loves them
and asks Jesus to help him catch up.
|