Security Guidelines: ASP.NET 2.0

 

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

patterns & practices Developer Center

J.D. Meier, Alex Mackman, Blaine Wastell, Prashant Bansode, Andy Wigley, Kishore Gopalan

Microsoft Corporation

August 2005

Applies To

  • ASP.NET version 2.0

Summary

This module presents a set of consolidated ASP.NET version 2.0 security guidelines. You can use these guidelines to learn security best practices for design, implementation, and deployment. The guidelines can also be used to check an existing application during security review. Each guideline organizes key information and explains what to do, why you should do it, and how you can implement it. Detailed step-by-step instructions for more complex procedures required to implement a guideline are provided by companion How To modules to which the guidelines refer. This guideline module has a corresponding checklist that summarizes the security guidelines. For the checklist, see "Security Checklist: ASP.NET 2.0." This module also includes an index of guidelines for ASP.NET version 2.0 applications.

Contents

How to Use This Module
What's New in 2.0
Index of Guidelines
Input/Data Validation
Authentication
Forms Authentication
Windows Authentication
Authorization
Code Access Security
Data Access
Exception Management
Impersonation/Delegation
Parameter Manipulation
Sensitive Data
Session Management
Auditing and Logging
Deployment Considerations
Communication Security
Companion Guidance
Additional Resources

How to Use This Module

To get the most from this module:

  • Use the index to browse the guidelines. Use the index to scan across the guidelines and to quickly jump to a specific guideline.
  • Learn the guidelines. Browse the guidelines to learn security best practices for design, implementation, and deployment. Each guideline explains what to do, why, and how.
  • Use the companion How To modules. Refer to the associated How To modules for more detailed step-by-step implementation details. They describe how to implement the more complex solution elements required to apply a guideline.
  • Use the companion checklist. Use the associated checklist as a quick reference job aid to help you learn and implement the guidelines.

What's New in 2.0

The .NET Framework version 2.0 and ASP.NET version 2.0 introduce many new security features. The most notable enhancements for ASP.NET Web applications are:

  • Forms authentication and membership. You can now use forms authentication with the new membership feature and membership API. The membership feature supports a provider model, with the SqlMembershipProvider for SQL Server databases and ActiveDirectoryMembershipProvider for Microsoft Active Directory® and Active Directory Application Mode (ADAM) stores provided as built-in providers. You can also create custom providers for your custom user stores. You no longer have to create your own custom databases and write your own custom authentication code.
  • Role manager. The new role management feature provides protected role storage and an API for managing and checking role membership. The role manager supports a provider model. The supplied providers are:
    • The SqlRoleProvider for SQL Server role stores.
    • The WindowsTokenRoleProvider used with Windows authentication, which uses Windows groups as roles.
    • The AuthorizationStoreRoleProvider, which uses Windows Server 2003 Authorization Manager for managing roles in Active Directory or ADAM.
  • DPAPI managed wrapper. The .NET Framework version 2.0 provides a set of managed classes to access the Win32 Data Protection API (DPAPI). Code requires the DataProtectionPermission to be able to use DPAPI.
  • Configuration file changes. Machine-wide configuration settings for all Web applications on a server are now maintained in a machine-level Web.config file instead of Machine.config. The machine-level Web.config file is located in the \Windows\Microsoft.NET\Framework\{version}\CONFIG directory.
  • Configuration file encryption. ASP.NET version 2.0 introduces a Protected Configuration feature to enable you to encrypt sections of your Machine.config and Web.config files by using either DPAPI or RSA encryption. This is particularly useful for encrypting connection strings and account credentials.
  • Healthmonitoring. ASP.NET version 2.0 introduces a health monitoring system. It supports many standard events that you can use to monitor the health of your application. Examples of security related events that are automatically generated include logon failures and successes when using the ASP.NET membership system, attempts to tamper with or reuse forms authentication tickets, and infrastructure events such as disk access failures. You can also create custom events to instrument your application for other security and non-security related notable events.
  • Code access security. The SQL Server managed data provider no longer demands Full trust. This means that Medium trust Web applications can now access SQL Server databases by using this provider. Also, in version 2.0, SmtpPermission is available at Full, High, and Medium trust levels. This allows partial trust Web applications to send e-mail.
  • MachineKey enhancements. The <machineKey> now supports a decryption attribute that specifies the symmetric encryption algorithm used to encrypt and decrypt forms authentication tickets. ASP.NET version 2.0 provides support for AES symmetric encryption, which is used by default, in addition to DES and 3DES.
  • Impersonation token can be retained in new thread. In .NET Framework 2.0, by default the impersonation token still does not flow across threads, but for ASP.NET 2.0 applications you can configure ASP.NET to flow the impersonation token to newly created threads.

Index of Guidelines

Input/Data Validation

  • Do not rely on ASP.NET request validation
  • Validate input for length, range, format, and type
  • Validate input from all sources like QueryString, cookies, and HTML controls
  • Do not rely on client-side validation
  • Avoid user-supplied file name and path input
  • Do not echo untrusted input
  • If you need to write out untrusted data, encode the output

Authentication

Forms Authentication

  • Use membership providers instead of custom authentication
  • Use SSL to protect credentials and authentication cookies
  • If you cannot use SSL, consider reducing session lifetime
  • Validate user login information
  • Do not store passwords directly in the user store
  • Enforce strong passwords
  • Protect access to your credential store
  • Do not persist authentication cookies
  • Restrict authentication tickets to HTTPS connections
  • Consider partitioning your site to restricted areas and public areas
  • Use unique cookie names and paths

Windows Authentication

  • Choose Windows authentication when you can
  • Enforce strong password policies

Authorization

  • Use URL authorization for page and directory access control
  • Configure ACLs on your Web site files
  • Use ASP.NET role manager for roles authorization
  • If your role lookup is expensive, consider role caching
  • Protect your authorization cookie

Code Access Security

  • Consider code access security for partial trust applications
  • Choose a trust level that does not exceed your application's requirements
  • Create a custom trust policy if your application needs additional permissions
  • Use Medium trust in shared hosting environments

Data Access

  • Encrypt your connection strings
  • Use least-privileged accounts for database access
  • Use Windows authentication where possible
  • If you use Windows authentication, use a trusted service account
  • If you cannot use a domain account, consider mirrored accounts
  • When using SQL authentication, use strong passwords
  • When using SQL authentication, protect credentials over the network
  • When using SQL authentication, protect credentials in configuration files
  • Validate untrusted input passed to your data access methods
  • When constructing SQL queries, use type safe SQL parameters
  • Avoid dynamic queries that accept user input

Exception Management

  • Use structured exception handling
  • Do not reveal exception details to the client
  • Use a global error handler to catch unhandled exceptions

Impersonation/Delegation

  • Know your tradeoffs with impersonation
  • Avoid Calling LogonUser
  • Avoid programmatic impersonation where possible
  • If you need to impersonate, consider threading issues
  • If you need to impersonate, clean up appropriately
  • Avoid losing impersonation tokens

Parameter Manipulation

  • Do not make security decisions based on parameters accessible on the client-side
  • Validate all input parameters
  • Avoid storing sensitive data in ViewState
  • Encrypt ViewState if it must contain sensitive data

Sensitive Data

  • Avoid plaintext passwords in configuration files
  • Use platform features to manage keys where possible
  • Do not pass sensitive data from page to page
  • Protect sensitive data over the wire
  • Do not cache sensitive data

Session Management

  • Do not rely on client-side state management options
  • Protect your out-of-process state service
  • Protect SQL Server session state

Auditing and Logging

  • Use health monitoring to log and audit events
  • Instrument for user management events
  • Instrument for unusual activity
  • Instrument for significant business operations
  • Consider using an application-specific event source
  • Protect audit and log files

Deployment Considerations

  • Use a least-privileged account for running ASP.NET applications
  • Encrypt configuration sections that store sensitive data
  • Consider your key storage location
  • Block Protected File Retrieval by Using HttpForbiddenHandler
  • Configure the MachineKey to use the same keys on all servers in a Web farm
  • Lock configuration settings to enforce policy settings

Communication Security

  • Consider SSL vs. IPSec
  • Optimize pages that use SSL

Input/Data 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 serious vulnerabilities in Web applications.

To help avoid input data validation vulnerabilities:

  • Do not rely on ASP.NET request validation.
  • Validate input for length, range, format, and type.
  • Validate input from all sources like QueryString, cookies, and HTML controls.
  • Do not rely on client-side validation.
  • Avoid user-supplied file name and path input.
  • Do not echo untrusted input.
  • If you need to write out untrusted data, encode the output.

Do Not Rely on ASP.NET Request Validation

The ASP.NET request validation feature performs basic input validation. Do not rely on it. Use it as an extra precautionary measure in addition to your own input validation. Only you can define what represents good input for your application.

