asp:cover story
LANGUAGES: C#
ASP.NET VERSIONS: 1.0 | 1.1
7 Tips for Secure Apps
Use these tips to make sure you don't sacrifice securityin the name of performance.
By John Paul Mueller
Most developers know that securityis a major issue and that their livelihoods will depend on getting the securityof their next application right. There's no lack of pressure to makeapplications perform well and still protect data. Unfortunately, performanceoften wins out, and in some cases, time is the factor that causes both securityand performance to fall by the wayside. The tips in this article should helpyou make your application secure, without giving up much in the way ofperformance and without missing those important deadlines.
1. Don't Assume the Environmentis Consistent or Static
Many developers assume that theprogramming environment is going to remain consistent forever. They deal in astatic world of absolute facts that never change. However, the world is farfrom consistent or static. Some of the worst programming nightmares in historyhave occurred because the programming environment changes. Consider the Y2Kdebacle or the rise of crackers on the Internet. The real-world environment inwhich your application executes is constantly changing - in many cases, not forthe better.
Consider the case of a company thatemployed a number of programmers to maintain an application. After some numberof changes, the application did work better. However, because the variousprogrammers assumed that the company would distribute the application in aparticular environment and that every programmer was conscientious aboutsecurity concerns, the application developed a number of security holes, manyof which proved difficult to find. The company finally ended up hiring aconsultant to look through the code and close the security holes.
The best way to avoid assumptionsabout the application environment is to make the application flexible. Aflexible application responds to changing threats. For example, you can overcomemany threats through the simple check of incoming data type, content, andlength. In addition, restricting user input to just the information you needreduces the chance of errant data or a cracker attack. Such applications areflexible because they don't assume anything about the user or the environmentin which the application will operate.
The moment you choose to focus on aparticular security area because it's currently a threat, an applicationbecomes vulnerable to attack. Looking for anything that tends to make anapplication less reliable, usable, or robust also tends to point out potentialsecurity flaws, even if an issue isn't a security problem now. For example, italways pays to validate the data going into a buffer because such checks make theapplication more reliable. However, such checks take time to write, and theyreduce application performance because they use up processing cycles, so manydevelopers didn't add them in the past. Today, the buffer overflow is one ofthe most pressing security problems vendors face.
2. Perform Range Checks
Determining that the data youreceive is in the correct range is important. For example, if you need a numberbetween 1 and 5, don't accept the number 6. This simple check extends to otherdata types, as well. Make sure you check the length of strings and don't acceptsomething that is too long. You can perform some range checks by using simpletag attributes, such as the maxlength attribute for the <input> tag.Other range checks can rely on code behind; you can check the data once itarrives at the server. However, the simplest range check is using one or morevalidators to ensure the integrity of the data the client sends.
A validator is a special .NETFramework control. To use it, you fill in two or more properties depending onthe control type. Figure 1 shows a password dialog box that includes twovalidators for each of the text boxes. In this case, the first validatorensures the associated text box contains some text, and the second validator ensuresthe input is in the correct format. The ControlToValidate property associatesthe validator with the TextBox control, and the ErrorMessage property controlsthe error message you see in the figure. When a user fails to provide thecorrect input, the validator catches the error and displays the error message.You don't have to do any programming; just place the validator on screen andfill out the appropriate properties. In general, you'll rely on four validatorsfor your applications: RangeValidator, CompareValidator,RegularExpressionValidator, and RequiredFieldValidator.
Figure 1. Forms commonly containmultiple validators for each data-entry field. Each validator performs aspecific check on that field to ensure it meets all input requirements and providesthe user with field-specific help should an error occur.
The RangeValidator ensures theinput in a control falls within a range of values. The MinimumValue andMaximumValue properties contain the limit of values the user can input. You'lluse the Type property to determine what type of data the control will accept.The RangeValidator accepts common types, including string, integer, double,date, and currency. If the input doesn't fall within the selected range ofvalues or is of the wrong type, the control will display an error message.
The CompareValidator accepts twocontrols as input and then compares the value of each control. If the twocontrols don't match the condition you specify, the CompareValidator displaysan error message. The name of the second control appears in theControlToCompare property. The Operator property defines the comparison betweenthe two controls. For example, if you choose the GreaterThan option, the valueof the control listed in the ControlToValidate property must be greater thanthe value of the control listed in the ControlToCompare property. A Typeproperty ensures the second control contains data of the correct type, but thisis almost superfluous because the two controls won't compare if their typesdon't match.
The RegularExpressionValidator usesan expression to validate the content or format of the input. You'll find thatthe Microsoft help topics tend to focus on the format of the expression, as dothe built-in expressions. However, the ValidationExpression property cancontain any regular expression that describes the content you expect. Forexample, using a ValidationExpression property value of[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]{3,15} tells theRegularExpressionValidator to accept only characters as input and to restrictthe length of the input from three to 15 characters. Given the extremeflexibility of regular expressions, you can define any kind of range check youmight need.
The RequiredFieldValidator is theeasiest validator to understand. If the target control is blank, the validatordisplays an error message. Some developers will use an asterisk in place of theerror message and simply display one error message for all required fields.However, the use of a custom error message for each control means that you canprovide example input for each field.
3. Analyze the Input
Regular expressions and other formsof analysis can be your best friend for some types of input. For example, Iregularly visit Web sites that don't know whether I've input a telephone numberor a credit-card number or some text that came to mind. Using a regularexpression to test the data won't verify that it's correct, but at least itwill verify that the kind of data is correct. Ensuring the data is in the correctform automatically reduces the chance that someone will send a virus or hackinto your system by sending incorrect input.
You can accomplish this task usinga RegularExpressionValidator. This control comes with a number of presetvalidations, as shown in Figure 2. It's also possible to create your ownregular expressions.
Figure 2. The Regular ExpressionEditor contains a number of preset expressions you can use to check input.These examples also help you create your own regular expressions without spendinga lot of time in the documentation.
Sometimes, you can't use aRegularExpressionValidator because the data is too complex for a simplestatement or because you need to look for specific information. For example,you can't validate a credit-card number using a RegularExpressionValidator.It's also hard to verify free-form data or some types of complex data, such aspart numbers. All of these kinds of input require local validation at theserver. In this case, you can combine a number of checks in a function, such asthe one shown in Figure 3.
private void btnTest_Click(
object sender,System.EventArgs e)
{
Regex PartCheck; // Checks thepart number format.
// Create the regularexpression.
PartCheck =
newRegex("[ABCDEFGHIJKLMNOPQRSTUVWXYZ]{3}-" +
"[0123456789]{3}-[0123456789]{5}");
// Check the inputlength.
if(txtPartNum.Text.Length != 13)
{
lblResponse.ForeColor = System.Drawing.Color.Red;
lblResponse.Text ="Use input form of XYZ-123-01234";
return;
}
// Verify the text isin the correct form.
if(!PartCheck.IsMatch(txtPartNum.Text))
{
lblResponse.ForeColor = System.Drawing.Color.Red;
lblResponse.Text ="Use input form of XYZ-123-01234";
return;
}
// Ensure the user hasactually changed the input.
if (txtPartNum.Text =="XYZ-123-01234")
{
lblResponse.ForeColor = System.Drawing.Color.Red;
lblResponse.Text ="Please change the part number.";
return;
}
// Check for part indatabase
// Perform otherchecks.
}
Figure 3. Usecustomized checks when a RegularExpressionValidator won't work for datavalidation. At a minimum, always check the input length and data type (orcomplex type).
The code shown in this listing beginsby creating a Regex object. This object checks the form of the data input. Inthis case, the input consists of three letters, a dash, three numbers, a dash,and five more numbers.
At this point, the code performs anumber of data checks. The first check is the length of the input. The reasonyou perform this check first is to reduce the risk of someone sending a scriptas part of the data. The next check validates the form of the data. Finally,the code ensures the user didn't just send the sample data as input.
At this point, the data isrelatively safe, but you haven't verified that it's actually good data. You'dneed to check for the part number in the database or perform some othervalidation checks. The idea is to move from general checks to specific checksin order to prove the data is good (or at least as close as possible) beforeyou use it for anything. Although this series of checks borders on theparanoid, you'll find that paranoid developers tend to have fewer securityproblems.
4. Perform Double Checks
You can limit the length of inputusing a simple HTML tag attribute. For example, take a look at the following:
<input type=text name="MyCheck" maxlength=40/>
This tag limits the size of theinput to 40 characters. Any legitimate user will type 40 characters, discoverthere's no room for additional text, and leave the input as is. A cracker, onthe other hand, might be tempted to exceed the 40-character limit.
The double check comes at theserver with your ASP.NET code. If you suddenly discover that this 40-characterinput has 500 characters in it, you know someone has been tampering with theinput. This activity is suspicious, and you need to check it out. No, thischeck won't prevent someone from attempting to input bad data, but it willalert you to the tampering. This kind of security check is a lot more helpfulthan you might think, because it looks for the unexpected.
5. Use Discrete Inputs
One of the main problems with mostWeb forms is that the user faces a blank text-input field that could containanything. Figuring out what to type takes time that the user might not have.Most developers like to use text-input fields because they provide freedom ofchoice, but users often see text-input fields as problematic. Even if the developerprovides a sample value and the user honestly is trying to use the formcorrectly, there's still a lot of room for interpretation. Users ask questionssuch as, "Do I need to type a number or a word in this field?"
In many cases, you can replace thegeneral input with discrete input. For example, a list box contains specificallowable values that make the choices clear to the user. The form is moreaccessible because the user doesn't have to figure anything out; all thecorrect answers appear directly on the form. A user also can fill the form outfaster.
From a security perspective, theprogrammer wins in a big way. When you use discrete input such as a list box,the cracker can't provide a handy script in place of the simple text you wereexpecting. Discrete inputs require less validation and less code, so usingdiscrete inputs is better for the developer and the user.
6. Stem the Tide of LeakingInformation
Most employees at your companyaren't trustworthy. It's not that they're spies for someone else ordisgruntled; it's that they simply don't think about the consequences ofplacing certain information on a Web site or understand computers well enoughto know they're letting the information out. Consider the case of a companythat handles sensitive information. An employee let some of this informationout by mistake - an honest error. Because the application assumed the employeeunderstood the consequences of making this information available, the employeelost a job, and the company lost clients. If the application developer hadassumed that employees are human and make mistakes, the data might not haveappeared on the Web site. The employee might have gotten a reprimand for notfollowing procedure, but he or she still would have a job, and the companywouldn't have lost clients.
Many developers discount theproblem of data leaks because they think they have followed every securityprocedure. However, data leaks occur not because of a programming problem butbecause of an implementation problem. The reason you should care about the dataoutput of your application is that people make mistakes, and your applicationcan help protect them from errors.
Displaying any information at allon a Web site causes an information leak for your organization. In anothercase, an employee posted a list of contact names and e-mail addresses on a Website without considering the effects of providing such a list. A cracker usedthe list to perform social engineering at the company. By using the informationprovided on the Web site, the cracker was able to obtain user-name and passwordinformation from several employees. The employees felt the cracker must have alegitimate need for the information because he or she knew so much about thepeople working there. Of course, because the purpose of a Web site is toprovide information to viewers, your application wouldn't be worth very muchwithout at least a small information leak. In addition, some kinds ofinformation leaks are actually good for your organization, such as when youannounce a new product.
Part of your responsibility as adeveloper is ensuring your design doesn't leak private data. For example, makesure your template contains only the information it needs to contain. Ofcourse, the template still has to provide functionality, so you can't leave itblank. Make sure that you don't confuse protected company information withessential user information. You still have to make the Web page accessible, andit still requires good help to avoid confusing the user.
Data-leak protection can take otherforms. For example, your organization might rely on a database to supplyinformation for Web pages. At some point, your program will need to access therequired database record, extract the data, apply it to a page, and output theresult. This setup provides a perfect opportunity to scan for forbidden words -such as the name of a new secret project - during the extraction phase, usingregular expression parsing. Obviously, this form of data-leak protection won'tfulfill every need; a smart user that is determined to leak information willfind some other words to do it. This limitation is the same reason spam filtersare only partially successful.
There is one last thing to considerabout data leaks: All of this monitoring does have an effect on applicationperformance. Consequently, you might have to weigh the security and privacybenefits of output monitoring against the performance penalty of doing it. Sometypes of businesses, such as financial institutions, probably should monitoreverything and make up the performance loss in other ways. However, if yourorganization works mainly with public data, you probably can use lessmonitoring to achieve better performance.
7. Use Performance Counters toYour Advantage
Some people assume that performancecounters are only useful for monitoring how fast a system performs a giventask. However, performance counters can be the best security investment youever made, because they help you monitor the condition of your application. Youcan use a performance counter to count anything, including the number of errorsyour application experiences because of incorrect password entries. Too manypassword errors could signal a cracker's attempt to infiltrate your system.Performance counters also can measure application traffic and make it possibleto discover trends in application usage. Analysis helps you learn aboutproblems, such as a Distributed Denial of Service (DDOS) attack, before theyreally become problems.
The reason analysis is important isthat you can prevent only so many kinds of security problems. Given enoughtime, any good cracker can overwhelm the fixed fortifications you provide foryour application, which means you must include some form of monitoring, also.Detecting security breaches is at least as important as preventing them in thefirst place.
Unfortunately, developers didn'talways have access to the simple monitoring technique demonstrated in this tip.Creating a performance counter in the Win32 API environment is nothing short ofa nightmare, so many developers avoid performing this task. However, the .NETenvironment makes the task of creating a custom performance counter relativelyeasy. All you need to do is create a custom performance counter that tracks thenumber of bad requests to your application. When the number reaches a specificthreshold, you know that something is wrong - it could be a DDOS attack. Figure4 shows an example of the performance-counter approach.
// A collection of counters.
CounterCreationDataCollection CounterCollect;
// The error counter.
CounterCreationData ErrorCount;
// Determines random error.
Random EventVal;
// Error performance counter.
PerformanceCounter PerfCount;
public frmMain()
{
// Required for WindowsForm Designer support
InitializeComponent();
// Create thecollection and error counter.
CounterCollect = newCounterCreationDataCollection();
ErrorCount =
newCounterCreationData(
"Error_Count",
"Contains the application errorcount.",
PerformanceCounterType.RateOfCountsPerSecond32);
// Add the counter tothe collection.
CounterCollect.Add(ErrorCount);
// Create a customcounter category.
PerformanceCounterCategory.Create(
Application.ProductName,
"Error checkingcounter.",
CounterCollect);
// Create theperformance counter.
PerfCount = newPerformanceCounter(
Application.ProductName,
"Error_Count",
false);
// Create the randomnumber generator.
EventVal = newRandom(DateTime.Now.Second);
}
private void btnTest_Click(
object sender,System.EventArgs e)
{
// Set the number ofevents per second.
EventGen.Interval =
Convert.ToInt32(1000/ txtTimerVal.Value);
// Start the timer.
EventGen.Start();
}
private void EventGen_Tick(
object sender,System.EventArgs e)
{
// Generate a randomerror event.
if (EventVal.Next(5)<= 1)
PerfCount.Increment();
}
private void frmMain_Closing(object sender,
System.ComponentModel.CancelEventArgs e)
{
// Destroy the counter.
PerformanceCounterCategory.Delete(
Application.ProductName);
}
Figure 4. Performancecounters can do more than just help you track performance. You also can usethem for error trapping and making security checks. Using performance countersadds automation to the security process.
I used a standard Windows application for the example tomake it easier to test, but the same technique works fine in a component or anyother kind of application you want to create. The constructor begins byinitializing the counter. This example shows a very simple counter. The codebegins by creating a counter and a counter collection to hold it. It then usesthe PerformanceCounterCategory.Create method to add the collection to Windows.The collection can contain any number of counters, and you can create countersof various types. You can read more about the various performance counter typesat http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemDiagnosticsPerformanceCounterTypeClassTopic.asp.
Creating a performance counter forWindows doesn't provide access to it. The code creates a new PerformanceCounterobject using the name of the counter category and the individual counter.Notice that you must set the ReadOnly argument to False, or you won't be ableto generate new data for the counter.
The one element in the constructorthat you don't need to create, in most cases, is the Random object, EventVal.This object generates random errors in this example. In a productionenvironment, you'd only want to know about the real errors.
The btnTest_Click method sets thetimer interval and starts the timer. Once the timer starts, it generates ticksat the rate specified by the Interval property. Each call to the EventGen_Tickevent handler is the result of a tick. The code checks the current randomnumber value. If it's less than or equal to 1, the code increments theperformance counter using the PerfCount.Increment method.
Because this is a temporarycounter, you should destroy it before you exit the application. ThefrmMain_Closing method accomplishes this task. All you need to supply is thename of the counter category.
The sample code in this article isavailable for download.
John Mueller is afreelance author, technical editor, and consultant who has produced 60 booksand more than 200 articles to date. The topics range from networking toartificial intelligence and from database management to heads-down programming.His most recent book is.NET DevelopmentSecurity Solutions (Sybex, ISBN 0-7821-4266-4). His technical-editingskills have helped at least 32 authors refine the content of their manuscripts.You can reach John on the Internet at mailto:JMueller@mwt.net,and his Web site at http://www.mwt.net/~jmueller/.