.gif)
patterns & practices Developer Center J.D. Meier, Alex Mackman, Blaine Wastell, Prashant Bansode, Andy Wigley, Kishore Gopalan
Microsoft Corporation
August 2005
Applies To
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.
- Health monitoring. 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
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 Cookie Names and Paths
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:
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.
Protect Your Authorization 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:
- Create a SQL Server login for the account.
- Map the login to a database user in the required database.
- Place the database user in a database role.
- 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#" runat="server">
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 applicatio