Request validation is enabled by default. You can see this by examining the validateRequest attribute, which is set to true on the <pages> element in the Machine.config.comments file. Ensure that it is enabled for all pages except those that need to accept a range of HTML elements. If you need to disable it for a page, set the ValidateRequest attribute to false by using the @ Page directive.

Validate Input for Length, Range, Format, and Type

Do not trust input. An attacker passing malicious input can attempt SQL injection, cross-site scripting, and other injection attacks that aim to exploit your application's vulnerabilities. Check for known good data and constrain input by validating it for type, length, format, and range. For Web form applications that obtain input through server controls, use the ASP.NET validator controls, such as the RegularExpressionValidator, RangeValidator, and CustomValidator, to validate and constrain input. Check all numeric fields for type and range. If you are not using server controls, you can use regular expressions and the Regex class, and you can validate numeric ranges by converting the input value to an integer or double and then performing a range check.

Validate Input from All Sources Like QueryString, Cookies, and HTML Controls

Most Web applications accept input from various sources, including HTML controls, server controls, query strings, and cookies. Validate input from all of these sources to help prevent injection attacks. Use regular expressions to help validate input. The following example shows how to use the Regex class.

using System.Text.RegularExpressions ;
 
// Instance method:
Regex reg = new Regex(@"^[a-zA-Z'.\s]{1,40}$");
Response.Write(reg.IsMatch(Request.QueryString["Name"]));

// Static method:
if (!Regex.IsMatch(Request.QueryString["Name"],@"^[a-zA-Z'.\s]{1,40}$")) 
{
   // Name does not match expression
}
  

If you cannot cache your regular expression for frequent use, you should use the static IsMatch method where possible for performance reasons, to avoid unnecessary object creation.

Do Not Rely on Client-Side Validation

Do not rely on client-side validation because it can be easily bypassed. For example, a malicious user could disable your client-side script routines by disabling JavaScript. Use client-side validation in addition to server-side validation to reduce round trips to the server and to improve the user experience.

Avoid User-Supplied File Name and Path Input

Where possible, avoid writing code that accepts user-supplied file or path input. Failure to do this can result in attackers coercing your application into accessing arbitrary files and resources. If your application must accept input file names, file paths, or URL paths, you need to validate that the path is in the correct format and that it points to a valid location within the context of your application.

File Names

Ensure that file paths only refer to files within your application's virtual directory hierarchy if that is appropriate. When checking file names, obtain the full name of the file by using the System.IO.Path.GetFullPath method.

File Paths

If you use MapPath to map a supplied virtual path to a physical path on the server, use the overloaded Request.MapPath method that accepts a bool parameter so that you can prevent cross-application mapping. The following code example shows this technique.

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 this results in an exception of type HttpException.

Do Not Echo Untrusted Input

Do not echo input back to the user without first validating and/or encoding the data. Echoing input directly back to the user makes your application susceptible to malicious input attacks, such as cross-site scripting.

If You Need to Write Out Untrusted Data, Encode the Output

If you write output that includes user input or data from a shared database or a local file that you do not trust, encode it. Echoing input directly back to the user makes your application vulnerable to cross-site scripting attacks. Encoding the data ensures that it is treated as literal text and not as script. You can use the HttpUtility.HtmlEncode method. Similarly, if you write URLs that might contain unsafe characters because they have been constructed from input data or data from a shared database, use HttpUtilty.UrlEncode to make them safe.

Note   Make sure that you encode data at the last possible opportunity before the data is returned to the client.

Additional Resources

For additional resources on input validation, see the following How Tos:

Authentication

Where possible, you should use Windows authentication because this enables you to use an existing identity store such as your corporate Active Directory, it enables you to enforce strong password policies, you do not need to build custom identity store management tools and passwords are not transmitted over the network.

ASP.NET creates an Identity object that implements the System.Security.Principal.IIdentity interface to represent the authenticated user. Regardless of authentication method, you access the authenticated user through HttpContext.Current.User.

If your application is configured for Windows authentication, Internet Information Services (IIS) authenticates the user and the user's Windows token is passed to ASP.NET. ASP.NET constructs a System.Security.Principal.WindowsIdentity object, which contains the user's token, places this inside a System.Security.Principal.WindowsPrincipal object, and then attaches this to the current Web request.

When your application is configured for a non-Windows authentication mechanism such as forms authentication, the Windows token passed by IIS to ASP.NET is a token for the anonymous Internet user account IUSR_MACHINENAME. With forms authentication, ASP.NET constructs a System.Web.Security.FormsIdentity object and places it inside a System.Security.Principal.GenericPrincipal object before attaching it to the current Web request.

This section provides guidance on forms authentication and Windows authentication:

  • Forms authentication
  • Windows authentication

Forms Authentication

To protect forms authentication, you need to protect user-supplied credentials, the credential store and the authentication ticket. To do this, use the following guidelines:

  • Use membership providers instead of custom authentication.
  • Use SSL to protect credentials and authentication cookies.
  • If you cannot use SSL, consider reducing session lifetime.
  • Validate user login information.
  • Do not store passwords directly in the user store.
  • Enforce strong passwords.
  • Protect access to your credential store.
  • Do not persist authentication cookies.
  • Restrict authentication tickets to HTTPS connections.
  • Consider partitioning your site to restricted areas and public areas.
  • Use unique cookie names and paths.

Use Membership Providers Instead of Custom Authentication

In ASP.NET version 1.1, you had to implement custom logic for validating user credentials, performing user management, and managing the authentication ticket. In version 2.0, you can use the built-in membership feature. The membership feature helps protect credentials, can enforce strong passwords, and provides consistent APIs for user validation and secure user management. The membership feature also automatically creates the authentication ticket for you.

The membership feature has built-in providers for user stores including SQL Server, Active Directory, and Active Directory Application Mode (ADAM). If you want to use an existing user store, such as a non-Active Directory LDAP directory, or a user store on another platform, create a custom membership provider inheriting from the MembershipProvider abstract base class. By doing this, your application can still benefit from using the standard membership features and API and login controls.

For more information, see How To: Use Membership in ASP.NET 2.0.

Use SSL to Protect Credentials and Authentication Cookies

Use Secure Sockets Layer (SSL) to protect the authentication credentials and authentication cookies passed between browser and server. By using SSL, you prevent an attacker monitoring the network connection to obtain authentication credentials and capturing the authentication cookie to gain spoofed access to your application.

If You Cannot Use SSL, Consider Reducing Session Lifetime

If you cannot use SSL, limit the cookie lifetime to reduce the time window in which an attacker can use a captured cookie to gain access to your application with a spoofed identity. The default timeout for an authentication cookie is 30 minutes. Consider reducing this to 10 minutes as shown here.

<forms 
    timeout="00:10:00" 
    slidingExpiration="true"... />
  

The slidingExpiration="true" setting ensures that the expiration period is reset after each Web request. With the preceding configuration, this means that the cookie only times out after a 10 minute period of inactivity.

If you are in a scenario where you are concerned about cookie hijacking, consider reducing the timeout and setting slidingExpiration="false". If sliding expiration is turned off, the authentication cookie expires after the timeout period whether or not the user is active. After the timeout period, the user must re-authenticate.

Validate User Login Information

Validate user login information including user names and passwords for type, length, format, and range. Use regular expressions to constrain the input at the server. If you are not using the SqlMembershipProvider and need to develop your own queries to access your user store database, do not use login details to dynamically construct SQL statements because this makes your code susceptible to SQL injection. Instead, validate the input and then use parameterized stored procedures.

Also encode login details before echoing them back to the user's browser to prevent possible script injection. For example, use HtmlEncode as shown here.

Response.Write(HttpUtility.HtmlEncode("Welcome " + Request.Form["username"]));
  

Do Not Store Passwords Directly in the User Store

Do not store user passwords either in plaintext or encrypted format. Instead, store password hashes with salt. By storing your password with hashes and salt, you help prevent an attacker that gains access to your user store from obtaining the user passwords. If you use encryption, you have the added problem of securing the encryption key. Use one of the membership providers to help protect credentials in storage and where possible, specify a hashed password format on your provider configuration.

If you must implement your own user stores, store one-way password hashes with salt. Generate the hash from a combination of the password and a random salt value. Use an algorithm such as SHA256. If your credential store is compromised, the salt value helps to slow an attacker who is attempting to perform a dictionary attack. This gives you additional time to detect and react to the compromise.

Enforce Strong Passwords

Ensure that your passwords are complex enough to prevent users guessing other users' passwords and to prevent successful dictionary attacks against your user credential store.

