Chapter 10 – Building Secure ASP.NET Pages and Controls

 

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

patterns & practices Developer Center

Improving Web Application Security: Threats and Countermeasures

J.D. Meier, Alex Mackman, Michael Dunner, Srinath Vasireddy, Ray Escamilla and Anandha Murukan
Microsoft Corporation

Published: June 2003

Last Revised: January 2006

Applies to:

  • ASP.NET version 1.1

See the "patterns & practices Security Guidance for Applications Index" for links to additional security resources.

See the Landing Page Landing Page for the starting point and a complete overview of Improving Web Application Security: Threats and Countermeasures.

Summary: This chapter shows you a variety of implementation techniques to improve the security of your ASP.NET Web pages and controls. The chapter shows you how to counter the threat of cross-site scripting, session hijacking, cookie replay, parameter manipulation and other top threats. It also describes the techniques you should use to develop a secure forms authentication solution.

Contents

In This Chapter
Overview
How to Use This Chapter
Threats and Countermeasures
Design Considerations
Input Validation
Cross-Site Scripting
Authentication
Authorization
Impersonation
Sensitive Data
Session Management
Parameter Manipulation
Exception Management
Auditing and Logging
Summary
Additional Resources

In This Chapter

  • Preventing cross-site scripting (XSS) attacks
  • Partitioning sites into public and restricted areas
  • Preventing session hijacking and cookie replay attacks
  • Developing secure Forms authentication
  • Preventing rich exception details from reaching the client
  • Validating input in Web pages and controls

Overview

Web pages and controls are in your application's front line of defense and can be subject to intense probing by attackers who are intent on compromising your application's security. These attacks are often ultimately aimed at back-end systems and data stores.

Input data validation should be a top consideration when you build Web pages because the majority of top application-level attacks rely on vulnerabilities in this area. One of the most prevalent attacks today is cross-site scripting (XSS), which is more of an attack on your application's users than on the application itself, but it exploits server-side application vulnerabilities all the same. The results can be devastating and can lead to information disclosure, identity spoofing, and elevation of privilege.

How to Use This Chapter

To build secure Web pages and controls, you need to follow the correct programming practices that this chapter discusses. In addition to secure programming practices, use the corresponding chapters in this guide to help you build secure ASP.NET pages and controls.

  • Implement the steps in Chapter 19, "Securing Your ASP.NET Application and Web Services." The chapter helps you configure ASP.NET appropriately with secure settings in Machine.config and Web.config.
  • Use the accompanying checklist in the checklist section of this guide. "Checklist: Securing ASP.NET" ties the recommendations made in this chapter and in Chapter 19 together. Make sure you implement the guidance.
  • Understand the threats and attacks that are specific to ASP.NET pages and controls. Apply countermeasures according to guidelines in this chapter.
  • Read Chapter 4, "Design Guidelines for Secure Web Applications." Many of the recommendations in this chapter (Chapter 10) are based on the design guidelines discussed in Chapter 4.
  • Architects should use the "Design Considerations" section of this chapter.
  • Developers should apply the guidance in this chapter to their development process.
  • Learn the controls from a programmatic standpoint to fine-tune ASP.NET pages and controls security.
  • Use the application vulnerability categories as a means to tackle common problems. Application vulnerability categories provide a useful way to approach and group problems.

Threats and Countermeasures

Most Web application attacks require that malicious input is passed within HTTP requests. The general goal is either to coerce the application into performing unauthorized operations or to disrupt its normal operation. This is why thorough input validation is an essential countermeasure to many attacks and should be made a top priority when you develop ASP.NET Web pages and controls. Top threats include:

  • Code injection
  • Session hijacking
  • Identity spoofing
  • Parameter manipulation
  • Networkeavesdropping
  • Information disclosure

Figure 10.1 highlights the most common threats to Web applications.

Ff648635.f10thcm01(en-us,PandP.10).gif

Figure 10.1

Common threats to ASP.NET Web pages and controls

Code Injection

Code injection occurs when an attacker causes arbitrary code to run using your application's security context. The risk increases if your application runs using a privileged account.

Attacks

There are various types of code injection attacks. These include:

  • Cross-site scripting. Malicious script is sent to a Web application as input. It is echoed back to a user's browser, where it is executed.
  • Buffer overflows. The type safe verification of managed code reduces the risk significantly, but your application is still vulnerable, especially where it calls unmanaged code. Buffer overflows can allow an attacker to execute arbitrary code inside your Web application process, using its security context.
  • SQL injection. This attack targets vulnerable data access code. The attacker sends SQL input that alters the intended query or executes completely new queries in the database. Forms authentication logon pages are common targets because the username and password are used to query the user store.

Vulnerabilities

Vulnerabilities that can lead to successful code injection attacks include:

  • Weak or missing input validation or reliance on client-side input validation
  • Including unvalidated input in HTML output
  • Dynamically constructing SQL statements that do not use typed parameters
  • Use of over-privileged process accounts and database logins

Countermeasures

The following countermeasures can be used to prevent code injection:

  • Validate input so that an attacker cannot inject script code or cause buffer overflows.
  • Encode all output that includes input. This prevents potentially malicious script tags from being interpreted as code by the client's browser.
  • Use stored procedures that accept parameters to prevent malicious SQL input from being treated as executable statements by the database.
  • Use least privileged process and impersonation accounts. This mitigates risk and reduces the damage that can be done if an attacker manages to execute code using the application's security context.

Session Hijacking

Session hijacking occurs when the attacker captures an authentication token and takes control of another user's session. Authentication tokens are often stored in cookies or in URLs. If the attacker captures the authentication token, he can transmit it to the application along with a request. The application associates the request with the legitimate user's session, which allows the attacker to gain access to the restricted areas of the application that require authenticated access. The attacker then assumes the identity and privileges of the legitimate user.

Vulnerabilities

Common vulnerabilities that make your Web pages and controls susceptible to session hijacking include:

  • Unprotected session identifiers in URLs
  • Mixing personalization cookies with authentication cookies
  • Authentication cookies passed over unencrypted links

Attacks

Session hijacking attacks include:

  • Cookie replay. The attacker captures the authentication cookie either by using network monitoring software or by some other means, for example, by exploiting an XSS scripting vulnerability.
  • Query string manipulation. A malicious user changes the session identifier that is clearly visible in the URL query string.

Countermeasures

You can employ the following countermeasures to prevent session hijacking:

  • Separate personalization and authentication cookies.
  • Only transmit authentication cookies over HTTPS connections.
  • Do not pass session identifiers that represent authenticated users in query strings.
  • Re-authenticate the user before critical operations, such as order placement, money transfers, and so on, are performed.

Identity Spoofing

Identity spoofing occurs when a malicious user assumes the identity of a legitimate user so that he can access the application.

Vulnerabilities

Common vulnerabilities that make your Web pages and controls susceptible to an identity spoofing attack include:

  • Authentication credentials that are passed over unencrypted links
  • Authentication cookies that are passed over unencrypted links
  • Weak passwords and policies
  • Weak credential storage in the user store

Attacks

Identity spoofing attacks include:

  • Cookie replay. The attacker steals the authentication cookie either by using network monitoring software or by using an XSS attack. The attacker then sends the cookie to the application to gain spoofed access.
  • Brute force password attacks. The attacker repeatedly tries username and password combinations.
  • Dictionary attacks. In this automated form of a brute force password attack, every word in a dictionary is tried as a password.

Countermeasures

You can employ the following countermeasures to prevent identity spoofing:

  • Only transmit authentication credentials and cookies over HTTPS connections.
  • Enforce strong passwords. Regular expressions can be used to ensure that user-supplied passwords meet suitable complexity requirements.
  • Store password verifiers in the database. Store non-reversible password hashes combined with a random salt value to mitigate the risk of dictionary attacks.

For more information about storing password hashes and other secrets in the database, see Chapter 14, "Building Secure Data Access."

Parameter Manipulation

Parameters are the items of data that are passed from the client to the server over the network. They include form fields, query strings, view state, cookies, and HTTP headers. If sensitive data or data that is used to make security decisions on the server are passed using unprotected parameters, your application is potentially vulnerable to information disclosure or unauthorized access.

Vulnerabilities

Vulnerabilities that can lead to parameter manipulation include:

  • Using hidden form fields or query strings that contain sensitive data
  • Passing cookies that contain security-sensitive data over unencrypted connections

Attacks

Parameter manipulation attacks include:

  • Cookie replay attacks. The attacker captures and alters a cookie and then replays it to the application. This can easily lead to identity spoofing and elevation or privileges if the cookie contains data that is used for authentication or authorization on the server.
  • Manipulation of hidden form fields. These fields contain data used for security decisions on the server.
  • Manipulation of query string parameters.

Countermeasures

You can employ the following countermeasures to prevent parameter manipulation:

  • Do not rely on client-side state management options. Avoid using any of the client-side state management options such as view state, cookies, query strings or hidden form fields to store sensitive data.
  • Store sensitive data on the server. Use a session token to associate the user's session with sensitive data items that are maintained on the server.
  • Use a message authentication code (MAC) to protect the session token. Pair this with authentication, authorization, and business logic on the server to ensure that the token is not being replayed.

