Skip navigation

7 Tips for Secure Apps

Use these tips to make sure you don’t sacrifice security in the name of performance.

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 security in the name of performance.

 

By John Paul Mueller

 

Most developers know that security is a major issue and that their livelihoods will depend on getting the security of their next application right. There's no lack of pressure to make applications perform well and still protect data. Unfortunately, performance often wins out, and in some cases, time is the factor that causes both security and performance to fall by the wayside. The tips in this article should help you make your application secure, without giving up much in the way of performance and without missing those important deadlines.

 

1. Don't Assume the Environment is Consistent or Static

Many developers assume that the programming environment is going to remain consistent forever. They deal in a static world of absolute facts that never change. However, the world is far from consistent or static. Some of the worst programming nightmares in history have occurred because the programming environment changes. Consider the Y2K debacle or the rise of crackers on the Internet. The real-world environment in which your application executes is constantly changing - in many cases, not for the better.

 

Consider the case of a company that employed a number of programmers to maintain an application. After some number of changes, the application did work better. However, because the various programmers assumed that the company would distribute the application in a particular environment and that every programmer was conscientious about security concerns, the application developed a number of security holes, many of which proved difficult to find. The company finally ended up hiring a consultant to look through the code and close the security holes.

 

The best way to avoid assumptions about the application environment is to make the application flexible. A flexible application responds to changing threats. For example, you can overcome many threats through the simple check of incoming data type, content, and length. In addition, restricting user input to just the information you need reduces the chance of errant data or a cracker attack. Such applications are flexible because they don't assume anything about the user or the environment in which the application will operate.

 

The moment you choose to focus on a particular security area because it's currently a threat, an application becomes vulnerable to attack. Looking for anything that tends to make an application less reliable, usable, or robust also tends to point out potential security flaws, even if an issue isn't a security problem now. For example, it always pays to validate the data going into a buffer because such checks make the application more reliable. However, such checks take time to write, and they reduce application performance because they use up processing cycles, so many developers didn't add them in the past. Today, the buffer overflow is one of the most pressing security problems vendors face.

 

2. Perform Range Checks

Determining that the data you receive is in the correct range is important. For example, if you need a number between 1 and 5, don't accept the number 6. This simple check extends to other data types, as well. Make sure you check the length of strings and don't accept something that is too long. You can perform some range checks by using simple tag attributes, such as the maxlength attribute for the tag. Other range checks can rely on code behind; you can check the data once it arrives at the server. However, the simplest range check is using one or more validators to ensure the integrity of the data the client sends.

 

A validator is a special .NET Framework control. To use it, you fill in two or more properties depending on the control type. Figure 1 shows a password dialog box that includes two validators for each of the text boxes. In this case, the first validator ensures the associated text box contains some text, and the second validator ensures the input is in the correct format. The ControlToValidate property associates the validator with the TextBox control, and the ErrorMessage property controls the error message you see in the figure. When a user fails to provide the correct 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 and fill out the appropriate properties. In general, you'll rely on four validators for your applications: RangeValidator, CompareValidator, RegularExpressionValidator, and RequiredFieldValidator.

 


Figure 1. Forms commonly contain multiple validators for each data-entry field. Each validator performs a specific check on that field to ensure it meets all input requirements and provides the user with field-specific help should an error occur.

 

The RangeValidator ensures the input in a control falls within a range of values. The MinimumValue and MaximumValue properties contain the limit of values the user can input. You'll use 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 of values or is of the wrong type, the control will display an error message.

 

The CompareValidator accepts two controls as input and then compares the value of each control. If the two controls don't match the condition you specify, the CompareValidator displays an error message. The name of the second control appears in the ControlToCompare property. The Operator property defines the comparison between the two controls. For example, if you choose the GreaterThan option, the value of the control listed in the ControlToValidate property must be greater than the value of the control listed in the ControlToCompare property. A Type property ensures the second control contains data of the correct type, but this is almost superfluous because the two controls won't compare if their types don't match.

 

The RegularExpressionValidator uses an expression to validate the content or format of the input. You'll find that the Microsoft help topics tend to focus on the format of the expression, as do the built-in expressions. However, the ValidationExpression property can contain any regular expression that describes the content you expect. For example, using a ValidationExpression property value of [ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]{3,15} tells the RegularExpressionValidator to accept only characters as input and to restrict the length of the input from three to 15 characters. Given the extreme flexibility of regular expressions, you can define any kind of range check you might need.

 

The RequiredFieldValidator is the easiest validator to understand. If the target control is blank, the validator displays an error message. Some developers will use an asterisk in place of the error 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 can provide example input for each field.

 

3. Analyze the Input

Regular expressions and other forms of analysis can be your best friend for some types of input. For example, I regularly visit Web sites that don't know whether I've input a telephone number or a credit-card number or some text that came to mind. Using a regular expression to test the data won't verify that it's correct, but at least it will verify that the kind of data is correct. Ensuring the data is in the correct form automatically reduces the chance that someone will send a virus or hack into your system by sending incorrect input.

 