By default, the ASP.NET membership providers enforce strong passwords. For example, the SqlMembershipProvider and the ActiveDirectoryMembership providers ensure that passwords are at least seven characters in length with at least one non-alphanumeric character. Ensure that your membership provider configuration enforces passwords of at least this strength. To configure the precise password complexity rules enforced by your provider, you can set the following additional attributes:

  • passwordStrengthRegularExpression. The default is "".
  • minRequiredPasswordLength. The default is 7.
  • minRequiredNonalphanumericCharacters. The default is 1.

Note   The default values shown here apply to the SqlMembershipProvider and the ActiveDirectoryMembershipProvider. The ActiveDirectoryMembershipProvider also verifies passwords against the default domain password policy.

Protect Access to Your Credential Store

Ensure only those accounts that require access are granted access to your credential store. This helps to protect the credential store by limiting access to it. For example, consider limiting access to only your application's account. Ensure that the connection string used to identify your credential store is encrypted.

Also consider storing your credential database on a physically separate server from your Web server. This makes it harder for an attacker to compromise your credential store even if he or she manages to take control of your Web server.

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. To ensure a non-persistent cookie, set the DisplayRememberMe property of the Login control to false. If you are not using the login controls, you can specify a non-persistent cookie when you call either the RedirectFromLoginPage or SetAuthCookie methods of the FormsAuthentication class having validated the user's credentials, as shown here.

public void Login_Click(object sender, EventArgs e)
{
   // Is the user valid?
   if (Membership.ValidateUser(userName.Text, password.Text))
   {
        // Parameter two set to false indicates non-persistent cookie
        FormsAuthentication.RedirectFromLoginPage(username.Text, false);
    }
    else
    {
        Status.Text = "Invalid credentials. Please try again.";
    }
}
  

Restrict Authentication Tickets to HTTPS Connections

Set the secure property of the authentication cookie to ensure that browsers only send authentication cookies over HTTPS connections. By using SSL, you prevent an attacker from capturing the authentication cookie to gain spoofed access to your application.

Set the secure property by using requireSSL="true" on the <forms> element as shown here.

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

Consider Partitioning Your Site to Restricted Areas and Public Areas

To avoid the performance overhead of using SSL across your entire site, consider using a separate folder to help protect pages that require authenticated access. Configure that folder in IIS to require SSL access. Those pages that support anonymous access can safely be accessed over HTTP connections.

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 do not 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.

Additional Considerations

In addition to the preceding guidelines, consider the following.

  • Set httpOnly on the authentication cookie. Set the httpOnlyCookies attribute on the authentication cookie. Internet Explorer 6 Service Pack 1 supports this attribute, which prevents client-side script from accessing the cookie from the document.cookie property. The System.Net.Cookie class in .NET Framework version 2.0 supports an HttpOnly property. The HttpOnly property is always set to true by forms authentication.

Additional Resources

For additional resources on forms authentication, see the following How Tos:

Windows Authentication

If you use Windows authentication, consider the following guidelines:

  • Choose Windows authentication when you can.
  • Enforce strong password policies.

Choose Windows Authentication When You Can

When possible, use Windows authentication to authenticate your users. By using Windows authentication with Active Directory, you benefit from a unified identity store, centralized account administration, enforceable account and password policies, and strong authentication that avoids sending passwords over the network.

Enforce Strong Password Policies

To help ensure that users cannot guess one another's passwords and to help prevent successful dictionary attacks in the event your password store is compromised, enforce strong passwords through Active Directory policy. To enforce a strong password policy:

  • Set password length and complexity. Strong passwords are eight or more characters and must include both alphabetical and numeric characters. The default enforced by the ActiveDirectoryMembershipProvider is seven characters with at least one non-alphanumeric character. If you use this provider, the provider settings are checked first, followed by the Active Directory settings. Users must supply passwords that conform to the stronger of the two.
  • Set password expiration. Passwords that expire regularly reduce the likelihood that an old password can be used for unauthorized access. Frequency of expiration is usually guided by a company's security policy. You should define this in Active Directory.

For more information on forms authentication, see How To: Use Windows Authentication in ASP.NET 2.0.

Authorization

You control authorization administratively through URL and file authorization and programmatically by performing identity and role checks in code.

You also need to consider who you are authorizing. Are you authorizing the application, the caller. or both? Also, what are you authorizing access to? You could be authorizing against:

  • System resources. These include the file system and registry.
  • Application resources. These include business logic and network resources, such as databases and Web services.
  • User resources. These include resources such as customer records.

When implementing authorization logic, consider the following guidelines:

  • Use URL authorization for page and directory access control.
  • Configure ACLs on your Web site files.
  • Use ASP.NET role manager for roles authorization.
  • If your role lookup is expensive, consider role caching.
  • Protect your authorization cookie.

Use URL Authorization for Page and Directory Access Control

Use URL authorization to control which users and groups of users have access to the application or to parts of the application. In ASP.NET version 1.1, URL authorization only applies to file types that are mapped by IIS to the ASP.NET ISAPI extension (Aspnet_isapi.dll). In ASP.NET version 2.0 on Windows Server 2003, URL authorization protects all files in a directory, even files that are not mapped to ASP.NET, such as .html, .gif, and .jpg files.

When your application uses Windows authentication, you can authorize access to Windows user and/or Windows group accounts. To do so, you should configure your application to use ASP.NET Role Manager. For more information, see the "Use ASP.NET Role Manager for Roles Authorization" section in this topic.

You can use the <location> tag to apply authorization settings to an individual file or directory. The following example shows how you can apply authorization to a specific file (page.aspx).

<location path="page.aspx" />
 <authorization>
   <allow users="DomainName\Bob, DomainName\Mary" />
   <deny users="*" />
 </authorization>
</location>
  

Configure ACLs on Your Web Site Files

You need to configure the right access control lists (ACLs) for the right identities on your Web site files so that IIS and also ASP.NET file authorization control access to these files appropriately. You need to grant access to the following identities:

  • Your Web application identity. If you are using a custom service account to run your ASP.NET application, you can grant the appropriate permissions to the IIS metabase and to the file system by running Aspnet_regiis.exe with the–ga switch.
  • Your application's users. ASP.NET file authorization performs access checks for file types mapped by IIS to the ASP.NET ISAPI extension (Aspnet_isapi.dll). If you are using Windows authentication, the authenticated user's Windows access token (which may be IUSR_MACHINE for anonymous users) is checked against the ACL attached to the requested ASP.NET file. If you are using forms authentication, access is checked against IUSR_MACHINE.

File authorization works automatically when using Windows authentication, and there is no need to impersonate the original user. The FileAuthorizationModule only performs access checks against the requested file. For example, if you request Default.aspx and it contains an embedded user control (Usercontrol.ascx), which in turn includes an image tag (pointing to Image.gif), the FileAuthorizationModule performs an access check for Default.aspx and Usercontrol.ascx, because these file types are mapped by IIS to the ASP.NET ISAPI extension. The FileAuthorizationModule does not perform a check for Image.gif, because this is a static file handled internally by IIS. However, because access checks for static files are performed by IIS, the authenticated user must still be granted read permission to the file with an appropriately configured ACL.

Use ASP.NET Role Manager for Roles Authorization

In ASP.NET version 1.1, you had to create, manage, and look up roles for the authenticated user by writing your own code. ASP.NET version 2.0 provides a new role manager feature that automatically performs this work. Roles are accessed from the configured role store by the RoleManager HTTP module by using the configured role provider. This occurs after the user is authenticated but before URL authorization and file authorization access checks occur and before programmatic role checks can occur.

If your application needs role-based authorization, use the following guidelines:

  • Use role providers to perform role authorization. Role providers allow you to load the roles for users without writing and maintaining code. Additionally, the role providers offer a consistent way for you to check the role membership of your users, regardless of the underlying data store. Where possible, use one of the supplied roles providers; these include SqlRoleProvider, WindowsTokenRoleProvider, and AuthorizationStoreRoleProvider. If you already have a custom role store in a non-SQL Server database or in a non-Active Directory LDAP store, consider implementing your own custom role provider.

    The following code shows how to use the role manager API and specifically Roles.IsUserInRole.

    // Tests whether the currently authenticated user is a member 
    // of a particular role.
     if(Roles.IsUserInRole("Manager"))
       // Perform restricted operation
     else
       // Return unauthorized access error.
    
     // Tests whether any given user is a member of a role:
     if(Roles.IsUserInRole("Bob","Manager"))
       // Perform restricted operation
     else
       // Return unauthorized access error.
    
    
  • Use the SqlRoleProvider when your roles are in SQL Server. If you store role information in SQL Server, configure your application to use the SqlRoleProvider. You can also configure forms authentication with the SqlMembershipProvider to use the same database for authentication and authorization, although this is not required.

  • Use the WindowsTokenRoleProvider when you use Windows groups as roles. If your application uses Windows authentication and you use Windows groups as roles, configure your application to use the WindowsTokenRoleProvider.

  • Use the AuthorizationStoreRoleProvider when your application roles are in ADAM. If your application uses Windows authentication against Active Directory, and you need application specific roles instead of using your domain Windows group membership, you can store role information in SQL Server or in an Authorization Manager (AzMan) policy store in ADAM. Authorization Manager provides a Microsoft Management Console (MMC) snap-in, to create and manage roles, and to manage role membership for users.

If your user accounts are in Active Directory, but you cannot use Windows authentication and must use forms authentication, a good solution for roles management is to use the AuthorizationStoreRoleProvider with an AzMan policy store in ADAM.

Note   The AuthorizationStoreRoleProvider does not directly support Authorization Manager business logic such as operations and tasks. To do this, you must use P/Invoke and call the Authorization Manager API directly.

For more information, see How To: Use Role Manager in ASP.NET 2.0.

If Your Role Lookup Is Expensive, Consider Role Caching

If the performance overhead of role lookup is too great, perhaps because of slow data access or a large number of roles, consider caching roles in the roles cookie by setting the cacheRolesInCookie attribute to true in the Web.config file as shown here.

<roleManager enabled="true"
             cacheRolesInCookie="true"
                 ... >
</roleManager>
  

When role checks are performed, the roles cookie is checked before calling the role provider to check the list of roles within the data source. This improves performance. The cookie is dynamically updated to cache the most recently validated role names. If the role information for a user is too long to store in a cookie, ASP.NET stores only the most recently used role information in the cookie and then it looks up additional role information in the data source as required. The most recently referred to roles end up being cached in the cookie.

You should protect roles data in the authorization cookie to stop users from modifying the list of roles to which they belong, and to stop intruders from gaining information about the roles used by your application. You protect the authorization cookie by appropriately configuring the <roleManager> element as follows:

  • Set the cookieProtection attribute to All. This ensures that the role names in the cookie are digitally signed and encrypted to prevent unauthorized viewing and modification of the role data.
  • Set the cookieSlidingExpiration attribute to true and the cookieTimeout attribute to an integer number of minutes (for example, 10 to 20 or fewer). The cookie expires when the timeout expires and must be renewed.
  • Set the cookieRequireSSL attribute to true to specify that the authorization cookie with the role information should only be returned to the server over HTTPS connections.
  • Set the createPersistentCookie attribute to false to ensure that the roles cookie is session-based and not a persistent cookie.
  • If you cannot use SSL, consider reducing the cookieTimeout to 10 minutes. If you are concerned with cookie hijacking, consider setting the cookieSlidingExpiration attribute to false. This causes the cookie to expire after the fixed timeout period and must be renewed.

The following example shows a <roleManager> element configured to protect the authorization cookie.

<roleManager enabled="true"
             cacheRolesInCookie="true"
             cookieName=".ASPROLES"
             cookieTimeout="30"
             cookiePath="/"
             cookieRequireSSL="true"
             cookieSlidingExpiration="true"
             cookieProtection="All" 
             createPersistentCookie="false">
</roleManager>
  

Additional Resources

For additional resources on Windows authentication, see the following How Tos:

Code Access Security

Code access security is a resource constraint model designed to restrict the types of system resource that code can access and the types of privileged operation that the code can perform. These restrictions are independent of the user who calls the code or the user account under which the code runs. ASP.NET code access security policy is defined by trust levels.

When using code access security with ASP.NET, consider the following guidelines:

  • Consider code access security for partial trust applications.
  • Choose a trust level that does not exceed your application's requirements.
  • Create a custom trust policy if your application needs additional permissions.
  • Use Medium trust in shared hosting environments.

Consider Code Access Security for Partial Trust Applications

If your application calls only managed code, you can use different code access security trust levels to incrementally limit your exposure to security attacks and provide extra degrees of application isolation. You should consider code access security particularly if you need application isolation in shared hosting environments.

If you do not plan on running your application at a partial trust level, code access security presents no additional design or development considerations because at Full trust, code access permission demands will succeed. An application's trust level is determined by the <trust> element as shown here.

<trust level="Full|High|Medium|Low|Minimal" />
  

Choose a Trust Level That Does Not Exceed Your Application's Requirements

Choose a trust level that does not provide additional permissions beyond what your application requires. By doing this, you follow the principle of least privilege and constrain your application as much as possible. To select the most appropriate trust level, identify the precise set of code access security permissions that your application requires. You can do this by manually reviewing your code or by using the Permissions Calculator tool (Permcalc.exe). Evaluate whether the permissions required for your application match those provided by any of the standard trust levels. To see the permissions each trust level provides, examine each trust level policy file in the %windir%\Microsoft.NET\Framework\{version}\CONFIG directory, beginning with the High trust policy file web_hightrust.config.

If your application requires fewer code access security permissions than those provided by the High trust level, move on to consider Medium trust. Repeat the process, moving from Medium to Low to Minimal, and keep evaluating the partial trust levels until you reach an exact match to your application's requirements or until your application's required permissions slightly exceed a partial trust level. If your application needs more permissions than are granted by one level but requires fewer that are provided by the next level, consider creating a custom trust policy.

For more information, see How To: Use Code Access Security in ASP.NET 2.0.

Create a Custom Trust Policy if Your Application Needs Additional Permissions

If your application requires additional permissions beyond those provided at a particular trust level, but it does not need the additional permissions provided by the next trust level, create a custom trust policy file. This avoids granting your application unnecessary permissions.

To create a custom policy, copy the trust level policy file that most closely matches your application's permission requirements to create a new custom policy file. Locate this file in the standard policy file directory, which is %Windir%\Microsoft.NET\Framework\{Version}\CONFIG. Then add the required additional permissions to the custom policy file and configure your application to run using the custom policy.

For more information, see How To: Use Code Access Security in ASP.NET 2.0.

Use Medium Trust in Shared Hosting Environments

In ASP.NET version 1.1, Web applications configured for Medium trust could not access SQL Server databases. In ASP.NET version 2.0, SQL Server database access is available to Medium trust applications because the SQL Server managed data provider no longer demands Full trust. If you need to isolate multiple applications on a shared server from one another and from shared system resources, use Medium trust. To enforce Medium trust for all Web applications on a server, use the following configuration in the machine-level Web.config file.

<location allowOverride="false">
  <system.web>
   <trust level="Medium" originUrl="" />
  </system.web>
 </location>
  

By setting allowOverride="false", an individual developer is unable to override the Medium trust policy setting in his or her application's Web.config file.

Medium trust provides application isolation because it restricts file system access to the application's own virtual directory hierarchy, and it limits access to HTTP Web resources to a defined address or set of addresses specified by the originUrl attribute on the <trust> element. It also prevents access to shared system resources such as the Windows event log and registry. Additionally, Medium trust applications cannot use reflection and cannot access non-SQL Server OLE DB data sources.

For more information, see How To: Use Medium Trust in ASP.NET 2.0.

Data Access

When building the data access layer of your application, consider the following guidelines:

  • Encrypt your connection strings.
  • Use least-privileged accounts for database access.
  • Use Windows authentication where possible.
  • If you use Windows authentication, use a trusted service account.
  • If you cannot use a domain account, consider mirrored accounts.
  • When using SQL authentication, use strong passwords.
  • When using SQL authentication, protect credentials over the network.
  • When using SQL authentication, protect credentials in configuration files.
  • Validate untrusted input passed to your data access methods.
  • When constructing SQL queries, use type safe SQL parameters.
  • Avoid dynamic queries that accept user input.

Encrypt your connection strings

In ASP.NET version 1.1, you could encrypt connection strings by using DPAPI encryption, although you had to write managed code to wrap the calls to DPAPI. This approach also presented problems in Web farms because of machine affinity. In ASP.NET version 2.0, you can use a protected configuration provider, such as DPAPI or RSA, which is easier to use in Web farms. You do not have to write code because you use the Aspnet_regiis.exe utility.

In ASP.NET version 2.0 applications, place your database connection strings inside the <connectionStrings> element of the Web.config file and then encrypt that element by using the Aspnet_regiis utility. In a Web farm, use the RSA-protected configuration provider because RSA keys can be exported and imported across servers.

For more information, 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 Least-Privileged Accounts for Database Access

Your application should connect to the database by using a least-privileged account. This limits the damage that can be done in the event of a SQL injection attack or in the event of an attacker obtaining your account's credentials. With Windows authentication, use a least-privileged account with limited operating system permissions, and limited ability to access Windows resources. Regardless of authentication mechanism, restrict the account's permissions in the database.