Network Eavesdropping

Network eavesdropping involves using network monitoring software to trace packets of data sent between browser and Web server. This can lead to the disclosure of application-specific confidential data, the retrieval of logon credentials, or the capture of authentication cookies.

Vulnerabilities

Vulnerabilities that can lead to successful network eavesdropping include:

  • Lack of encryption when sending sensitive data
  • Sending authentication cookies over unencrypted channels

Attacks

Network eavesdropping attacks are performed by using packet sniffing tools that are placed on the network to capture traffic.

Countermeasures

To counter network eavesdropping, use Secure Sockets Layer (SSL) to provide an encrypted communication channel between browser and Web server. It is imperative that SSL is used whenever credentials, authentication tickets, or sensitive application data are sent over the network.

Information Disclosure

Information disclosure occurs when an attacker probes your Web pages looking for ways to cause exception conditions. This can be a fruitful exercise for the attacker because exception details, which often are returned as HTML and displayed in the browser, can divulge extremely useful information, such as stack traces that contain database connection strings, database names, database schema information, SQL statements, and operating system and platform versions.

Vulnerabilities

Vulnerabilities that lead to information disclosure include:

  • Weak exception handling
  • Letting raw exception details propagate to the client

Attacks

There are many attacks that can result in information disclosure. These include:

  • Buffer overflows.
  • Sending deliberately malformed input.

Countermeasures

To prevent information disclosure:

  • Use structured exception handling.
  • Return generic error pages to the client.
  • Use default redirect pages that contain generic and harmless error messages.

Design Considerations

Before you develop Web pages and controls, there are a number of important issues that you should consider at design time. The following are the key considerations:

  • Use server-side input validation.
  • Partition your Web site.
  • Consider the identity that is used for resource access.
  • Protect credentials and authentication tickets.
  • Fail securely.
  • Consider authorization granularity.
  • Place Web controls and user controls in separate assemblies.
  • Place resource access code in a separate assembly.

Use Server-Side Input Validation

At design time, identify all the various sources of user input that your Web pages and controls process. This includes form fields, query strings, and cookies received from the Web user, as well as data from back-end data sources. The Web user clearly lives outside your application's trust boundary, so all of the input from that source must be validated at the server. Unless you can absolutely trust the data retrieved from back-end data sources, that data should also be validated and sanitized before it is sent to the client. Make sure your solution does not rely on client-side validation because this is easily bypassed.

Partition Your Web Site

Your Web site design should clearly differentiate between publicly accessible areas and restricted areas that require authenticated access. Use separate subdirectories beneath your application's virtual root directory to maintain restricted pages, such as checkout functionality in a classic e-commerce Web site that requires authenticated access and transmits sensitive data such as credit card numbers. Separate subdirectories allow you to apply additional security (for example, by requiring SSL) without incurring SSL performance overhead across the entire site. It also allows you to mitigate the risk of session hijacking by restricting the transmission of authentication cookies to HTTPS connections. Figure 10.2 shows a typical partitioning.

Ff648635.f10thcm02(en-us,PandP.10).gif

Figure 10.2

A Web site partitioned into public and secure areas

Note that in Figure 10.2, the restricted subfolder is configured in Internet Information Services (IIS) to require SSL access. The first <authorization> element in Web.config allows all users to access the public area, while the second element prevents unauthenticated users from accessing the contents of the secured subfolder and forces a login.

For more information about restricting authentication cookies so that they are passed only over HTTPS connections and about how to navigate between restricted and non-restricted pages, see "Use Absolute URLs for Navigation" in the "Authentication" section of this chapter.

Consider the Identity That Is Used for Resource Access

By default, ASP.NET applications do not impersonate, and the least privileged ASP.NET process account is used to run ASP.NET Web applications and for resource access. The default is the recommended configuration. There are several situations in which you may want to use a different Windows security context for resource access. These include:

  • Hosting multiple applications on the same server

    You can use IIS to configure each application to use a separate anonymous Internet user account and then enable impersonation. Each application then has a distinct identity for resource access. For more information about this approach, see Chapter 20, "Hosting Multiple ASP.NET Applications."

    Note   If your applications run on Windows Server 2003 with IIS 6.0, you can use application pools and configure each application to run in its own worker process that provides process-level isolation. By default, all applications run in a default application pool. With application pools, you can configure each process to run using a separate identity and, as a result, you do not need to use impersonation. For more information, see "How To: Improve Security When Hosting Multiple Applications in ASP.NET 2.0."

  • Accessing a remote resource with specific authentication requirements

    If you need to access a specific remote resource (for example, a file share) and have been given a particular Windows account to use, you can use configure this account as the anonymous Web user account for your application. Then you can use programmatic impersonation prior to accessing the specific remote resource. For more information, see "Impersonation" later in this chapter.

Protect Credentials and Authentication Tickets

Your design should factor in how to protect credentials and authentication tickets. Credentials need to be secured if they are passed across the network and while they are in persistent stores such as configuration files. Authentication tickets must be secured over the network because they are vulnerable to hijacking. Encryption provides a solution. SSL or IPSec can be used to protect credentials and tickets over the network and DPAPI provides a good solution for encrypting credentials in configuration files.

Fail Securely

If your application fails with an unrecoverable exception condition, make sure that it fails securely and does not leave the system wide open. Make sure the exception details that are valuable to a malicious user are not allowed to propagate to the client and that generic error pages are returned instead. Plan to handle errors using structured exception handling, rather than relying on method error codes.

Consider Authorization Granularity

Consider the authorization granularity that you use in the authenticated parts of your site. If you have configured a directory to require authentication, should all users have equal access to the pages in that directory? If necessary, you can apply different authorization rules for separate pages based on the identity, or more commonly, the role membership of the caller, by using multiple <authorization> elements within separate <location> elements.

For example, two pages in the same directory can have different <allow> and <deny> elements in Web.config.

Place Web Controls and User Controls in Separate Assemblies

When Web controls and user controls are put in their own assemblies, you can configure security for each assembly independently by using code access security policy. This provides additional flexibility for the administrator and it means that you are not forced to grant extended permissions to all controls just to satisfy the requirements of a single control.

Place Resource Access Code in a Separate Assembly

Use separate assemblies and call them from your page classes rather than embedding resource access code in your page class event handlers. This provides greater flexibility for code access security policy and is particularly important for building partial-trust Web applications. For more information, see Chapter 9, "Using Code Access Security with ASP.NET."

Input Validation

If you make unfounded assumptions about the type, length, format, or range of input, your application is unlikely to be robust. Input validation can become a security issue if an attacker discovers that you have made unfounded assumptions. The attacker can then supply carefully crafted input that compromises your application. The misplaced trust of user input is one of the most common and devastating vulnerabilities in Web applications.

Constrain, Then Sanitize

Start by constraining input and check for known good data by validating for type, length, format, and range. Sometimes you also need to sanitize input and make potentially malicious input safe. For example, if your application supports free-format input fields, such as comment fields, you might want to permit certain "safe" HTML elements, such as <b> and <i>, and strip out any other HTML elements. The following table summarizes the options that are available for constraining and sanitizing data:

Table 10.1   Options for Constraining and Sanitizing Data

Requirement Options
Type checks .NET Framework type system. Parse string data, convert to a strong type, and then handle FormatExceptions.

Regular expressions. Use ASP.NET RegularExpressionValidator control or Regex class.

Length checks Regular expressions

String.Length property

Format checks Regular expressions for pattern matching

.NET Framework type system

Range checks ASP.NET RangeValidator control (supports currency, date, integer, double, and string data)

Typed data comparisons

Regular Expressions

You can use regular expressions to restrict the range of valid characters, to strip unwanted characters, and to perform length and format checks. You can constrain input format by defining patterns that the input must match. ASP.NET provides the RegularExpressionValidator control and the Regex class is available from the System.Text.RegularExpressions namespace.

If you use the validator controls, validation succeeds if the control is empty. For mandatory fields, use a RequiredFieldValidator. Also, the regular expression validation implementation is slightly different on the client and server. On the client, the regular expression syntax of Microsoft JScript® development software is used. On the server, System.Text.RegularExpressions.Regex syntax is used. Since JScript regular expression syntax is a subset of System.Text.RegularExpressions.Regex syntax, it is recommended that JScript regular expression syntax be used to yield the same results on both the client and the server.

For more information about the full range of ASP.NET validator controls, refer to the .NET Framework documentation.

RegularExpressionValidator Control

To validate Web form field input, you can use the RegularExpressionValidator control. Drag the control onto a Web form and set its ValidationExpression, ControlToValidate, and ErrorMessage properties.

You can set the validation expression using the properties window in Microsoft Visual Studio .NET or you can set the property dynamically in the Page_Load event handler. The latter approach allows you to group together all of the regular expressions for all controls on the page.

Regex Class

If you use regular HTML controls with no **** property (which rules out using the RegularExpressionValidator control), or you need to validate input from other sources such as query strings or cookies, you can use the Regex class either in your page class or in a validation helper method, possibly in a separate assembly. Some examples are shown later in this section.

Regular Expression Comments

Regular expressions are much easier to understand if you use the following syntax and comment each component of the expression using #. To enable comments, you must also specify RegexOptions.IgnorePatternWhitespace, which means that non-escaped white space is ignored.

Regex regex = new Regex(@"
                        ^           # anchor at the start
                       (?=.*\d)     # must contain at least one digit
                       (?=.*[a-z])  # must contain one lowercase
                       (?=.*[A-Z])  # must contain one uppercase
                       .{8,10}      # From 8 to 10 characters in length
                       $            # anchor at the end", 
                       RegexOptions.IgnorePatternWhitespace);

String Fields

To validate string fields, such as names, addresses, tax identification numbers, and so on, use regular expressions to do the following:

  • Constrain the acceptable range of input characters.
  • Apply formatting rules. For example, pattern-based fields, such as tax identification numbers, ZIP codes, or postal codes, require specific patterns of input characters.
  • Check lengths.

Names

The following example shows a RegularExpressionValidator control that has been used to validate a name field.

<form id="WebForm" method="post" >
  <asp:TextBox id="txtName" ></asp:TextBox>
  <asp:RegularExpressionValidator id="nameRegex" 
        ControlToValidate="txtName" 
        ValidationExpression="[a-zA-Z'.'-'\s]{1,40}" 
        ErrorMessage="Invalid name">
  </asp:regularexpressionvalidator>
</form>

The preceding validation expression constrains the input name field to alphabetic characters (lowercase and uppercase), the single apostrophe for names such as O'Dell, and the dot character. In addition, the field length is constrained to 40 characters.

Social Security Numbers

The following example shows the HTML code that is generated for a RegularExpressionValidator control that has been used to validate a U.S. social security number form field:

<form id="WebForm" method="post" >
  <asp:TextBox id="txtSSN" ></asp:TextBox>
  <asp:RegularExpressionValidator id="ssnRegex"  
       ErrorMessage="Invalid social security number" 
       ValidationExpression="\d{3}-\d{2}-\d{4}" 
       ControlToValidate="txtSSN">
  </asp:RegularExpressionValidator>
</form>

The preceding validation expression is one of the standard expressions that Visual Studio .NET provides. It validates the format of the supplied input field as well as its type and length. The input must consist of three numeric digits followed by a dash, then two digits followed by a dash, and then four digits.

If you are not using server controls (which rule out the validator controls), or you need to validate input from sources other than form fields, you can use the System.Text.RegularExpression.Regex class in your method code. The following example shows how to validate the same field by using the static Regex.IsMatch method directly in the page class rather than using a validator control:

if (!Regex.IsMatch(txtSSN.Text, @"\d{3}-\d{2}-\d{4}"))
{
  // Invalid Social Security Number
}

Date Fields

Input fields that have an equivalent .NET Framework type can be type checked by the.NET Framework type system. For example, to validate a date, you can convert the input value to a variable of type System.DateTime and handle any resulting format exceptions if the input data is not compatible, as follows.

try
{
  DateTime dt = DateTime.Parse(txtDate.Text).Date;
}
// If the type conversion fails, a FormatException is thrown
catch( FormatException ex )
{
  // Return invalid date message to caller
}

In addition to format and type checks, you might need to perform a range check on a date field. This is easily performed using the DateTime variable, as follows.

// Exception handling is omitted for brevity
  DateTime dt = DateTime.Parse(txtDate.Text).Date;
  // The date must be today or earlier
  if ( dt > DateTime.Now.Date )
    throw new ArgumentException("Date must be in the past");

Numeric Fields

If you need to validate numeric data, for example, an age, perform type checks using the int type. To convert string input to integer form you can use Int32.Parse or Convert.ToIn32, and then handle any FormatException that occurs with an invalid data type, as follows:

try
{
  int i = Int32.Parse(txtAge.Text);
  . . .
}
catch( FormatException)
{
 . . .
}

Range Checks

Sometimes you need to validate that input data falls within a predetermined range. The following code uses an ASP.NET RangeValidator control to constrain input to whole numbers between 0 and 255. This example also uses the RequiredFieldValidator. Without the RequiredFieldValidator, the other validator controls accept blank input.

<form id="WebForm3" method="post" >
  <asp:TextBox id="txtNumber" ></asp:TextBox>
  <asp:RequiredFieldValidator 
       id="rangeRegex" 
        
       ErrorMessage="Please enter a number between 0 and 255" 
       ControlToValidate="txtNumber"
       style="LEFT: 10px; POSITION: absolute; TOP: 47px" >
  </asp:RequiredFieldValidator>
  <asp:RangeValidator 
       id="RangeValidator1" 
        
       ErrorMessage="Please enter a number between 0 and 255" 
       ControlToValidate="TextBox1" 
       Type="Integer" 
       MinimumValue="0"
       MaximumValue="255" 
       style="LEFT: 10px; POSITION: absolute; TOP: 47px" >
  </asp:RangeValidator>
  <asp:Button id="Button1" style="LEFT: 10px; POSITION: absolute; TOP: 100px" 
               Text="Button"></asp:Button>
</form>

The following example shows how to validate range by using custom code:

try
{
  // The conversion will raise an exception if not valid.
  int i = Convert.ToInt32(sInput);
  if ((0 <= i && i <= 255) == true)
  {
    // data is valid, use the number
  }
}
catch( FormatException )
{
  . . .
}

Sanitizing Input

Sanitizing is about making potentially malicious data safe. It can be helpful when the range of allowable input cannot guarantee that the input is safe. This might include stripping a null from the end of a user-supplied string or escaping values so they are treated as literals. If you need to sanitize input and convert or strip specific input characters, use Regex.Replace.

Note   Use this approach for defense in depth. Always start by constraining input to the set of known "good" values.

The following code strips out a range of potentially unsafe characters, including < > \ " ' % ; ( ) &.

private string SanitizeInput(string input)
{
  Regex badCharReplace = new Regex(@"([<>""'%;()&])");
  string goodChars = badCharReplace.Replace(input, "");
  return goodChars;
}

For more information about sanitizing free format input fields, such as comment fields, see "Sanitizing Free Format Input" under "Cross-Site Scripting," later in this chapter.

Validating HTML Controls

If you do not use server controls — that is, controls with the **** attribute — and instead use regular HTML controls, you cannot use the ASP.NET validator controls. Instead, you can validate your Web pages' content by using regular expressions in the Page_Load event handler, as follows.

using System.Text.RegularExpressions;
. . .
private void Page_Load(object sender, System.EventArgs e)
{
  // Note that IsPostBack applies only for 
  // server forms (with )
  if ( Request.RequestType == "POST" ) // non-server forms
  {
    // Validate the supplied email address
    if( !Regex.Match(Request.Form["email"], 
         @"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
         RegexOptions.None).Success) 
    {
      // Invalid email address
    }

    // Validate the supplied name
    if ( !RegEx.Match(Request.Form["name"],
         @"[A-Za-z'\- ]",
         RegexOptions.None).Success) 
    {
      // Invalid name
    }
  }
}

Validating Input Used for Data Access

If you are generating dynamic SQL queries based on user input, a SQL injection attack can inject malicious SQL commands that can be executed by the database. In a typical Web-based data access scenario, the following defense in depth strategy can be used:

  • Use regular expressions to constrain input within your page class.
  • Sanitize or reject input. For defense in depth, you can choose to use a helper method to strip null characters or other known bad characters.
  • Use parameterized stored procedures for data access to ensure that type and length checks are performed on the data used in SQL queries.

For more information about using parameters for data access and about writing secure data access code, see Chapter 14, "Building Secure Data Access."

Validating Input Used For File I/O

In general, you should avoid writing code that accepts file input or path input from the caller. Instead, use fixed file names and locations when reading and writing data. This ensures that your code cannot be coerced into accessing arbitrary files. It also ensures that your code is not vulnerable to canonicalization bugs.

If you do need to accept input file names, there are two main challenges. First, is the resulting file path and name a valid file system name? Second, is the path valid in the context of your application? For example, is it beneath the application's virtual directory root?

To canonicalize the file name, use System.IO.Path.GetFullPath. To check that the file path is valid in the context of your application, you can use .NET code access security to grant the precise FileIOPermission to your code so that is able to access only files from specific directories. For more information, see the "File I/O" sections in Chapter 7, "Building Secure Assemblies" and Chapter 8, "Code Access Security in Practice."

Using MapPath

If you use MapPath to map a supplied virtual path to a physical path on the server, use the overload of Request.MapPath that accepts a bool parameter so that you can prevent cross application mapping, as follows:

try
{
  string mappedPath = Request.MapPath( inputPath.Text, 
                                       Request.ApplicationPath, false);
}
catch (HttpException)
{
  // Cross-application mapping attempted
}

The final false parameter prevents cross-application mapping. This means that a user cannot successfully supply a path that contains ".." to traverse outside of your application's virtual directory hierarchy. Any attempt to do so results in an exception of type HttpException.

Note   Server controls can use the Control.MapPathSecure method to read files. This method requires that the calling code is granted full trust by code access security policy; otherwise an HttpException is thrown. For more information, see Control.MapPathSecure in the .NET Framework SDK documentation.

Common Regular Expressions

Visual Studio .NET provides a set of useful regular expressions. To access them, add a RegularExpresssionValidator control to a Web form and click the ellipsis button in the control's Expression property field. The following table shows several additional useful expressions for commonly used Web page fields.

Table 10.2   Useful Regular Expression Fields


Field

Expression
Format Samples
Description
Name [a-zA-Z'`-´\s]{1,40} John Doe

O'Dell

Validates a name. Allows up to 40 uppercase and lowercase characters and a few special characters that are common to some names. This list can be tailored.
Numbers ^\D?(\d{3})\D?\D?(\d{3})
\D?(\d{4})$
(425)-555-0123

425-555-0123

425 555 0123

Validates a U.S. phone number.
E-mail \w+([-+.]\w+)*@\w+
([-.]\w+)*\.\w+([-.]\w+)*
someone@
example.com
Validates an e-mail address.
URL ^(http|https|ftp)\://[a-zA-Z
0-9\-\.]+\.[a-zA-Z]{2,3}
(:[a-zA-Z0-9]*)?/?([a-zA-Z
0-9\-\._\?\,\'/\\\+&%\$#
\=~])*$
  Validates a URL.
Zip Code ^(\d{5}-\d{4}|\d{5}|\d{9})
$|^([a-zA-Z]\d[a-zA-Z]
\d[a-zA-Z]\d)$
  Validates a U.S. ZIP code allowing 5 or 9 digits.
Password ^(?=.*\d)(?=.*[a-z])(?=.*
[A-Z]).{8,10}$
  Validates a strong password. Must be between 8 and 10 characters. Must contain a combination of uppercase, lowercase, and numeric digits, with no special characters.
Non- negative integers \d+ 0

986

Validates for integers greater than zero.
Currency (non- negative) "\d+(\.\d\d)?"   Validates for a positive currency amount. Requires two digits after the decimal point.
Currency (positive or negative) "(-)?\d+(\.\d\d)?"   Validates for a positive or negative currency amount. Requires two digits after the decimal point.

Cross-Site Scripting

XSS attacks exploit vulnerabilities in Web page validation by injecting client-side script code. This code is subsequently sent back to an unsuspecting user and executed by the browser. Because the browser downloads the script code from a trusted site, the browser has no way of identifying that the code is not legitimate, and Internet Explorer security zones provide no defense. XSS attacks also work over HTTP or HTTPS (SSL) connections. One of the most serious exploits occurs when an attacker writes script to retrieve the authentication cookie that provides access to the trusted site and posts it to a Web address known to the attacker. This allows the attacker to spoof the legitimate user's identity and gain illicit access to the Web site.

Use the following countermeasures to prevent XSS attacks:

  • Validate input
  • Encode output

Validate Input

Validate any input that is received from outside your application's trust boundary for type, length, format, and range using the various techniques described previously in this chapter.

Encode Output

If you write text output to a Web page and you do not know with absolute certainty that the text does not contain HTML special characters (such as <, >, and &), then make sure to pre-process it using the HttpUtility.HtmlEncode method. Do this even if the text came from user input, a database, or a local file. Similarly, use HttpUtility.UrlEncode to encode URL strings.

The HtmlEncode method replaces characters that have special meaning in HTML to HTML variables that represent those characters. For example, < is replaced with &lt and " is replaced with &quot. Encoded data does not cause the browser to execute code. Instead, the data is rendered as harmless HTML.

Response.Write(HttpUtility.HtmlEncode(Request.Form["name"]));

Data-Bound Controls

Data bound controls are web controls that are bindable to data components through a public inherited 'DataSource' property. To mention a few, you will find DataGrid, ListBox and DropDownList to be used very often. Not all data bound controls perform encoding when displaying data retrieved from a bound data component; thus, it will be your responsibility to perform encoding on non-trusted data components in order to prevent XSS attacks. For example, a data component cannot be trusted in a scenario where different applications share a single database. If an attacker has the ability to insert malicious XSS code into the database (by abusing a vulnerability in one of the applications, for instance) all applications using non-encoding web controls bound to it, will turn vulnerable. Only avoid encoding if you can be certain that the output from the data component will always be valid. 

Examples of data bound controls that do not perform encoding are DataGrid, DataList, RadioButtonList and CheckBoxList. Performing encoding for a data bound control may vary depending on each specific control. For example, for a DataGrid control, you have the following options:

  • Turn all columns into templates and manually use HtmlEncode()/UrlEncode() on each call to DataBinder.Eval
  • Override one of its DataBinding methods, such as OnDatabinding or OnItemDataBound and perform encoding on its items. The following example illustrates how to override the OnItemDataBound method of a DataGrid control in order to encode its items by either using HtmlEncode() or UrlEncode() when required:
...
[DefaultProperty("Text"),
  ToolboxData("<{0}:DataGrid runat=server></{0}:DataGrid>")]
 
public class DataGrid : System.Web.UI.WebControls.DataGrid
{
    /// <summary>
    /// The ItemDataBound event is raised after an item is data bound to the DataGrid
    /// control. This event provides you with the last opportunity to access the data
    /// item before it is displayed on the client. After this event is raised, the data
    /// item is nulled out and no longer available. - .NET Framework Class Library
    /// </summary>
    /// <param name="e"></param>
    protected override void OnItemDataBound(DataGridItemEventArgs e)
    {
      base.OnItemDataBound (e);
 
      switch (e.Item.ItemType)
      {
        case ListItemType.Item:
        case ListItemType.AlternatingItem:
        case ListItemType.EditItem:
        case ListItemType.SelectedItem:
        case ListItemType.Footer:
        case ListItemType.Header:
        case ListItemType.Pager:  
          // even though not all of these ListItemTypes are data bound,
          // perform HtmlEncode or UrlEncode on each control. If there are
          // no controls, we perform HtmlEncode on any available text.
          // Also, don't let &nbsp;'s be encoded.
          TableCellCollection cCells = e.Item.Cells;
          foreach (TableCell tc in cCells)
          {
            if (tc.Controls.Count > 0)
            {
              foreach (Control ctrl in tc.Controls)
              {
                
                // don't perform HtmlEncode on URL's
                if (ctrl is HyperLink)
                {
                  HyperLink hLnk = (HyperLink)ctrl;
 
                  if (hLnk.Text.Length > 0)
                    hLnk.Text = HttpUtility.HtmlEncode(hLnk.Text);
                  if (hLnk.href.Length > 0)
                    hLnk.href = HttpUtility.UrlEncode(hLnk.href);
                }
                else if (ctrl is LinkButton)
                {
                  LinkButton lButton = (LinkButton)ctrl;
 
                  if (lButton.Text.Length > 0)
                    lButton.Text = HttpUtility.HtmlEncode(lButton.Text);
                }
                else if (ctrl is Button)
                {
                  Button cButton = (Button)ctrl;
 
                  if (cButton.Text.Length > 0)
                    cButton.Text = HttpUtility.HtmlEncode(cButton.Text);
                }
              }
            } 
            else 
            {              
              // there are no controls in the table cell
              // HTMLEncode any available text
              if (tc.Text.Length > 0) 
              {
                if ("&nbsp;" != tc.Text) 
                  tc.Text = HttpUtility.HtmlEncode(tc.Text);
              }
            }
          }
          break;
         default:
          break;
      }
     }
   }
...

Sanitizing Free Format Input

If your Web page includes a free-format text box, such as a "comments" field, in which you want to permit certain safe HTML elements such as <b> and <i>, you can handle this safely by first pre-processing with HtmlEncode, and then selectively removing the encoding on the permitted elements, as follows:

StringBuilder sb = new StringBuilder( HttpUtility.HtmlEncode(userInput) ) ;
sb.Replace("&lt;b&gt;", "<b>");
sb.Replace("&lt;/b&gt;", "</b>");
sb.Replace("&lt;i&gt;", "<i>");
sb.Replace("&lt;/i&gt;", "</i>");
Response.Write(sb.ToString());

Defense in Depth Countermeasures

In addition to the techniques discussed earlier, use the following countermeasures for defense in depth to prevent XSS:

  • Set the correct character encoding.
  • Use the ASP.NET validateRequest option.
  • Install URLScan on your Web server.
  • Use the HttpOnly cookie option.
  • Use the <frame> security attribute.
  • Use the innerText property.

Set the Correct Character Encoding

To successfully restrict what data is valid for your Web pages, it is important to limit the ways in which the input data can be represented. This prevents malicious users from using canonicalization and multi-byte escape sequences to trick your input validation routines.

ASP.NET allows you to specify the character set at the page level or at the application level by using the <globalization> element in Web.config. Both approaches are shown below using the ISO-8859-1 character encoding, which is the default in early versions of HTML and HTTP.

To set the character encoding at the page level, use the <meta> element or the ResponseEncoding page-level attribute as follows:

<meta http-equiv="Content Type" 
      content="text/html; charset=ISO-8859-1" />

OR

<% @ Page ResponseEncoding="ISO-8859-1" %>

To set the character encoding in Web.config, use the following configuration:

<configuration>
   <system.web>
      <globalization 
         requestEncoding="ISO-8859-1"
         responseEncoding="ISO-8859-1"/>
   </system.web>
</configuration>

Validating Unicode Characters

Use the following code to validate Unicode characters in a page:

using System.Text.RegularExpressions;
. . .
private void Page_Load(object sender, System.EventArgs e)
{
  // Name must contain between 1 and 40 alphanumeric characters
  // together with (optionally) special characters '`' for names such
  // as D'Angelo
  if (!Regex.IsMatch(Request.Form["name"], @"^[\p{L}\p{Zs}\p{Lu}\p{Ll}\']{1,40}$"))
    throw new ArgumentException("Invalid name parameter");
  // Use individual regular expressions to validate other parameters
  . . .
}

The following explains the regular expression shown in the preceding code:

  • {<name>} specifies a named Unicode character class.
  • \p{<name>} matches any character in the named character class specified by {<name>}.
  • {L} performs a left-to-right match.
  • {Lu} performs a match of uppercase.
  • {Ll} performs a match of lowercase.
  • {Zs} matches separator and space.
  • {1,40} means no less that 1 and no more than 40 characters.
  • {Mn} matches mark and non-spacing characters.
  • {Zs} matches separator and space.
  • * specifies zero or more matches.
  • $ means stop looking at this position.

Use the ASP.NET validateRequest Option

The validateRequest attribute is set to true by default on the <pages> element in Machine.config. It instructs ASP.NET to examine all data received from the browser for potentially malicious input, for example, input that contains <script> elements. ASP.NET examines input received from HTML form fields, cookies, and query strings. .NET Framework version 1.0 does not provide any equivalent functionality, but the IIS URLScan Internet Server Application Programming Interface (ISAPI) filter can perform a similar job. You can also

<% @ Page validateRequest="True" %>

Install URLScan on Your Web Server

URLScan is an ISAPI filter that is installed when you run the IISLockdown tool. This helps mitigate the threat of XSS attacks by rejecting potentially malicious input. For more information about IISLockdown and URLScan, see Chapter 16, "Securing Your Web Server."

Note   IIS 6.0 on Windows Server 2003 has functionality equivalent to URLScan built in.

Internet Explorer 6 Service Pack 1 supports a new HttpOnly cookie attribute, which prevents client-side script from accessing the cookie from the document.cookie property. Instead, an empty string is returned. The cookie is still sent to the server whenever the user browses to a Web site in the current domain.

Note   Web browsers that do not support the HttpOnly cookie attribute either ignore the cookie or ignore the attribute, which means it is still subject to XSS attacks.

The System.Net.Cookie class does not currently support an HttpOnly property. To add an HttpOnly attribute to the cookie, you need to use an ISAPI filter, or if you want a managed code solution, add the following code to your application's Application_EndRequest event handler in Global.asax:

protected void Application_EndRequest(Object sender, EventArgs e) 
{
  string authCookie = FormsAuthentication.FormsCookieName;
  foreach (string sCookie in Response.Cookies) 
  {
    // Just set the HttpOnly attribute on the Forms authentication cookie
    // Skip this check to set the attribute on all cookies in the collection
    if (sCookie.Equals(authCookie))
    { 
      // Force HttpOnly to be added to the cookie header
      Response.Cookies[sCookie].Path += ";HttpOnly";
    }
  }
}

Note   ASP.NET 2.0 provides an HttpOnly property on the HttpCookie class, which you can directly set to true.

Use the <frame> Security Attribute

Internet Explorer 6 and later supports a new security attribute on the <frame> and <iframe> elements. You can use the security attribute to apply the user's Restricted Sites Internet Explorer security zone settings to an individual frame or iframe. By default, the Restricted Sites zone doesn't support script execution. If you use the security attribute, it must currently be set to "restricted" as shown below:

<frame security="restricted" src="http://www.somesite.com/somepage.htm"></frame>

Use the innerText Property

If you create a page with untrusted input, use the innerText property instead of innerHTML. The innerText property renders content safe and ensures that script is not executed.

Authentication

Weak authentication increases the identity spoofing threat. If a user's logon credentials fall into the wrong hands, an attacker can spoof the user's identity and gain access to the application. The attacker shares all of the user's privileges in the application. Credentials must be protected as they are passed over the network and while they are persistent, for example, in the application's user store. The authentication cookie that represents an authenticated identity to the application after the initial logon must also be protected to mitigate the risk of session hijacking and cookie replay attacks.

Forms Authentication

The threat of session hijacking and cookie replay attacks is particularly significant for applications that use Forms authentication. You must take particular care when querying the database using the user-supplied credentials to ensure that you are not vulnerable to SQL injection. Additionally, to prevent identity spoofing, you should make sure that the user store is secure and that strong passwords are enforced.

The following fragment shows a "secure" Forms authentication configuration in Web.config:

  <forms loginUrl="Restricted\login.aspx"  Login page in an SSL protected folder
         protection="All"                  Privacy and integrity
         requireSSL="true"                 Prevents cookie being sent over http
         timeout="10"                      Limited session lifetime
         name="AppNameCookie"              Unique per-application name
         path="/FormsAuth"                    and path
         slidingExpiration="true" >        Sliding session lifetime
  </forms>

The following recommendations help you build a secure Forms authentication solution:

  • Partition your Web site.
  • Secure restricted pages with SSL.
  • Use URL Authorization.
  • Secure the authentication cookie.
  • Use absolute URLs for navigation.
  • Use secure credential management.

Partition Your Web Site

In your site design, make sure that secure pages that require authenticated access are placed in a subdirectory that is separate from the anonymously accessible pages. Figure 10.3 shows a typical arrangement in the Visual Studio .NET Solution Explorer window. Notice how the Forms login page is placed along with other restricted pages in a separate subdirectory.

Ff648635.f10thcm03(en-us,PandP.10).gif

Figure 10.3

Subdirectory for restricted pages that require authenticated access

Note   If you are using Server.Transfer in your application to transfer from an anonymous page to a secure page, the .NET Framework bypasses authentication checks, so code that uses Server.Transfer should be verified to ensure that it does not transfer to a secure directory.

Secure Restricted Pages with SSL

To ensure that SSL is used to protect the logon credentials that are posted from the login form, and that the authentication cookie passed on subsequent requests to restricted pages, configure the secure folders in IIS to require SSL. This sets the AccessSSL=true attribute for the folder in the IIS metabase. Requests for pages in the secured folders will only be successful if https is used on the request URL.

For SSL, you must have a server certificate installed on the Web server. For more information, see "How To: Setup SSL on a Web Server" in the "How To" section of "Microsoft patterns & practices Volume I, Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication" at https://msdn.microsoft.com/en-us/library/aa302383.aspx.

Use URL Authorization

To allow anonymous access to public pages, use the following <authorization> element.

<system.web>
  <!-- The virtual directory root folder contains general pages.
       Unauthenticated users can view them and they do not need 
       to be secured with SSL. -->
  <authorization>
    <allow users="*" />
  </authorization>
</system.web>

Use the following <authorization> element inside a <location> element in Web.config to deny access to unauthenticated users and force a redirect to the login page that is specified on the <forms> element:

<!-- The restricted folder is for authenticated and SSL access only. -->
<location path="Secure" >
  <system.web>
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</location>

To prevent session hijacking and cookie replay attacks, secure the cookie by making sure that it is only passed over SSL connections using the HTTPS protocol. For additional risk mitigation, encrypt the cookie before sending it to the client and limit the period for which the cookie is valid. To secure the authentication cookie:

  • Restrict the authentication cookie to HTTPS connections.
  • Encrypt the cookie and check its integrity.
  • Limit cookie lifetime.
  • Consider using a fixed expiration period.
  • Do not persist authentication cookies.
  • Keep authentication and personalization cookies separate.
  • Use distinct cookie names and paths.

Cookies support a "secure" property that determines whether or not browsers should send the cookie back to the server. With the secure property set, the cookie is sent by the browser only to a secure page that is requested using an HTTPS URL.

If you are using .NET Framework version 1.1, set the secure property by using requireSSL="true" on the <forms> element as follows:

<forms loginUrl="Secure\Login.aspx"
       requireSSL="true" . . . />

If you are using .NET Framework version 1.0, set the secure property manually in the Application_EndRequest event handler in Global.asax using the following code:

protected void Application_EndRequest(Object sender, EventArgs e) 
{
  string authCookie = FormsAuthentication.FormsCookieName;

  foreach (string sCookie in Response.Cookies) 
  {
    if (sCookie.Equals(authCookie))
    { 
      // Set the cookie to be secure. Browsers will send the cookie
      // only to pages requested with https
      Response.Cookies[sCookie].Secure = true;
    }
  }
}

Encrypt the cookie and check its integrity, even if you are using SSL. This prevents an attacker from viewing or modifying the cookie if he or she manages to steal it through a XSS exploit. In this event, the attacker can still use the cookie to gain access to your application. The best way to mitigate this risk is to implement the appropriate countermeasures to prevent XSS attacks (described under "Cross-Site Scripting" earlier in this chapter), and limit the cookie lifetime as described in the next recommendation.

To provide privacy and integrity for the cookie, set the protection attribute on the <forms> element as follows:

<forms protection="All"    Privacy and integrity

Limit the cookie lifetime to reduce the time window in which an attacker can use a captured cookie to gain spoofed access to your application.

<forms timeout="10"                Reduced cookie lifetime (10 minutes)

Consider Using a Fixed Expiration Period

Consider setting slidingExpiration="false" on the <forms> element to fix the cookie expiration, rather than resetting the expiration period after each Web request. This is particularly important if you are not using SSL to protect the cookie.

Do not Persist Authentication Cookies

Do not persist authentication cookies because they are stored in the user's profile and can be stolen if an attacker gets physical access to the user's computer. You can specify a non-persistent cookie when you create the FormsAuthenticationTicket as follows:

FormsAuthenticationTicket ticket = 
               new FormsAuthenticationTicket(
                        1,                           // version
                        Context.User.Identity.Name,  // user name
                        DateTime.Now,                // issue time
                        DateTime.Now.AddMinutes(15), // expires every 15 mins
                        false,                       // do not persist the cookie
                        roleStr );                   // user roles

Keep Authentication and Personalization Cookies Separate

Keep personalization cookies that contain user-specific preferences and non-sensitive data separate from authentication cookies. A stolen personalization cookie might not represent a security threat, whereas an attacker can use a stolen authentication cookie to gain access to your application.

Use unique name and path attribute values on the <forms> element. By ensuring unique names, you prevent possible problems that can occur when hosting multiple applications on the same server. For example, if you don't use distinct names, it is possible for a user who is authenticated in one application to make a request to another application without being redirected to that application's logon page.

For more information, see Microsoft Knowledge Base articles 313116, "PRB: Forms Authentication Requests Are Not Directed to loginUrl Page," and 310415, "PRB: Mobile Forms Authentication and Different Web Applications."

Use Absolute URLs for Navigation

Navigating between the public and restricted areas of your site (that is, between HTTP and HTTPS pages) is an issue because a redirect always uses the protocol (HTTPS or HTTP) of the current page, not the target page.

Once a user logs on and browses pages in a directory that is secured with SSL, relative links such as "..\publicpage.aspx" or redirects to HTTP pages result in the pages being served using the https protocol, which incurs an unnecessary performance overhead. To avoid this, use absolute links such as "https://servername/appname/publicpage.aspx" when redirecting from an HTTPS page to an HTTP page.

Similarly, when you redirect to a secure page (for example, the login page) from a public area of your site, you must use an absolute HTTPS path, such as "https://servername/appname/secure/login.aspx", rather than a relative path, such as restricted/login.aspx. For example, if your Web page provides a logon button, use the following code to redirect to the secure login page.

private void btnLogon_Click( object sender, System.EventArgs e )
{
  // Form an absolute path using the server name and v-dir name
  string serverName = 
         HttpUtility.UrlEncode(Request.ServerVariables["SERVER_NAME"]);
  string vdirName = Request.ApplicationPath;
  Response.Redirect("https://" + serverName + vdirName + 
                    "/Restricted/Login.aspx");
}

Use Secure Credential Management

Identity spoofing is one of the most common authentication-related threats to your application. Identity spoofing occurs when an attacker gains access to the application under the guise of another user. One way to do this is to hijack the session cookie, but if you have secured the authentication cookie as described earlier, the risk is significantly reduced. In addition, you must build secure credential management and a secure user store to mitigate the risk posed by brute force password attacks, dictionary attacks, and SQL injection attacks.

The following recommendations help you reduce risk:

  • Use one-way hashes for passwords.
  • Use strong passwords.
  • Prevent SQL injection.

Use One-Way Hashes for Passwords

If your user store is SQL Server, store one-way password digests (hash values) with an added random salt value. The added salt value mitigates the risk of brute force password cracking attempts, for example, dictionary attacks. The digest approach means you never actually store passwords. Instead, you retrieve the password from the user and validate it by recalculating the digest and comparing it with the stored value.

Use Strong Passwords

Use regular expressions to ensure that user passwords conform to strong password guidelines. The following regular expression can be used to ensure that passwords are between 8 and 10 characters in length and contain a mixture of uppercase, lowercase, numeric, and special characters. This further mitigates the dictionary attack risk.

private bool IsStrongPassword( string password )
{
return Regex.IsMatch(password, @"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$");
}

Note   When using the Membership feature in ASP.NET 2.0 for Forms authentication, you can configure the membership providers for password strengths. For more information on using the Membership feature in ASP.NET 2.0, see "How To: Use Membership in ASP.NET 2.0."

Prevent SQL Injection

Forms authentication is especially prone to vulnerabilities that lead to SQL injection attacks because of the way that the user-supplied logon credentials are used to query the database. To mitigate the risk:

  • Thoroughly validate the supplied credentials. Use regular expressions to make sure they do not include SQL characters.
  • Use parameterized stored procedures to access the user store database.
  • Use a login to the database that is restricted and least privileged.

For more information about preventing SQL injection, see Chapter 14, "Building Secure Data Access."

Authorization

You can use authorization to control access to directories, individual Web pages, page classes, and methods. If required, you can also include authorization logic in your method code. When you build authorization into your Web pages and controls, consider the following recommendations:

  • Use URL authorization for page and directory access control.
  • Use File authorization with Windows authentication.
  • Use principal demands on classes and methods.
  • Use explicit role checks for fine-grained authorization.

Use URL Authorization for Page and Directory Access Control

For page-level and directory-level access control, use URL authorization, which is configured by the <authorization> element. To restrict access to specific files or directories, place the <authorization> element inside a <location> element.

For more information, see "Authorization" in Chapter 19, "Securing Your ASP.NET Application and Web Services."

Use File Authorization with Windows Authentication

If ASP.NET is configured for Windows authentication, the FileAuthorizationModule checks all requests for ASP.NET file types. This includes ASP.NET page files (.aspx), user controls (.ascx), and any other file type mapped by IIS to the ASP.NET ISAPI filter.

To configure the FileAuthorizationModule, set the appropriate Windows access control lists (ACLs) on the ASP.NET files.

Use Principal Demands on Classes and Methods

Principal permission demands allow you to make authorization decisions based on the identity and role membership of the caller. The caller's identity and role membership is maintained by the principal object that is associated with the current Web request (accessed through HttpContext.User). Use declarative security attributes to provide access controls on classes and methods, as follows:

// Declarative syntax
[PrincipalPermission(SecurityAction.Demand, 
          Role=@"DomainName\WindowsGroup")]
public void SomeRestrictedMethod()
{
}

Use Explicit Role Checks for Fine-Grained Authorization

Declarative security checks prevent a user from accessing a class or calling a specific method. If you need additional logic inside a method to make authorization decisions, either use imperative principal permission demands or explicit role checks using IPrincipal.IsInRole. These approaches allow you to use additional runtime variables to fine tune the authorization decision. The following example shows the use of an imperative principal permission demand:

// Imperative syntax
public void SomeRestrictedMethod()
{
  // Only callers that are members of the specified Windows group
  // are allowed access
  PrincipalPermission permCheck = new PrincipalPermission(
                                         null, @"DomainName\WindowsGroup");
  permCheck.Demand();
  // Some restricted operations (omitted)
}

The following example shows the use of IPrincipal.IsInRole:

public void TransferMoney( string fromAccount,
                           string toAccount, double amount)
{
  // Extract the authenticated user from the current HTTP context.
  // The User variable is equivalent to HttpContext.Current.User if you 
  // are using an .aspx page (or .asmx)
  WindowsPrincipal authenticatedUser = User as WindowsPrincipal;
  if (null != authenticatedUser)
  {
    // Note:  To retrieve the authenticated user's username, use the 
    // following line of code
    // string username = authenticatedUser.Identity.Name;
    // If the amount exceeds a threshold value, manager approval is required
    if (amount > thresholdValue) {
      // Perform a role check
      if (authenticatedUser.IsInRole(@"DomainName\Manager") )
      {
        // OK to proceed with transfer
      }
      else
      {
         throw new Exception("Unauthorized funds transfer");
      }
    }
    else
    {
      . . .
    }
  }
}

You may also have a method that allows callers from several different roles. However, you might want to subsequently call a different method, which is not possible with declarative security.

Note   If you are running ASP.NET 2.0, you can use the Role Manager feature for fine-grained authorization. For more information on using the Role Manager feature in ASP.NET 2.0, see "How To: Use Role Manager in ASP.NET 2.0."

Impersonation

By default, ASP.NET applications usually do not impersonate the original caller for design, implementation, and scalability reasons. For example, impersonating prevents effective middle-tier connection pooling, which can have a severe impact on application scalability.

In certain scenarios, you might require impersonation (for example, if you require an alternate identity (non-process identity) for resource access). In hosting environments, multiple anonymous identities are often used as a form of application isolation. For example, if your application uses Forms or Passport authentication, you can impersonate the anonymous Internet user account associated by IIS with your application's virtual directory.

You can impersonate the original caller, which might be the anonymous Internet user account or a fixed identity. To impersonate the original caller (the IIS authenticated identity), use the following configuration:

<identity impersonate="true" />

To impersonate a fixed identity, use additional userName and password attributes on the <identity> element, but make sure you use Aspnet_setreg.exe to store encrypted credentials in the registry. For more information about encrypting credentials in configuration files and about Aspnet_setreg.exe, see Chapter 19, "Securing Your ASP.NET Application and Web Services."

Note   In ASP.NET 2.0, you can use the Protected Configuration feature to encrypt the configuration section in the configuration file. For more information on using the Protected Configuration feature in ASP.NET 2.0, see "How To: Encrypt Configuration Sections in ASP.NET 2.0 Using DPAPI " and "How To: Encrypt Configuration Sections in ASP.NET 2.0 Using RSA".

Using Programmatic Impersonation

If you do not want to impersonate an account for the entire request, you can use programmatic impersonation to impersonate for a portion of the request. For example, you want to use the ASP.NET process account to access you application's primary resources and downstream database, but you need to access an alternate resource, such as another remote database or a remote file share, using an alternate identity.

To do this, use IIS to configure the anonymous user account as the trusted alternate identity. Then use the following code to create an impersonation token using the anonymous account only while you execute your remote resource access code:

HttpContext context = HttpContext.Current;
// Get the service provider from the context
IServiceProvider iServiceProvider = context as IServiceProvider;
//Get a Type which represents an HttpContext
Type httpWorkerRequestType = typeof(HttpWorkerRequest);
// Get the HttpWorkerRequest service from the service provider
// NOTE:  When trying to get a HttpWorkerRequest type from the HttpContext
// unmanaged code permission is demanded.
HttpWorkerRequest httpWorkerRequest = 
     iServiceProvider.GetService(httpWorkerRequestType) as HttpWorkerRequest;
// Get the token passed by IIS
IntPtr ptrUserToken = httpWorkerRequest.GetUserToken();
// Create a WindowsIdentity from the token
WindowsIdentity winIdentity = new WindowsIdentity(ptrUserToken);
// Impersonate the user
Response.Write("Before impersonation: " + 
                WindowsIdentity.GetCurrent().Name + "<br>");
WindowsImpersonationContext impContext = winIdentity.Impersonate();
Response.Write("Impersonating: " + WindowsIdentity.GetCurrent().Name + "<br>");
// Place resource access code here

// Stop impersonating
impContext.Undo();
Response.Write( "After Impersonating: " + 
                WindowsIdentity.GetCurrent().Name + "<br>");

Note   This approach assumes Forms or Passport authentication where your application's virtual directory is configured in IIS to support anonymous access.

If you use this code, use the following <identity> configuration:

<identity impersonate="false" />

Note   The code demands the unmanaged code permission SecurityPermission(SecurityPermissionFlag.UnmanagedCode), which is granted only to fully trusted Web applications.

Sensitive Data

Sensitive data includes application configuration details (for example, connection strings and service account credentials) and application-specific data (for example, customer credit card numbers). The following recommendations help to reduce risk when you handle sensitive data:

  • Do not pass sensitive data from page to page.
  • Avoid plain text passwords in configuration files.
  • Use DPAPI to avoid key management.
  • Protect sensitive data over the wire.
  • Do not cache sensitive data.

Do Not Pass Sensitive Data from Page to Page

Avoid using any of the client-side state management options, such as view state, cookies, query strings, or hidden form-field variables, to store sensitive data. The data can be tampered with and viewed in clear text. Use server-side state management options, such as a SQL Server database for secure data exchange.

Avoid Plaintext Passwords in Configuration Files

The <processModel>, <sessionState>, and <identity> elements in Machine.config and Web.config have userName and password attributes. Do not store these in plaintext. Store encrypted credentials in the registry using the Aspnet_setreg.exe tool.

For more information about encrypting credentials in configuration files and about Aspnet_setreg.exe, see Chapter 19, "Securing Your ASP.NET Application and Web Services."

Note   In ASP.NET 2.0, you can use the Protected Configuration feature to encrypt various configuration sections for protecting sensitive data. For more information on using the Protected Configuration feature in ASP.NET 2.0, see "How To: Encrypt Configuration Sections in ASP.NET 2.0 Using DPAPI" and "How To: Encrypt Configuration Sections in ASP.NET 2.0 Using RSA"

Use DPAPI to Avoid Key Management

DPAPI is ideally suited for encrypting secrets such as connection strings and service account credentials. If your pages need to use this type of configuration data, use DPAPI to avoid the key management problem.

For more information see "Cryptography" in Chapter 7, "Building Secure Assemblies."

Protect Sensitive Data over the Wire

It is important to consider how sensitive data such as credentials and application-specific data are transmitted over a network link. If you need to send sensitive data between the Web server and a browser, you should consider using SSL. If you need to protect server-to-server communication — for example, between your Web server and database — you should consider using either IPSec or SSL.

Do Not Cache Sensitive Data

If your page contains data that is sensitive, such as a password, credit card number, or account status, the page should not be cached. Output caching is off by default.

Session Management

There are two main factors that you should consider to provide secure session management. First, ensure that the session token cannot be used to gain access to sensitive pages where secure operations are performed or to gain access to sensitive items of data. Second, if the session data contains sensitive items, you must secure the session data, including the session store.

The following two types of tokens are associated with session management:

  • The session token. This token is generated automatically by ASP.NET if session state is enabled, for example, by setting the mode attribute of the <sessionState> element to InProc, SQLServer, or StateServer.

    Note   You can override the <sessionState> configuration and disable or enable session state on a per-page basis using the EnableSessionState attribute on the @Page tag.

  • The authentication token. This is generated by authentication mechanisms, such as Forms authentication, to track an authenticated user's session. With a valid authentication token, a user can gain access to the restricted parts of your Web site.

The following recommendations help you build secure session management:

  • Require authentication for sensitive pages.
  • Do not rely on client-side state management options.
  • Do not mix session tokens and authentication tokens.
  • Use SSL effectively.
  • Secure the session data.

Require Authentication for Sensitive Pages

Make sure that you authenticate users before allowing them access to the sensitive and restricted parts of your site. If you use secure authentication and protect the authentication token with SSL, then a user's session is secure because an attacker cannot hijack and replay a session token. The attacker would need the authentication token to get past the authorization gates.

For more information about how to secure the authentication token for Forms authentication, see "Forms Authentication" earlier in this chapter.

Do Not Rely on Client-Side State Management Options

Avoid using any of the client-side state management options, such as view state, cookies, query strings, or hidden form fields, to store sensitive data. The information can be tampered with or seen in clear text. Use server-side state management options, for example, a database, to store sensitive data.

Do Not Mix Session Tokens and Authentication Tokens

Secure session management requires that you do not mix the two types of tokens. First, secure the authentication token to make sure an attacker cannot capture it and use it to gain access to the restricted areas of your application. Second, build your application in such a way that the session token alone cannot be used to gain access to sensitive pages or data. The session token should be used only for personalization purposes or to maintain the user state across multiple HTTP requests. Without authentication, do not maintain sensitive items of the user state.

Use SSL Effectively

If your site has secure areas and public access areas, you must protect the secure authenticated areas with SSL. When a user moves back and forth between secure and public areas, the ASP.NET-generated session cookie (or URL if you have enabled cookie-less session state) moves with them in plaintext, but the authentication cookie is never passed over unencrypted HTTP connections as long as the Secure cookie property is set.

Note   You can set the Secure property for a Forms authentication cookie by setting requireSSL="true" on the <forms> element.

An attacker is able to obtain a session cookie passed over an unencrypted HTTP session, but if you have designed your site correctly and place restricted pages and resources in a separate and secure directory, the attacker can use it to access only to the non-secure, public access pages. In this event, there is no security threat because these pages do not perform sensitive operations. Once the attacker tries to replay the session token to a secured page, because there is no authentication token, the attacker is redirected to the application's login page.

For more information about using the Secure cookie property and how to build secure Forms authentication solutions, see "Forms Authentication" earlier in this chapter.

Secure the Session Data

If the session data on the server contains sensitive items, the data and the store needs to be secured. ASP.NET supports several session state modes. For information about how to secure ASP.NET session state, see "Session State" in Chapter 19, "Securing Your ASP.NET Application and Web Services."

Parameter Manipulation

Parameters, such as those found in form fields, query strings, view state, and cookies, can be manipulated by attackers who usually intend to gain access to restricted pages or trick the application into performing an unauthorized operation.

For example, if an attacker knows that you are using a weak authentication token scheme such as a guessable number within a cookie, the attacker can construct a cookie with another number and make a request as a different (possibly privileged) user.

The following recommendations help you avoid parameter manipulation vulnerabilities:

  • Avoid storing sensitive data in view state.
  • Protect view state with MACs.
  • Use Page.ViewStateUserKey to counter one-click attacks.
  • Maintain sensitive data on the server.
  • Validate input parameters.