You can accomplish this task using a RegularExpressionValidator. This control comes with a number of preset validations, as shown in Figure 2. It's also possible to create your own regular expressions.

 


Figure 2. The Regular Expression Editor contains a number of preset expressions you can use to check input. These examples also help you create your own regular expressions without spending a lot of time in the documentation.

 

Sometimes, you can't use a RegularExpressionValidator because the data is too complex for a simple statement 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 as part numbers. All of these kinds of input require local validation at the server. In this case, you can combine a number of checks in a function, such as the one shown in Figure 3.

 

private void btnTest_Click(

   object sender, System.EventArgs e)

{

   Regex PartCheck;   // Checks the part number format.

 

   // Create the regular expression.

   PartCheck =

      new Regex("[ABCDEFGHIJKLMNOPQRSTUVWXYZ]{3}-" +

                "[0123456789]{3}-[0123456789]{5}");

 

   // Check the input length.

   if (txtPartNum.Text.Length != 13)

   {

      lblResponse.ForeColor = System.Drawing.Color.Red;

      lblResponse.Text = "Use input form of XYZ-123-01234";

      return;

   }

 

   // Verify the text is in 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 has actually 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 in database

   

   // Perform other checks.

}

Figure 3. Use customized checks when a RegularExpressionValidator won't work for data validation. At a minimum, always check the input length and data type (or complex type).

 

The code shown in this listing begins by creating a Regex object. This object checks the form of the data input. In this case, the input consists of three letters, a dash, three numbers, a dash, and five more numbers.

 

At this point, the code performs a number of data checks. The first check is the length of the input. The reason you perform this check first is to reduce the risk of someone sending a script as 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 is relatively safe, but you haven't verified that it's actually good data. You'd need to check for the part number in the database or perform some other validation checks. The idea is to move from general checks to specific checks in order to prove the data is good (or at least as close as possible) before you use it for anything. Although this series of checks borders on the paranoid, you'll find that paranoid developers tend to have fewer security problems.

 

4. Perform Double Checks

You can limit the length of input using a simple HTML tag attribute. For example, take a look at the following:

 

 

This tag limits the size of the input to 40 characters. Any legitimate user will type 40 characters, discover there's no room for additional text, and leave the input as is. A cracker, on the other hand, might be tempted to exceed the 40-character limit.

 

The double check comes at the server with your ASP.NET code. If you suddenly discover that this 40-character input has 500 characters in it, you know someone has been tampering with the input. This activity is suspicious, and you need to check it out. No, this check won't prevent someone from attempting to input bad data, but it will alert you to the tampering. This kind of security check is a lot more helpful than you might think, because it looks for the unexpected.

 

5. Use Discrete Inputs

One of the main problems with most Web forms is that the user faces a blank text-input field that could contain anything. 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 of choice, but users often see text-input fields as problematic. Even if the developer provides a sample value and the user honestly is trying to use the form correctly, there's still a lot of room for interpretation. Users ask questions such as, "Do I need to type a number or a word in this field?"

 

In many cases, you can replace the general input with discrete input. For example, a list box contains specific allowable values that make the choices clear to the user. The form is more accessible because the user doesn't have to figure anything out; all the correct answers appear directly on the form. A user also can fill the form out faster.

 

From a security perspective, the programmer 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 were expecting. Discrete inputs require less validation and less code, so using discrete inputs is better for the developer and the user.

 

6. Stem the Tide of Leaking Information

Most employees at your company aren't trustworthy. It's not that they're spies for someone else or disgruntled; it's that they simply don't think about the consequences of placing certain information on a Web site or understand computers well enough to know they're letting the information out. Consider the case of a company that handles sensitive information. An employee let some of this information out by mistake - an honest error. Because the application assumed the employee understood the consequences of making this information available, the employee lost a job, and the company lost clients. If the application developer had assumed that employees are human and make mistakes, the data might not have appeared on the Web site. The employee might have gotten a reprimand for not following procedure, but he or she still would have a job, and the company wouldn't have lost clients.

 

Many developers discount the problem of data leaks because they think they have followed every security procedure. However, data leaks occur not because of a programming problem but because of an implementation problem. The reason you should care about the data output of your application is that people make mistakes, and your application can help protect them from errors.

 

Displaying any information at all on a Web site causes an information leak for your organization. In another case, an employee posted a list of contact names and e-mail addresses on a Web site without considering the effects of providing such a list. A cracker used the list to perform social engineering at the company. By using the information provided on the Web site, the cracker was able to obtain user-name and password information from several employees. The employees felt the cracker must have a legitimate need for the information because he or she knew so much about the people working there. Of course, because the purpose of a Web site is to provide information to viewers, your application wouldn't be worth very much without at least a small information leak. In addition, some kinds of information leaks are actually good for your organization, such as when you announce a new product.

 