Use the following pattern to limit permissions in the database:

  1. Create a SQL Server login for the account.
  2. Map the login to a database user in the required database.
  3. Place the database user in a database role.
  4. Grant the database role limited permissions to only those stored procedures or tables that your application needs to access.

Ideally, provide no direct table access and limit access to selected stored procedures only. If you must grant table access, grant the minimum access that the application requires. For example, do not grant update access if read access is sufficient.

By using a database role, you avoid granting permissions directly to the database user. This isolates you from potential changes to the database user name. For example, if you change the database user name, you can simply add the new user to the database role and remove the existing one.

Use Windows Authentication Where Possible

Prefer Windows authentication when connecting to SQL Server or other databases that support it. Windows authentication offers a number of security advantages in comparison to SQL authentication:

  • Accounts are centralized and managed by your Active Directory or local authority store.
  • Strong password policies can be controlled and enforced by your domain or local security policy.
  • Passwords are not transmitted over the network.
  • User IDs and passwords are not specified in database connection strings.

For more information, see How To: Connect to SQL Server Using Windows Authentication in ASP.NET 2.0.

If You Use Windows Authentication, Use a Trusted Service Account

If you use Windows authentication, use a trusted service account to access the database when possible. This is usually your application's process account. By using a single trusted service account, your application benefits from connection pooling; this provides greater scalability. Also, account administration and authorization within the database is simplified. If you need per-user authorization in the database or need to use operating system auditing to track the activity of individual users, you need to use impersonation and delegation and access the database using the caller's identity. This approach has limited scalability because it prevents the efficient use of connection pooling.

If You Cannot Use a Domain Account, Consider Mirrored Accounts

If you cannot use domain accounts because of domain trust or firewall restrictions, consider using mirrored service accounts instead. With this approach, you still use Windows authentication, but you create two local accounts with the same name and password on the Web server and database server. You configure your application to run using the local account created on the Web server and create a SQL login for the local account on the database server. With this approach, you must ensure that the passwords of the two accounts remain in synchronization.

For more information, see How To: Connect to SQL Server Using SQL Authentication in ASP.NET 2.0.

When Using SQL Authentication, Use Strong Passwords

If you use SQL Server authentication, make sure you use a least-privileged account with a strong password to prevent an attacker guessing your account's password. A strong password should be at least seven characters in length and contain a combination of alphabetic, numeric, and special characters.

Avoid using blank passwords and the sa account as shown in the following connection string.

SqlConnectionString = "Server=YourServer\Instance; Database=YourDatabase; uid=sa; pwd=;"
  

Use least-privileged accounts with a strong password, such as the following.

SqlConnectionString= "Server=YourServer\Instance;
                      Database=YourDatabase;
                      uid=YourStrongAccount; 
                      pwd=YourStrongP@ssw0rd;"
  

When Using SQL Authentication, Protect Credentials Over the Network

If your application is not located in a physically secure isolated data center, you should use Internet Protocol Security (IPSec) or SSL to create an encrypted communication channel between the Web server and database server if your use SQL authentication. Failure to do this means that credentials can be easily captured with a network monitor. When you connect to SQL Server with SQL authentication, the credentials are not encrypted prior to transmission across the network.

Use SSL when you need granular channel protection for a particular application instead of for all applications and services running on a computer. If you want to protect all the IP traffic between Web and database server, use IPSec. You can also use IPSec to restrict which computers can communicate with one another. For example, you can help protect a database server by establishing a policy that permits requests only from a trusted client computer, such as an application or Web server. You can also restrict communication to specific IP protocols and TCP/UDP ports.

When Using SQL Authentication, Protect Credentials in Configuration Files

To protect credentials in configuration files, encrypt them. Place your database connection strings inside the <connectionStrings> element of the Web.config file and then encrypt that element by using the Aspnet_regiis utility. You can use DPAPI or RSA encryption. Use RSA in Web farms because you can easily export and import RSA keys across servers. Protecting connection strings is particularly important for connection strings that use SQL authentication because they contain clear text user IDs and passwords.

Note   You should also encrypt connection strings if you use Windows authentication. Although this form of connection string does not contain credentials, you should aim to keep server and database names private.

For more information, 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.

Validate Untrusted Input Passed to Your Data Access Methods

If your data access methods receive input parameters from outside the trust boundary of your data access code, make sure you validate them for type, length, format, and range. You can use regular expressions for text input and perform type and range checks on numeric data. If you do not do this, your data access code is potentially susceptible to SQL injection.

Only omit input parameter validation in your data access methods if you know for certain that data can only be supplied by trusted code, such as your application's business logic, which you know has thoroughly validated the data passed to it.

Note   Avoid storing encoded data; instead, encode the data as close as possible to the output.

When Constructing SQL Queries, Use Type Safe SQL Parameters

Use type safe parameters when constructing SQL queries to avoid possible SQL injection attacks that can occur with unfiltered input. You can use type safe parameters with stored procedures and with dynamic SQL statements. Parameters are treated as literal values by the database and not as executable code. Parameters are also checked for type and length.

The following code shows how to use type safe parameters with the SqlParameterCollection when calling a stored procedure.

using System.Data;
using System.Data.SqlClient;

using (SqlConnection connection = new SqlConnection(connectionString))
{
 DataSet userDataset = new DataSet();
 SqlDataAdapter myCommand = new SqlDataAdapter(LoginStoredProcedure", connection);
 myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
 myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11);
 myCommand.SelectCommand.Parameters["@au_id"].Value = SSN.Text;
 myCommand.Fill(userDataset);
}
  

In the preceding code example, the input value cannot be longer than 11 characters. If the data does not conform to the type or length defined by the parameter, the SqlParameter class throws an exception. For more information about preventing SQL injection, see How To: Protect from SQL Injection in ASP.NET.

Avoid Dynamic Queries That Accept User Input

Avoid constructing SQL queries in code that include user input; instead, prefer parameterized store procedures that use type safe SQL parameters. If you construct queries dynamically using user input, your code is susceptible to SQL injection. For example, avoid the following style of code.

// Use dynamic SQL
SqlDataAdapter myCommand = new SqlDataAdapter(
         "SELECT au_lname, au_fname FROM authors WHERE au_id = '" + 
         SSN.Text + "'", myConnection);
  

If a malicious user supplies "' ; DROP DATABASE pubs --'" for the SSN.Text field, the code inserts the user's malicious input and generates the following query.

SELECT au_lname, au_fname FROM authors WHERE au_id = ''; DROP DATABASE pubs --'
  

The ; (semicolon) character tells SQL that this is the end of the current statement, which is then followed by the following malicious SQL code, which in this example drops the authors table.

Additional Resources

For additional resources on connecting to SQL Server, see the following How Tos:

Exception Management

Correct exception handling in your Web pages prevents sensitive exception details from being revealed to the user, improves application robustness, and helps avoid leaving your application in an inconsistent state in the event of errors. Consider the following guidelines:

  • Use structured exception handling.
  • Do not reveal exception details to the client.
  • Use a global error handler to catch unhandled exceptions.

Use Structured Exception Handling

Use structured exception handling and catch exception conditions. Doing this improves robustness and avoids leaving your application in an inconsistent state that may lead to information disclosure. It also helps protect your application from denial of service attacks. Use finally blocks to ensure that resources are cleaned up and closed even in the event of an exception condition. Decide how to propagate exceptions internally in your application and give special consideration to what occurs at the application boundary.

Do Not Reveal Exception Details to the Client

When exceptions occur, return concise error messages to the client and log specific details on the server. Do not reveal internal system or application details, such as stack traces, SQL statement fragments, and table or database names to the client. Ensure that this type of information is not allowed to propagate to the end user or beyond your current trust boundary. A malicious user could use system-level diagnostic information to learn about your application and probe for weaknesses to exploit in future attacks.

Prevent detailed error messages from displaying by setting the mode attribute of the <customErrors> element to On, so that all callers receive filtered exception information. Do not use mode="Off" because this allows detailed error pages intended for application developers that contain system-level information to be returned to the client.

You should also use the <customErrors> section of the Web.config file as shown in the following code example to specify a default error page to display, along with other required error pages for specific HTTP response codes that indicate errors.

<customErrors mode="On" defaultRedirect="ErrDefault.aspx">
    <error statusCode="401" redirect="ErrUnauthorized.aspx" />
    <error statusCode="404" redirect="ErrPageNotFound.aspx" />
    <error statusCode="500" redirect="ErrServer.htm" />
</customErrors>
  

The defaultRedirect attribute allows you to use a custom error page for your application, which. for example, might include support contact details. Use these application-wide error pages to provide user-friendly responses for errors that are not caught in a structured event handling.

Use a Global Error Handler to Catch Unhandled Exceptions

Define an application-level global error handler in Global.asax to catch any exceptions that are not handled in code. Do this to avoid accidentally returning detailed error information to the client. You should log all exceptions in the event log to record them for tracking and later analysis. Use code similar to the following.

<%@ Application Language="C#" %>
 <%@ Import Namespace="System.Diagnostics" %>
 
 <script language="C#" >
 void Application_Error(object sender, EventArgs e)
 {
    //get reference to the source of the exception chain
    Exception ex = Server.GetLastError().GetBaseException();
 
    // log the details of the exception and page state to the
    // event log.
    EventLog.WriteEntry("My Web Application",
      "MESSAGE: " + ex.Message + 
      "\nSOURCE: " + ex.Source, 
      EventLogEntryType.Error);
 
    // Optional e-mail or other notification here...
 }
 </script>
  

Impersonation/Delegation

By default, ASP.NET Web applications do not impersonate, although in some scenarios, you need to use impersonation to perform operations or access resources using the authenticated user's identity. If you use impersonation, consider the following guidelines:

  • Know your tradeoffs with impersonation.
  • Avoid calling LogonUser.
  • Avoid programmatic impersonation where possible.
  • If you need to impersonate, consider threading issues.
  • If you need to impersonate, clean up appropriately.
  • Avoid losing impersonation tokens.

Know Your Tradeoffs with Impersonation

Be aware that impersonation prevents the efficient use of connection pooling if you access downstream databases by using the impersonated identity. This impacts the ability of your application to scale. Also, using impersonation can introduce other security vulnerabilities, particularly in multi-threaded applications, such as ASP.NET Web applications.

You might need impersonation if you need to:

  • Flow the original caller's security context to the middle tier and/or data tier of your Web application to support fine-grained (per-user) authorization.
  • Flow the original caller's security context to the downstream tiers to support operating system level auditing.
  • Access a particular network resource by using a specific identity.

Avoid Calling LogonUser

Avoid writing code that calls the LogonUser API to create impersonation tokens because this forces you to store user names and passwords on your Web server.

Instead, use a unique application pool with a specific process identity if you need a specific identity to access downstream resources. If you need multiple identities to access a range of downstream resources and services, use Windows Server 2003 protocol transition and the new WindowsIdentity constructor; this allows you to create a Windows token that is given only an account's user principal name (UPN). To access a network resource, you need to delegate-level token. To get this token type, your server needs to be configured as trusted for delegation in Active Directory.

The following code shows how to use this constructor to obtain a Windows token for a given user.

using System;
using System.Security.Principal;
public void ConstructToken(string upn, out WindowsPrincipal p)
{
  WindowsIdentity id = new WindowsIdentity(upn);
  p = new WindowsPrincipal(id);
}
  

Note   An account's UPN is guaranteed to be unique within a forest. Frequently, the UPN is the user's e-mail address, but it does not have to be. A user always has a UPN and by default, it is userlogonname@fullyqualifieddomainname. If you are logged into a domain, you can find your UPN name by running whoami /upn from a command window.

For more information, see How To: Use Protocol Transition and Constrained Delegation with ASP.NET 2.0.

Avoid Programmatic Impersonation Where Possible

If programmatic impersonation is not done properly, it can introduce security vulnerabilities. It is difficult to get it correct, particularly in multithreaded applications. When possible, use alternative approaches, such as a custom domain process identity for resource access. You should avoid programmatic impersonation where possible for the following reasons:

  • It is easy to introduce errors because of thread switches where the thread impersonation token is not propagated across threads.
  • Some programmatic techniques require you to store credentials which should be avoided.
  • Some programmatic techniques require you to grant additional privileges to your process account, which you should avoid. For example, you must grant your process account "Act as part of the operating system" if you call LogonUser on Windows Server 2000 or to obtain an impersonate-level token on Windows Server 2003 when you use the new WindowsIdentity constructor that generates a token from a user principal name.
  • If exceptions occur while impersonating, it is possible for malicious code higher in the call stack to run using the impersonated identity. This can present security issues, particularly if you impersonate a highly privileged account.

If You Need to Impersonate, Consider Threading Issues

Losing an impersonated security context because of thread switches is a common vulnerability. The common language runtime (CLR) automatically propagates impersonation tokens to threads that you create using any of the managed threading techniques, such as Thread.Start, an asynchronous delegate, or QueueUserWorkItem. However, it is easy to drop the thread impersonation token if you use COM interop with components that have incompatible threading models or if you use unmanaged techniques to create new threads such as the Win32 CreateThread API.

If You Need to Impersonate, Clean Up Appropriately

If you must use programmatic impersonation, use structured exception handling and put the impersonation code inside try blocks. Use a catch block to handle exceptions and use a finally block to ensure that the impersonation is reverted as shown here.

using System.Security.Principal;
...
WindowsIdentity winIdentity = new WindowsIdentity("username@domainName");
WindowsImpersonationContext ctx = winIdentity.Impersonate();
try
{
  // Do work
}
catch(Exception ex)
{
  // Stop impersonating
  ctx.Undo();
}
finally
{
  // Stop impersonating
  ctx.Undo();
}
  

By using a finally block, you ensure that the impersonation token is removed from the current thread whether an exception is generated or not. Also be aware that if your code fails to catch exceptions, a malicious user could use exception filters to execute code that runs under the impersonated security context. This is particularly serious if your code impersonates a privileged account. If your code does not catch the exception, exception filters higher in the call stack are executed before code in your finally block is executed.

Note   Exception filters are supported by Microsoft Intermediate Language (MSIL) and Visual Basic .NET.

For more information, see How To: Use Impersonation and Delegation in ASP.NET 2.0.

Avoid Losing Impersonation Tokens

In .NET Framework 1.1, impersonation tokens did not automatically flow to newly created threads. This situation could lead to security vulnerabilities because new threads assume the security context of the process. In ASP.NET 2.0 applications you can now change this default behavior by configuring the ASPNET.config file in the %Windir%Microsoft.NET\Framework\{Version} directory.

If you need to flow the impersonation token to new threads, set the enabled attribute to true on the alwaysFlowImpersonationPolicy element in the ASPNET.config file, as shown in the following example.

....
<configuration>
  <runtime>
    <alwaysFlowImpersonationPolicy enabled="true"/>
     </runtime>
</configuration>
....

If you need to prevent impersonation tokens from being passed to new threads programmatically, you can use the ExecutionContext.SuppressFlow method.

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.

The following recommendations help you avoid parameter manipulation vulnerabilities:

  • Do not make security decisions based on parameters accessible on the client-side.
  • Validate all input parameters.
  • Avoid storing sensitive data in ViewState.
  • Encrypt ViewState if it must contain sensitive data.

Do Not Make Security Decisions Based on Parameters Accessible on the Client-Side

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.

Validate All 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, such as 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 such as apostrophes 
  // 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, see How To: Protect from Injection Attacks in ASP.NET and How To: Use Regular Expressions to Constrain Input in ASP.NET.

Avoid Storing Sensitive Data in ViewState

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

If your ViewState does contain sensitive data, you should consider protecting it against eavesdropping by enabling ViewState encryption as described in the next section.

Encrypt ViewState if It Must Contain Sensitive Data

Using SSL protects ViewState while it is passed over the network between server and browser, but it does not stop it being viewed and modified on the user's computer.

To prevent ViewState from being viewed on the user's computer (and over the network), use ASP.NET ViewState encryption. Do not use it if your ViewState does not contain sensitive data because encryption significantly adds to the size of the ViewState and this impacts performance.

For more information about protecting ViewState, see How To: Configure the Machine Key in ASP.NET 2.0.

Additional Considerations

In addition to the preceding guidelines, consider the following.

  • Use Page.ViewStateUserKey to counter one-click attacks.

    If you authenticate your callers and use ViewState, set the Page.ViewStateUserKey property in the Page_Init event handler to prevent one-click attacks.

    void Page_Init (object sender, EventArgs e) {
       ViewStateUserKey = Session.SessionID;
    }
    
    

    Set the property to a value you know is unique to each user, such as a session ID, user name, or user identifier.

    A one-click attack occurs when an attacker creates a Web page (.htm or .aspx) that contains a hidden form field named __VIEWSTATE that is already filled with ViewState data. The ViewState can be generated from a page that the attacker had previously created, such as a shopping cart page with 100 items. The attacker lures an unsuspecting user into browsing to the page, and then the attacker causes the page to be sent to the server where the ViewState is valid. The server has no way of knowing that the ViewState originated from the attacker. ViewState validation and HMACs do not counter this attack because the ViewState is valid and the page is executed under the security context of the user.

    By setting the ViewStateUserKey property, when the attacker browses to a page to create the ViewState, the property is initialized to his or her name. When the legitimate user submits the page to the server, it is initialized with the attacker's name. As a result, the ViewState HMAC check fails and an exception 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.

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:

  • Avoid plaintext passwords in configuration files.
  • Use platform features to manage keys where possible
  • Do not pass sensitive data from page to page
  • Protect sensitive data over the wire
  • Do not cache sensitive data

Avoid Plaintext Passwords in Configuration Files

The <sessionState> and <identity> elements in the Machine.config and Web.config files have userName and password attributes. If you store credentials in these sections, encrypt them by using one of the protected configuration providers.

For more information, 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 Platform Features to Manage Keys Where Possible

Use platform features where possible to avoid managing keys yourself. For example, by using DPAPI, the encryption key is derived from an account's password, so Windows handles this for you.

Do Not Pass Sensitive Data from Page to Page

Avoid using any of the client-side state management options, such as ViewState, 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 to help protect data exchange.

Protect Sensitive Data Over the Wire

Consider where items of 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 browser, consider using SSL. If you need to protect server-to-server communication, such as between your Web server and database, consider 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 help protect session state. 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 protect the session data, including the session store.

The following recommendations help you build secure session management:

  • Do not rely on client-side state management options.
  • Protect your out-of-process state service.
  • Protect SQL Server session state.

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.

Protect Your Out-of-Process State Service

If you use mode=StateServer on the <sessionState> element, use the following recommendations to help secure session state:

  • Use a least-privileged account to run the state service. By default, the state service runs using the Network Service local, least-privileged account. You should not need to change this configuration

  • Protect the channel. If the state service is located on a remote server and you are concerned about the network eavesdropping threat, protect the channel to the remote state store by using IPSec to ensure the user state remains private and unaltered.

  • Consider changing the default port. The ASP.NET state service listens on port 42424. To avoid using this default, well-known port, you can change the port by editing the following registry key:

    HKLM\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters

    The port number is defined by the Port named value. If you change the port number in the registry, for example, to 45678, you must also change the connection string on the <sessionState> element, as follows:

    stateConnectionString="tcpip=127.0.0.1:45678"

  • Encrypt the state connection string. Because this element contains service connection details, you should encrypt it by using one of the protected configuration providers. For more information, 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.

Protect SQL Server Session State

If you use mode="SQLServer" on the <sessionState> element, use the following recommendations to help protect session state:

  • Use Windows authentication to the database. This means that passwords are not sent over the network to the database. It also means you do not have to store user names and passwords in the database connection string.
  • Encrypt the <sessionState> section. Because this element contains database connection details, you should encrypt it by using one of the protected configuration providers. For more information, 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.
  • Limit the application's login in the database. Restrict the login in the database so that it can only be used to access the necessary state tables and the stored procedures used by ASP.NET to query the database. To do this, create a SQL Server login for your ASP.NET process account, and then map the login to a database user in the state store database. Assign the database user to a database role and grant execute permissions for the stored procedures that are provided with the ASPState database.
  • Protect the channel. To protect sensitive session state over the network between the Web server and remote state store, protect the channel to the two servers using IPSec or SSL. This provides privacy and integrity for the session state data across the network. If you use SSL, you must install a server certificate on the database server.

Auditing and Logging

You should audit and log activity across the tiers of your application. Using logs, you can detect suspicious-looking activity. This frequently provides early indications of a full-blown attack and the logs help address the repudiation threat where users deny their actions. Log files may be required in legal proceedings to prove the wrongdoing of individuals. Generally, auditing is considered most authoritative if the audits are generated at the precise time of resource access and by the same routines that access the resource.

When implementing auditing and logging in ASP.NET applications:

  • Use health monitoring to log and audit events.
  • Instrument for user management events.
  • Instrument for unusual activity.
  • Instrument for significant business operations.
  • Consider using an application-specific event source.
  • Protect audit and log files.

Use Health Monitoring to Log and Audit Events

ASP.NET version 2.0 introduces a health monitoring feature that you should use to log and audit events. By default, health monitoring is enabled for ASP.NET version 2.0 applications and all Web infrastructure error events (inheriting from System.Web.Management.WebErrorEvent) and all audit failure events (inheriting from System.Web.Management.WebFailureAuditEvent) are written to the event log. The default configuration is defined in the <healthMonitoring> element in the machine-level Web.config file. To audit additional security related events, you create custom event types by deriving from one of the built-in types.

The health monitoring feature has built-in providers that allow you to log events in an e-mail message (SimpleMailWebEventProvider, TemplatedMailWebEventProvider), to SQL Server (SqlWebEventProvider), to the event log (EventLogWebEventProvider), as ASP.NET trace output (TraceWebEventProvider), or to the Windows Management Instrumentation (WMI) Web event provider (WMIWebEventProvider). You can configure health monitoring in the machine or application Web.config file to modify the events that are logged and the way in which they are logged.

For more information, see How To: Use Health Monitoring in ASP.NET 2.0.

Instrument for User Management Events

Instrument your application and monitor user management events such as password resets, password changes, account lockout, user registration, and authentication events. Doing this helps you to detect and react to potentially suspicious behavior. It also enables you to gather operations data; for example, to track who is accessing your application and when user account passwords need to be reset.

By default, ASP.NET health monitoring records all WebFailureAuditEvents, which ASP.NET raises when user authentication fails in forms authentication with the membership system. ASP.NET also raises a WebFailureAuditEvent when authorization is not granted to a resource protected by file authorization and URL authorization.

For more information about auditing password changes and account lockout events, see How To: Instrument ASP.NET 2.0 Applications for Security.

Instrument for Unusual Activity

Instrument your application and monitor events that might indicate unusual or suspicious activity. This enables you to detect and react to potential problems as early as possible. Unusual activity might be indicated by:

  • Replays of old authentication tickets.
  • To many login attempts over a specific period of time.

Replays of old authentication tickets automatically raise an ExpiredTicketFailure event. To raise an event for too many login attempts, if you are using the membership system and the Login controls, you can write an event handler for the LoginError event of the Login control. In the handler, call Membership.GetUser(Login1.UserName).IsLockedOut to determine if there have been too many login attempts for the user. You can then raise a custom event to indicate that the account has been locked out.

For more information about auditing password changes and account lockout events, see How To: Instrument ASP.NET 2.0 Applications for Security.

Instrument for Significant Business Operations

Use ASP.NET health monitoring to track significant business operations. For example, instrument your application to record access to particularly sensitive methods and business logic. To do this, create a custom event class that inherits from System.Web.Management.WebSuccessAuditEvent or System.Web.Management.WebFailureAuditEvent and raise that event in the appropriate methods. For more information see How To: Instrument ASP.NET 2.0 Applications for Security.

Consider Using an Application-specific Event Source

By default, the EventLogWebEventProvider writes to the application event log by using an event source named "ASP.NET xxxxxx", where xxxxxx is the .NET Framework version number. This is not configurable. If you want events generated by the ASP.NET health monitoring feature to use an application-specific event source, you must create a custom event provider (inherit from EventLogWebEventProvider) and override the ProcessEvent method to write to the event log (by calling EventLog.Write) using the desired event source.

When using a custom event source, you need to create the event source at installation time when administrator privileges are available. If you are unable to create new 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>

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.

Protect Audit and Log Files

Protect audit and log files using Windows ACLs and restrict access to the log files. If you log events to SQL Server or to some custom event sink, use appropriate access controls to limit access to the event data. For example, grant write access to the account or accounts used by your application, grant full control to administrators, and read-only access to operators.

This makes it more difficult for attackers to tamper with log files to cover their tracks. Minimize the number of individuals who can manipulate the log files. Authorize access only to highly trusted accounts such as administrators.

Additional Considerations

In addition to the preceding guidelines, consider the following measures for auditing and logging:

  • Log application events on a separate, protected server. This helps to ensure that logs cannot be tampered by attackers.
  • Assign appropriate permissions to the log files. Logs should be written by a process with write permission only. Logs should be read by users with administrative access.
  • Log application events with sufficient details. Provide sufficient detail to permit reconstruction of system activity.
  • Use performance counters for high volume, per-request events. This helps to minimize the impact on performance.

Deployment Considerations

To avoid introducing vulnerabilities when you deploy your application into a production environment:

  • Use a least-privileged account for running ASP.NET applications.
  • Encrypt configuration sections that store sensitive data.
  • Consider your key storage location.
  • Block protected file retrieval by using HttpForbiddenHandler.
  • Configure the MachineKey to use the same keys on all servers in a Web farm.
  • Lock configuration settings to enforce policy settings.

Use a Least-Privileged Account for Running ASP.NET Applications

On IIS 5, ASP.NET Web applications run by default using the least privileged ASPNET account. On IIS 6, they run inside an application pool using the least privileged Network Service account. If you need to use a custom service account to run your ASP.NET Web application, create a least-privileged account. You might need to create a custom service account to isolate your application from other applications on the same server or to be able to audit each application separately.

To create a least privileged account

  1. Create a local or domain Windows account.

  2. Run the following Aspnet_regiis.exe command to assign the relevant ASP.NET permissions to the account:

    aspnet_regiis.exe -ga machineName\userName

    On Windows Server 2003, running the Aspnet_regiis.exe -ga command will add the account to the IIS_WPG group. The IIS_WPG group provides the Log on as a batch job user right and ensures that the necessary file system permissions are granted.

    Note   With ASP.NET Beta 2, the Aspnet_regiis.exe–ga command does not add the account to the IIS_WPG group. However, it will do so prior to the final release of ASP.NET 2.0.

  3. Use the Local Security Policy tool to grant the Windows account the Deny logon locally user right. This reduces the privileges of the account and prevents anyone logging onto Windows locally with the account.

  4. Use IIS Manager to create an application pool running under the new account's identity and assign your ASP.NET application to the pool.

For more information, see How To: Create a Service Account for an ASP.NET 2.0 Application.

Encrypt Configuration Sections That Store Sensitive Data

In ASP.NET version 1.1, you could use the Aspnet_setreg.exe tool to encrypt certain elements in the Web.config and Machine.config files that contain credentials. This tool replaces the credentials in the configuration file with a pointer to a registry key that holds the DPAPI encrypted data. In ASP.NET version 2.0, you can use either the DPAPI or RSA protected configuration providers to encrypt specific sections that contain sensitive data. The sections that usually contain sensitive information that you need to encrypt include <appSettings>, <connectionStrings>, <identity>, and <sessionState>.

To encrypt the <connectionStrings> section by using the DPAPI provider with the machine-key store (the default configuration), run the following command from a command window:

aspnet_regiis -pe "connectionStrings" -app "/MachineDPAPI" -prov "DataProtectionConfigurationProvider"

  • -pe: Specifies the configuration section to encrypt.
  • -app: Specifies your Web application's virtual path. If your application is nested, you need to specify the nested path from the root directory; for example, "/test/aspnet/MachineDPAPI".
  • -prov: Specifies the provider name.

If you need to encrypt configuration file data on multiple servers in a Web farm, you should use RSA protected configuration provider because of the ease with which you can export RSA key containers. For more information, 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.

Consider Your Key Storage Location

The DPAPI and RSA protected configuration providers used to encrypt sensitive data in configuration files can use either machine stores or user stores for key storage. You can either store the key in the machine store and create an ACL for your specific application identity or store the key in a user store. In the latter case, you need to load the user account's profile to access the key.

Use machine-level key storage when:

  • Your application runs on its own dedicated server with no other applications.
  • You have multiple applications on the same server that run using the same identity and you want those applications to be able to share sensitive information and the same encryption key.

Use user-level key storage if you run your application in a shared hosting environment and you want to ensure that your application's sensitive data is not accessible to other applications on the server. In this scenario, each application should have a separate identity so they all have their own individual and private key stores.

Block Protected File Retrieval by Using HttpForbiddenHandler

To prevent protected files being downloaded over HTTP, map them to the ASP.NET HttpForbiddenHandler. HTTP handlers are located in the machine-level Web.config file beneath the <httpHandlers> element. HTTP handlers are responsible for processing Web requests for specific file extensions.

Many well-known file extensions are already mapped to System.Web.HttpForbiddenHandler. For .NET Framework resources, if you do not use a particular file extension, map the extension to System.Web.HttpForbiddenHandler in the machine-level Web.config file, as shown here.

<add verb="*" path="*.asmx" type="System.Web.HttpForbiddenHandler" />
  

This example assumes that your Web server does not host Web services, so the .asmx file extension is mapped to System.Web.HttpForbiddenHandler. If a client requests a path that ends with .asmx, ASP.NET returns a message that says, "This type of page is not served." If your application uses other files with extensions not already mapped, also map these to System.Web.HttpForbiddenHandler.

Configure the MachineKey to Use the Same Keys on All Servers in a Web Farm

If you deploy your application in a Web farm, you must ensure that the configuration files on each server share hashing and encryption keys. These are used by ASP.NET to protect ViewState and forms authentication tickets. Manually generated, common keys are required because you cannot guarantee which server will handle successive requests.

With manually generated key values, the <machineKey> settings should be similar to the following example.

<machineKey validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B4
             4EA7DD4374267A75D7AD972A119482D15A4127461DB1DC347C1A63AE5F1CCFAACFF1B72A7F0A281B"
             decryptionKey="261F793EB53B761503AC445E0CA28DA44AA9B3CF06263B77"
             decryption="3DES"
             validation="SHA1" />
  

For more information about how to manually generate keys, see How To: Configure MachineKey in ASP.NET 2.0.

Lock Configuration Settings to Enforce Policy Settings

To prevent individual application's from being able to override configuration settings with their own Web.config files, apply configuration settings centrally in the machine-level Web.config file located in the following directory: %Windir%\Microsoft.NET\Framework\{version}\CONFIG.

To lock the configuration settings for all Web applications on a Web server, place the configuration settings inside a <system.web> element nested within a <location> element and set the allowOverride attribute to false.

The following example enforces the use of Windows authentication for all Web applications on the server.

<location allowOverride="false">
 <system.web>
   <authentication mode="Windows"/>
 </system.web>
</location>
  

If you need to apply and lock settings for a specific Web application, use the path attribute on the <location> element to identify the Web application as shown here.

<location path="Default Web Site/VDirName">
  <system.web>
    <authentication mode="Windows"/>
    <identity impersonate="false"/>
  </system.web>
</location>
  

If you specify the path, it must be fully qualified and include the Web site name and virtual directory name.

Communication Security

When passing sensitive data, such as credentials or application-specific data, across networks, you need to consider the security of the communication channel:

  • Consider SSL vs. IPSec.
  • Optimize pages that use SSL.

Consider SSL vs. IPSec

If your servers are not inside a physically secure data center where the network eavesdropping threat is considered insignificant, you need to use an encrypted communication channel to protect data sent between servers. SSL and IPSec can both be used to help protect communication between servers by encrypting traffic. Use SSL when you need granular channel protection for a particular application instead of for all applications and services running on a computer.

Use IPSec to help protect the communication channel between two servers and to restrict which computers can communicate with one another. For example, you can help protect a database server by establishing a policy that permits requests only from a trusted client computer such as an application or Web server. You can also restrict communication to specific IP protocols and TCP/UDP ports.

Optimize Pages That Use SSL

Using SSL is expensive. Use SSL only for pages that require it. This includes pages that contain or capture sensitive data, such as pages that accept credit card numbers and passwords. Use SSL only if the following conditions are true:

  • You want to encrypt the page data.
  • You want to guarantee that the server to which you send the data is the server that you expect.

For pages where you must use SSL, follow these guidelines:

  • Make the page size as small as possible.
  • Avoid using graphics that have large file sizes. If you use graphics, use graphics that have smaller file sizes and resolution. Or, use graphics from a site that is not secure. However, when you use graphics from a site that is not secure, Web browsers display a dialog box that asks the user if the user wants to display the content from the site that is not secure.

Companion Guidance

The following companion guidance is in the sequence that is referenced by this document. This is useful if you want to print the How To documents and refer to them in order.

Additional Resources

Feedback

Provide feedback by using either a Wiki or e-mail:

We are particularly interested in feedback regarding the following:

  • Technical issues specific to recommendations
  • Usefulness and usability issues

Technical Support

Technical support for the Microsoft products and technologies referenced in this guidance is provided by Microsoft Support Services. For product support information, please visit the Microsoft Product Support Web site at https://support.microsoft.com.

Community and Newsgroups

Community support is provided in the forums and newsgroups:

To get the most benefit, find the newsgroup that corresponds to your technology or problem. For example, if you have a problem with ASP.NET security features, you would use the ASP.NET Security forum.

Contributors and Reviewers

  • External Contributors and Reviewers: Eric Marvets, Dunn Training and Consulting; Jason Taylor, Security Innovation; Rudolph Araujo, Foundstone Professional Services
  • Microsoft Product Group: Stefan Schackow
  • Microsoft Services and PSS Contributors and Reviewers: Adam Semel, Tom Christian, Wade Mascia, Microsoft Corporation
  • Test team: Larry Brader, Microsoft Corporation; Nadupalli Venkata Surya Sateesh, Sivanthapatham Shanmugasundaram, Infosys Technologies Ltd.
  • Edit team: Nelly Delgado, Microsoft Corporation; Tina Burden McGrayne, TinaTech Inc.
  • Release Management: Sanjeev Garg, Microsoft Corporation.

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.