Avoid Storing Sensitive Data in View State

Avoid storing sensitive data in view state. View state is not designed for storage of sensitive data, and protecting the data with encryption adds to performance overhead. If you need to manage sensitive data, maintain it on the server; for example, in session state.

If your view state does contain sensitive data, you should consider protecting it against eavesdropping.

Protect View State with MACs

If your Web pages or controls use view state to maintain state across HTTP requests, ensure that the view state is encrypted and integrity checked through the use of MACs. By default, the enableViewStateMac attribute on the <pages> element in Machine.config ensures that view state is protected with a MAC.

<pages buffer="true" enableSessionState="true"
       enableViewState="true" enableViewStateMac="true" 
       autoEventWireup="true" validateRequest="true"/>

Note   The @Page directive also supports the preceding attributes, which allows you to customize settings on a per-page basis.

While you can override whether or not view state is enabled on a per-control, page, or application basis, make sure enableViewStateMac is set to true whenever you use view state.

Server.Transfer

If your application uses Server.Transfer as shown below and sets the optional second Boolean parameter to true so that the QueryString and Form collections are preserved, then the command will fail if enableViewStateMac is set to true.

Server.Transfer("page2.aspx", true);

If you omit the second parameter or set it to false, then an error will not occur. If you want to preserve the QueryString and Form collections instead of setting the enableViewStateMac to false, follow the workaround discussed in Microsoft Knowledge Base article 316920, "PRB: View State Is Invalid" Error Message When You Use Server.Transfer."

For information about configuring the <machineKey> element for view state encryption and integrity checks, see Chapter 19, "Securing Your ASP.NET Application and Web Services."

Use Page.ViewStateUserKey to Counter One-Click Attacks

If you authenticate your callers and use view state, set the Page.ViewStateUserKey property in the Page_Init event handler to prevent one-click attacks. A one-click attack occurs when an attacker creates a prefilled Web page (.htm or .aspx) with view state. The view state can be generated from a page that the attacker had previously created, for example, a shopping cart page with 100 items. The attacker lures an unsuspecting user into browsing to the page, then causes the page to be sent to the server where the view state is valid. The server has no way of knowing that the view state originated from the attacker. View state validation and MACs do not counter this attack because the view state is valid and the page is executed under the security context of the user.

Set the Page.ViewStateUserKey property to a suitably unique value as a countermeasure to the one-click attack. The value should be unique to each user and is typically a user name or identifier. When the attacker creates the view state, the ViewStateUserKey property is initialized to his or her name. When the user submits the page to the server, it is initialized with the attacker's name. As a result, the view state MAC check fails and an exception condition is generated.

Note   This attack is usually not an issue for anonymously browsed pages (where no user name is available) because this type of page should make no sensitive transactions.

Maintain Sensitive Data on the Server

Do not trust input parameters, especially when they are used to make security decisions at the server. Also, do not use clear text parameters for any form of sensitive data. Instead, store sensitive data on the server in a session store and use a session token to reference the items in the store. Make sure that the user is authenticated securely and that the authentication token is secured properly. For more information, see "Session Management" earlier in this chapter.

Validate Input Parameters

Validate all input parameters that come from form fields, query strings, cookies, and HTTP headers. The System.Text.RegularExpressions.Regex class helps validate input parameters. For example, the following code shows how to use this class to validate a name passed through a query string parameter. The same technique can be used to validate other forms of input parameter, for example, from cookies or form fields. For example, to validate a cookie parameter, use Request.Cookies instead of Request.QueryString.

using System.Text.RegularExpressions;
. . .
private void Page_Load(object sender, System.EventArgs e)
{
  // Name must contain between 1 and 40 alphanumeric characters
  // together with (optionally) special characters '`' for names such
  // as D'Angelo
  if (!Regex.IsMatch(Request.QueryString["name"], 
                     @"^[a-zA-Z'`-´\s]{1,40}$"))
    throw new Exception("Invalid name parameter");
  // Use individual regular expressions to validate all other
  // query string parameters
  . . .
}

For more information about using regular expressions and how to validate input data, see "Input Validation" earlier in this chapter.

Exception Management

Correct exception handling in your Web pages prevents sensitive exception details from being revealed to the user. The following recommendations apply to ASP.NET Web pages and controls.

  • Return generic error pages to the client.
  • Implementpage-level or application-level error handlers.

For more information about exception management, see Chapter 7, "Building Secure Assemblies."

Return Generic Error Pages to the Client

In the event of an unhandled exception, that is, one that propagates to the application boundary, return a generic error page to the user. To do this, configure the <customErrors> element as follows:

<customErrors mode="On" defaultRedirect="YourErrorPage.htm" />

The error page should include a suitably generic error message, possibly with additional support details. The name of the page that generated the error is passed to the error page through the aspxerrorpath query parameter.

You can also use multiple error pages for different types of errors. For example:

<customErrors mode="On" defaultRedirect="YourErrorPage.htm">
   <error statusCode="404" redirect="YourNotFoundPage.htm"/>              
   <error statusCode="500" redirect="YourInternalErrorPage.htm"/>              
</customErrors>

For individual pages you can supply an error page using the following page-level attribute:

<% @ Page ErrorPage="YourErrorPage" %>

Implement Page-Level or Application-Level Error Handlers

If you need to trap and process unhandled exceptions at the page level, create a handler for the Page_Error event that is similar to the one shown below.

public void Page_Error(object sender,EventArgs e)
{
  // Get the source exception details
  Exception ex = Server.GetLastError();
  // Write the details to the event log for diagnostics
  . . .
  // Prevent the exception from propagating and generating an 
  // application level event (Application.Error)
  Server.ClearError();
}  

If exceptions are allowed to propagate from the page handler or there is no page handler, an application error event is raised. To trap application-level events, implement Application_Error in Global.asax, as follows:

protected void Application_Error(Object sender, EventArgs e) 
{
  //  Write to the event log.
}

Auditing and Logging

The default ASP.NET process identity for Web applications can write new records to the event log, but it does not have sufficient permissions to create new event sources. To address this issue, you should create the event sources used by your application at installation time, when administrator privileges are available. A good approach is to use a .NET installer class, which can be instantiated by the Windows Installer (if you are using .msi deployment) or by the InstallUtil.exe system utility.

If you are unable to create event sources at installation time, and you are in deployment, the administrator should manually create new event source entry beneath the following registry key

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\<LogName>

To create an application event source at installation time

  1. Right-click your project in the Solution Explorer window in Visual Studio .NET, point to Add, and then click Add Component.

  2. Select InstallerClass from the list of templates and provide a suitable class file name.

    This creates a new installer class annotated with the RunInstaller(true) attribute.

    RunInstaller(true)
    public class EventSourceInstaller : System.Configuration.Install.Installer
    {
     . . .
    }
    
  3. Display the new installer class in Design view, display the Toolbox, and then click Components in the Toolbox. Drag an EventLogInstaller component onto the Designer work surface.

    Note   If EventLogInstaller does not appear in the Toolbox, right-click the Toolbox, and then click Add/Remove Items. Then select EventLogInstaller to add this component type.

  4. Set the following EventLogInstaller properties:

    • Log. Set this property to "Application" which is the name of the event log you should use. You can use the default Application log or create an application-specific log.
    • Source. Set this property to the event source name. This is usually your application name.
  5. Build your project and then create an instance of the installer class at installation time.

    Installer class instances are automatically created and invoked if you use a .NET Setup and Deployment project to create a Windows installer file (.msi). If you use xcopy or equivalent deployment, use the InstallUtil.exe utility to create an instance of the installer class and to execute it.

  6. To confirm the successful generation of the event source, use a registry editor and navigate to:

    HKLM\System\CurrentControlSet\Services\EventLog\Application\{source name}
    

    Confirm that the key exists and that it contains an EventMessageFile string value that points to the default .NET Framework event message file:

    \Windows\Microsoft.NET\Framework\{version}\EventLogMessages.dll
    

    Note    You should not grant write permission to the ASP.NET process account (or any impersonated account if your application uses impersonation) on the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\ registry key. If you allow write access to this key and the account is compromised, the attacker can modify any log-related setting, including access control to the log, for any log on the system.

EventLogPermission

Code that writes to the event log must be granted the EventLogPermission by code access security policy. This becomes an issue if your Web application is configured to run at a partial-trust level. For information about how to write to the event log from a partial trust Web application, see Chapter 9, "Using Code Access Security with ASP.NET."

Summary

This chapter started by showing you the main threats that you need to address when you build Web pages and controls. Many application-level attacks rely on vulnerabilities in input validation. Take special care in this area to make sure that your validation strategy is sound and that all data that is processed from a non-trusted source is properly validated. Another common vulnerability is the failure to protect authentication cookies. The "Forms Authentication" section of this chapter showed you effective countermeasures to apply to prevent unauthorized access, session hijacking, and cookie replay attacks.

Additional Resources

For more information, see the following resources:

patterns & practices Developer Center

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

© Microsoft Corporation. All rights reserved.