Part of your responsibility as a developer is ensuring your design doesn't leak private data. For example, make sure your template contains only the information it needs to contain. Of course, the template still has to provide functionality, so you can't leave it blank. Make sure that you don't confuse protected company information with essential user information. You still have to make the Web page accessible, and it still requires good help to avoid confusing the user.

 

Data-leak protection can take other forms. For example, your organization might rely on a database to supply information for Web pages. At some point, your program will need to access the required database record, extract the data, apply it to a page, and output the result. This setup provides a perfect opportunity to scan for forbidden words - such as the name of a new secret project - during the extraction phase, using regular expression parsing. Obviously, this form of data-leak protection won't fulfill every need; a smart user that is determined to leak information will find some other words to do it. This limitation is the same reason spam filters are only partially successful.

 

There is one last thing to consider about data leaks: All of this monitoring does have an effect on application performance. Consequently, you might have to weigh the security and privacy benefits of output monitoring against the performance penalty of doing it. Some types of businesses, such as financial institutions, probably should monitor everything and make up the performance loss in other ways. However, if your organization works mainly with public data, you probably can use less monitoring to achieve better performance.

 

7. Use Performance Counters to Your Advantage

Some people assume that performance counters are only useful for monitoring how fast a system performs a given task. However, performance counters can be the best security investment you ever made, because they help you monitor the condition of your application. You can use a performance counter to count anything, including the number of errors your application experiences because of incorrect password entries. Too many password errors could signal a cracker's attempt to infiltrate your system. Performance counters also can measure application traffic and make it possible to discover trends in application usage. Analysis helps you learn about problems, such as a Distributed Denial of Service (DDOS) attack, before they really become problems.

 

The reason analysis is important is that you can prevent only so many kinds of security problems. Given enough time, any good cracker can overwhelm the fixed fortifications you provide for your application, which means you must include some form of monitoring, also. Detecting security breaches is at least as important as preventing them in the first place.

 

Unfortunately, developers didn't always have access to the simple monitoring technique demonstrated in this tip. Creating a performance counter in the Win32 API environment is nothing short of a nightmare, so many developers avoid performing this task. However, the .NET environment makes the task of creating a custom performance counter relatively easy. All you need to do is create a custom performance counter that tracks the number of bad requests to your application. When the number reaches a specific threshold, you know that something is wrong - it could be a DDOS attack. Figure 4 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 Windows Form Designer support

   InitializeComponent();

 

   // Create the collection and error counter.

   CounterCollect = new CounterCreationDataCollection();

   ErrorCount =

      new CounterCreationData(

         "Error_Count",

          "Contains the application error count.",

         PerformanceCounterType.RateOfCountsPerSecond32);

 

   // Add the counter to the collection.

   CounterCollect.Add(ErrorCount);

 

   // Create a custom counter category.

   PerformanceCounterCategory.Create(

      Application.ProductName,

      "Error checking counter.",

      CounterCollect);

 

   // Create the performance counter.

   PerfCount = new PerformanceCounter(

      Application.ProductName,

      "Error_Count",

      false);

 

   // Create the random number generator.

   EventVal = new Random(DateTime.Now.Second);

}

 

private void btnTest_Click(

   object sender, System.EventArgs e)

{

   // Set the number of events 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 random error 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. Performance counters can do more than just help you track performance. You also can use them for error trapping and making security checks. Using performance counters adds automation to the security process.

 

I used a standard Windows application for the example to make it easier to test, but the same technique works fine in a component or any other kind of application you want to create. The constructor begins by initializing the counter. This example shows a very simple counter. The code begins by creating a counter and a counter collection to hold it. It then uses the PerformanceCounterCategory.Create method to add the collection to Windows. The collection can contain any number of counters, and you can create counters of various types. You can read more about the various performance counter types at http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemDiagnosticsPerformanceCounterTypeClassTopic.asp.

 

Creating a performance counter for Windows doesn't provide access to it. The code creates a new PerformanceCounter object 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 able to generate new data for the counter.

 

The one element in the constructor that you don't need to create, in most cases, is the Random object, EventVal. This object generates random errors in this example. In a production environment, you'd only want to know about the real errors.

 

The btnTest_Click method sets the timer interval and starts the timer. Once the timer starts, it generates ticks at the rate specified by the Interval property. Each call to the EventGen_Tick event handler is the result of a tick. The code checks the current random number value. If it's less than or equal to 1, the code increments the performance counter using the PerfCount.Increment method.

 

Because this is a temporary counter, you should destroy it before you exit the application. The frmMain_Closing method accomplishes this task. All you need to supply is the name of the counter category.

The sample code in this article is available for download.

 

John Mueller is a freelance author, technical editor, and consultant who has produced 60 books and more than 200 articles to date. The topics range from networking to artificial intelligence and from database management to heads-down programming. His most recent book is.NET Development Security Solutions (Sybex, ISBN 0-7821-4266-4). His technical-editing skills have helped at least 32 authors refine the content of their manuscripts. You can reach John on the Internet at mailto:[email protected], and his Web site at http://www.mwt.net/~jmueller/.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish