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.

See the Landing Page for the starting point and complete overview of Building Secure ASP.NET Applications.

Summary: This chapter presents recommendations and guidance that will help you develop a secure data access strategy. Topics covered include using Windows authentication from ASP.NET to the database, securing connection strings, storing credentials securely in a database, protecting against SQL injection attacks and using database roles. (33 printed pages)

When you build Web-based applications, it is essential that you use a secure approach to accessing and storing data. This chapter addresses some of the key data access issues. It will help you:

Choose between Microsoft® Windows® operating system authentication and SQL authentication when connecting to SQL Server.

Store connection strings securely.

Decide whether to flow the original caller's security context through to the database.

Take advantage of connection pooling.

Protect against SQL injection attacks.

Store credentials securely within a database.

The chapter also presents various trade-offs that relate to the use of roles, for example, roles in the database versus role logic applied in the middle tier. Finally, a set of core recommendations for data access are presented.

Introducing Data Access Security

Figure 12.1 shows key security issues associated with data access.

Figure 12.1. Key data access security issues

The key issues shown in Figure 12.1 and discussed throughout the remainder of this chapter are summarized below:

Using an appropriate identity or identities to access the database. Data access may be performed by using the process identity of the calling process, one or more service identities, or the original caller's identity (with impersonation/delegation). The choice is determined by your data access modeltrusted subsystem or impersonation/delegation.

Securing data that flows across the network. For example, securing login credentials and sensitive data passed to and from SQL Server.

Note Login credentials are only exposed on the network if you use SQL authentication, not Windows authentication.

SQL Server supports SSL, with server certificates. IPSec can also be used to encrypt traffic between the client computer (for example, a Web or application server) and database server.

SQL Server Gatekeepers

The chosen data store used to maintain the database connection string.

The SQL Server login (as determined by the server name specified in the connection string).

The database login (as determined by the database name specified in the connection string).

Permissions attached to individual database objects.

Permissions may be assigned to users, groups, or roles.

Trusted Subsystem vs. Impersonation/Delegation

Granularity of access to the database is a key factor to consider. You must consider whether you need user-level authorization at the database (which requires the impersonation/delegation model), or whether you can use application role logic within the middle tier of your application to authorize users (which implies the trusted subsystem model).

If your database requires user-level authorization, you need to impersonate the original caller. While this impersonation/delegation model is supported, you are encouraged to use the trusted subsystem model, where the original caller is checked at the IIS/ASP.NET gate, mapped to a role, and then authorized based on role membership. System resources for the application are then authorized at the application or role level using service accounts, or using the application's process identity (such as the ASP.NET process identity account).

There are a number of key factors that you should consider when connecting to SQL Server for data access. These are summarized below and elaborated upon in subsequent sections:

What type of authentication should you use? Windows authentication offers improved security, but firewalls and non-trusting domain issues may force you to use SQL authentication. If so, you should ensure that your application's use of SQL authentication is as secure as possible, as discussed in the "SQL Authentication" section later in this chapter.

Single user role versus multiple user roles. Does your application need to access SQL using a single account with a fixed set of permissions within the database, or are multiple (role-based) accounts required depending on the user of the application?

Caller identity. Does the database need to receive the identity of the original caller through the call context either to perform authorization or to perform auditing, or can you use one or more trusted connections and pass the original caller identity at the application level?

For the operating system to flow the original caller's identity, it requires impersonation/delegation in the middle tier. This dramatically reduces the effectiveness of connection pooling. Connection pooling is still enabled, but it results in many small pools (for each separate security context), with little if any reuse of connections.

Are you sending sensitive data to and from the database server? While Windows authentication means that you do not pass user credentials over the network to the database server, if your application's data is sensitive (for example, employee details or payroll data), then this should be secured using IPSec or SSL.

Authentication

This section discusses how you should authenticate clients to SQL Server and how you choose an identity to use for database access within client applications, prior to connecting to SQL Server.

Windows Authentication

Windows authentication is more secure than SQL authentication for the following reasons:

Credentials are managed for you and the credentials are not transmitted over the network.

You have used the trusted subsystem model and you connect to SQL Server using a single fixed identity. If you are connecting from ASP.NET, this assumes that the Web application is not configured for impersonation.

In this scenario, use the ASP.NET process identity or a serviced component identity (obtained from the account used to run an Enterprise Services server application).

You are intentionally delegating the original caller's security context by using delegation (and are prepared to sacrifice application scalability by foregoing database connection pooling).

Consider the following key points when you use Windows authentication to connect to SQL Server:

Use the principle of least privilege for the ASP.NET process account. Avoid giving the ASP.NET process account the "Act as part of the operating system" privilege.

Determine which code requires additional privileges, and place it within serviced components that run in out-of-process Enterprise Services applications.

More information

For more information about accessing network resources from ASP.NET and choosing and configuring an appropriate account to run ASP.NET, see Chapter 8, ASP.NET Security.

Using Windows authentication

You have the following options when you use Windows authentication to connect to SQL Server from an ASP.NET application (or Web service, or remote component hosted by ASP.NET):

Recommendation

The recommendation is to configure the local ASP.NET process identity by changing the password to a known value on the Web server and create a mirrored account on the database server by creating a local user with the same name and password. Further details for this and the other approaches are presented below.

Note With IIS 6.0 running on Windows Server 2003, the default ASP.NET process identity is identified as domainName\MachineName$ in the domain. Therefore, in a trusted domain scenario, this identity can be used for Windows authentication.

Using the ASP.NET process identity

If you connect to SQL Server directly from an ASP.NET application (or Web service, or remote component hosted by ASP.NET), use the ASP.NET process identity. This is a common approach and the application defines the trust boundary, that is, the database trusts the ASP.NET account to access database objects.

You have three options:

Use mirrored default ASP.NET process identity local accounts.

Use mirrored, custom local accounts.

Use a custom domain account.

Use mirrored default ASP.NET process identity local accounts

This is the simplest approach and is the one generally used when you own the target database (and can control the administration of local database-server accounts). With this option, you use the default ASP.NET process identity's least-privileged local account to run ASP.NET and then create a duplicated account on the database server.

Note This approach has the added advantages that it works across non-trusting domains and through firewalls. The firewall may not open sufficient ports to support Windows authentication.

Use mirrored, custom local accounts

This approach is the same as the previous approach except that you do not use the default ASP.NET process identity account. This means two things:

You will need to create a custom local account with appropriate permissions and privileges.

You are no longer using the default account created by the .NET Framework installation process. Your company may have a policy not to use default installation accounts. This can potentially raise the security bar of your application.

Use a custom domain account

This approach is similar to the previous one except that you use a least-privileged domain account instead of a local account. This approach assumes that client and server computers are in the same or trusting domains. The main benefit is that credentials are not shared across machines; the machines simply give access to the domain account. Also, administration is easier with domain accounts.

Implementing mirrored ASP.NET process identity

In order to use mirrored accounts to connect from ASP.NET to a database, you need to perform the following actions:

Use User Manager on the Web server to reset the ASPNET account's password to a known strong password value.

Important If you change the ASPNET password to a known value, the password in the Local Security Authority (LSA) on the local computer will no longer match the account password stored in the Windows Security Account Manager (SAM) database. If you need to revert to the AutoGenerate default, you must do the following:

Run Aspnet_regiis.exe to reset ASP.NET to its default configuration. For more information, see article Q306005, HOWTO: Repair IIS Mapping After You Remove and Reinstall IIS, in the Microsoft Knowledge Base. When you do this, you get a new account and a new Windows Security Identifier (SID). The permissions for this account are set to their default values. As a result, you need to explicitly reapply permissions and privileges that you had originally set for the old ASPNET account.

Explicitly set the password in Machine.config.

<processModel userName="machine" password="YourStrongPassword" .

You should protect Machine.config from unauthorized access by using Windows ACLs. For example, restrict Machine.config from the IIS anonymous Internet user account.

Create a mirrored account (with the same name and password) on the database server.

Within the SQL database, create a server login for the newly created local account and then map the login to a user account within the required database. Then create a database user role, add the database user to the role, and configure the appropriate database permissions for the role.

Connecting to SQL Server using Windows authentication

To connect to SQL Server using Windows authentication

Within the client application, use a connection string that contains either "Trusted Connection=Yes", or "Integrated Security=SSPI". The two strings are equivalent and both result in Windows authentication (assuming that your SQL Server is configured for Windows authentication). For example:

"server=MySQL; Integrated Security=SSPI; database=Northwind"

Note The identity of the client making the request (that is, the client authenticated by SQL Server) is determined by the client's thread impersonation token (if the thread is currently impersonating) or the client's current process token.

Using fixed identities within ASP.NET

With this approach, you configure your ASP.NET application to impersonate a specified, fixed identity, by using the following element in Web.config.

This becomes the default identity that is used when you connect to network resources, including databases.

This becomes the default identity that is used when you connect to network resources, including databases.

When using fixed identities, you need ensure the following:

The credentials are encrypted.

The SQL server is configured to grant access to the fixed identity.

Using serviced components

You can develop a serviced component specifically to contain data access code. With serviced components, you can access the database by either hosting your component in an Enterprise Services (COM+) server application running under a specific identity, or you can write code that uses the LogonUser API to perform impersonation.

Using out of process serviced components raises the security bar because process hops make an attacker's job more difficult, particularly if the processes run with different identities. The other advantage is that you can isolate code that requires more privilege from the rest of the application.

Calling LogonUser and impersonating a specific Windows identity

You should not call LogonUser directly from ASP.NET. In Windows 2000, this approach requires you to give the ASP.NET process identity "Act as part of the operating system".

Note This restriction has been removed in Microsoft Windows Server 2003.

A preferred approach is to call LogonUser outside of the ASP.NET process using a serviced component in an Enterprise Services server application, as discussed above.

Using the original caller's identity

For this approach to work, you need to use Kerberos delegation and impersonate the caller to the database, either directly from ASP.NET or from a serviced component.

From ASP.NET add the following to your application's Web.config.

<identity impersonate="true" />

From a serviced component, call CoImpersonateClient.

Using the anonymous Internet user account

As a variation of the previous approach, for scenarios where your application uses Forms or Passport authentication (which implies IIS anonymous authentication), you can enable impersonation within your application's Web.config in order to use the anonymous Internet user account for database access.

<identity impersonate="true" />

With IIS configured for anonymous authentication, this configuration results in your Web application's code running using the anonymous Internet user's impersonation token. In a Web hosting environment, this has the advantage of allowing you to separately audit and track database access from multiple Web applications.

More Information

For more information about how to configure IIS to use anonymous user account refer to Chapter 8, ASP.NET Security.

When can't you use Windows authentication?

Certain application scenarios may prevent the use of Windows authentication. For example:

Your database client and database server are separated by a firewall which prevents Windows authentication.

Your application needs to connect to one or more databases using multiple identities.

You are connecting to databases other than SQL Server.

You don't have a secure way within ASP.NET to run code as a specific Windows user. Either you can't (or won't) forward the original caller's security context, and/or you want to use a dedicated service account rather than grant logons to end users.

Specifying a user name and password in Machine.config (on the <processModel> element) or in Web.config (on the <identity> element) in order to run the ASP.NET worker process or your application is less secure than taking explicit steps to protect standard SQL credentials.

In these scenarios, you will have to use SQL authentication (or the database's native authentication mechanism), and you must:

Protect database user credentials on the application server.

Protect database user credentials while in transit from the server to the database.

If you do use SQL authentication, there are various ways in which you can make SQL authentication more secure. These are highlighted in the next section.

SQL Authentication

If your application needs to use SQL authentication, you need to consider the following key points:

Use a least-privileged account to connect to SQL.

Credentials are passed over the wire so they must be secured.

The SQL connection string (which contains credentials) must be secured.

Connection string types

If you connect to a SQL Server database using credentials (user name and password) then your connection string looks like this:

Note that this does not address the issue of storing credentials in plain text in your Web.config files. All you've done so far is limit the scope of damage possible in the event of a compromise, by using a least-privileged account. To further raise the security bar, you should encrypt the credentials.

Note If you selected a case-sensitive sort order when you installed SQL Server, your login ID is also case-sensitive.

Passing credentials over the network

When you connect to SQL Server with SQL authentication, the user name and password are sent across the network in clear text. This can represent a significant security concern. For more information about how to secure the channel between an application or Web server and database server, see Secure Communication later in this chapter.

Securing SQL connection strings

User names and passwords should not be stored in clear text in configuration files. For details about how to store connection strings securely, see "Storing Database Connection Strings" later in this chapter.

Authenticating Against Non-SQL Server Databases

The typical issues you may encounter when connecting to non-SQL databases are similar to scenarios where you need to use SQL authentication. You may need to supply explicit credentials if the target resources do not support Windows authentication. To secure this type of scenario, you must store the connection string securely and you must also secure the communication over the network (to prevent interception of credentials).

More information

For more information about securing the channel to the database server, see Secure Communication later in this chapter.

Authorization

SQL Server provides a number of role-based approaches for authorization. These revolve around the following thee types of roles supported by SQL Server:

User-defined Database Roles. These are used to group together users who have the same security privileges within the database. You add Windows user or group accounts to user database roles and establish permissions on individual database objects (stored procedures, tables, views, and so on) using the roles.

Application Roles. These are similar to user database roles in that they are used when establishing object permissions. However, unlike user database roles, they do not contain users or groups. Instead, they must are activated by an application using a built-in stored procedure. Once active, the permissions granted to the role determine the data access capabilities of the application.

Application roles allow database administrators to grant selected applications access to specified database objects. This is in contrast to granting permissions to users.

Fixed Database Roles. SQL Server also provides fixed server roles such as db_datareader and db_datawriter. These built-in roles are present in all databases and can be used to quickly give a user read specific (and other commonly used) sets of permissions within the database.

For more information about these various role types (and also fixed server roles which are similar to fixed database roles but apply at the server level instead of the database level), see SQL Server Books Online.

Using Multiple Database Roles

If your application has multiple categories of users, and the users within each category require the same permissions within the database, your application requires multiple roles.

Each role requires a different set of permissions within the database. For example, members of an Internet User role may require read-only permissions to the majority of tables within a database, while members of an Administrator or Operator role may require read/write permissions.

Options

To accommodate these scenarios, you have two main options for role-based authorization within SQL Server:

User-defined SQL Server Database Roles. These are used to assign permissions to database objects for groups of users who have the same security permissions within the database.

When you use user-defined database roles, you check at the gate, map users to roles, (for example, in an ASP.NET Web application or in a middle-tier serviced component in an Enterprise Services server application) and use multiple identities to connect to the database, each of which maps to a user-defined database role.

SQL Application Roles. These are similar to user-defined database roles in that they are used when you assign permissions to database objects. However, unlike user-defined database roles, they do not contain members and are activated from individual applications by using a built-in stored procedure.

When you use application roles, you check at the gate, map users to roles, connect to the database using a single, trusted, service identity, and activate the appropriate SQL application role.

User-Defined database roles

If you elect to use user-defined database roles, you must:

Create multiple service accounts to use for database access.

Map each account to a user-defined database role.

Establish the necessary database permissions for each role within the database.

Authorize users within your application (ASP.NET Web application, Web service, or middle tier component) and then use application logic within your data access layer to determine which account to connect to the database with. This is based on the role-membership of the caller.

Declaratively, you can configure individual methods to allow only those users that belong to a set of roles. You then add imperative role-checks within method code to determine precise role membership, which determines the connection to use.

More information

Application roles

Create a single service account to use for database access (this may be the process account used to run the ASP.NET worker process, or an Enterprise Services application).

Create a set of SQL application roles within the database.

Establish the necessary database permissions for each role within the database.

Authorize users within your application (ASP.NET Web application, Web service or middle tier component), and use application logic within your data access layer to determine which application role to activate within the database. This is based on the role-membership of the caller.

Figure 12.5 illustrates this approach.

Figure 12.5. Using multiple SQL application roles

In Figure 12.5, the identity ServiceIdentity1 that is used to access the database is usually obtained from the ASP.NET worker process or from an Enterprise Services server application process identity.

With this approach, the same service identity (and therefore the same connection) is used to connect to SQL Server. SQL application roles are activated with the sp_setapprole built-in stored procedure, based on the role membership of the caller. This stored procedure requires the role name and a password.

Limitations of SQL application roles

The following are the key points that you must be aware of before you choose to use SQL application roles:

You need to manage credentials for the SQL application roles. You must call the sp_setapprole stored procedure passing a role name and password for each connection. If you are activating a SQL application role from managed code then having a clear text password embedded in the assembly is not safe.

SQL application role credentials are passed to the database in clear text. You should secure them on the network by using IPSec or SSL between the application server and database server.

After a SQL application role is activated on a connection it cannot be deactivated. It remains active until the connection closes. Also, you cannot switch between two or more roles on the same connection.

Use SQL application roles only when your application uses a single, fixed identity to connect to the database. In other words, use them only when your application uses the trusted subsystem model.

If the security context of the connection changes (as it would if the original caller's context were use to connect to the database), then SQL application roles do not work in conjunction with connection pooling.

Secure Communication

In most application scenarios you need to secure the communication link between your application server and database. You need to be able to guarantee:

Message Confidentiality. The data must be encrypted to ensure that it remains private.

Message Integrity. The data must be signed to ensure that it remains unaltered.

In some scenarios, all of the data passed between application server and database server must be secured, while in other scenarios, selected items of data sent over specific connections must be secured. For example:

In an intranet Human Resources application, some of the employee details passed between client and the database server are sensitive.

In Internet scenarios, such as secure banking applications, all of the data passed between the application server and database server must be secured.

If you are using SQL authentication, you should also secure the communication link to ensure that user names and passwords can not be compromised with network monitoring software.

The Options

You have two options for securing the network link between an application server and database server:

IPSec

SSL (using a server certificate on the SQL Server computer)

Note You must be running SQL Server 2000 to support the use of SSL. Earlier versions do not support it. The client must have the SQL Server 2000 client libraries installed.

Choosing an Approach

Whether or not you should use IPSec or SSL depends on a number of primarily environmental factors, such as firewall considerations, operating system and database versions, and so on.

Note IPSec is not intended as a replacement for application-level security. Today it is used as a defense in depth mechanism, or to secure insecure applications without changing them, and to secure non-TLS (for example, SSL) protocols from network-wire attacks.

Connecting with Least Privilege

Connecting to the database with least privilege means that the connection you establish only has the minimum privileges that you need within the database. Simply put, you don't connect to your database using the sa or database owner accounts. Ideally, if the current user is not authorized to add or update records, then the corresponding account used for their connection (which may be aggregated to an identity that represents a particular role) cannot add or update records in the database.

When you connect to SQL Server, your approach needs to support the necessary granularity that your database authorization requires. You need to consider what the database trusts. It can trust:

The application

Application-defined roles

The original caller

The Database Trusts the Application

Consider a finance application that you authorize to use your database. The finance application is responsible for managing user authentication and authorizing access. In this case, you can manage your connections through a single trusted account (which corresponds to either a SQL login or a Windows account mapped to a SQL login). If you're using Windows authentication, this would typically mean allowing the process identity of the calling application (such as the ASP.NET worker process, or an Enterprise Services server application identity) to access the database.

From an authorization standpoint, this approach is very coarse-grained, because the connection runs as an identity that has access to all database objects and resources needed by the application. The benefits of this approach are that you can use connection pooling and you simplify administration because you are authorizing a single account. The downside is that all of your users run with the same connection privileges.

The Database Trusts Different Roles

You can use pools of separate, trusted connections to the database that correspond to the roles defined by your application, for example, one connection that is for tellers, another for managers, and so on.

These connections may or may not use Windows authentication. The advantage of Windows authentication is that it handles credential management and doesn't send the credentials over the network. However, while Windows authentication is possible at the process or application level (as when you use a single connection to the database), there are additional challenges presented by the fact you need multiple identities (one per role).

Many applications use the LogonUser API to establish a Windows access token. The problem with this approach is two-fold:

You now have a credential management issue (your application has to securely store the account user name and password).

The LogonUser API requires that the calling process account have the "Act as part of the operating system" privilege. This means that you are forced to give the ASP.NET process account this privilege, which is not recommended. An alternative is to use SQL Authentication, but then you need to protect the credentials on the server and over the network.

Note This LogonUser restriction is lifted in Windows Server 2003.

The Database Trusts the Original Caller

In this case, you need to flow the original caller through multiple tiers to the database. This means that your clients need network credentials to be able to hop from one computer to the next. This requires Kerberos delegation.

Although this solution provides a fine-grained level of authorization within the database, because you know the identity of the original caller and can establish per user permissions on database objects, it impacts application performance and scalability. Connection pooling (although still enabled) becomes ineffective.

Creating a Least Privilege Database Account

The following steps are provided as a simple example to show you how to create a least privilege database account. While most database administrators are already familiar with these steps, many developers may not be and resort to using the sa or database owner account to force their applications to work.

This can create difficulties when moving from a development environment, to a test environment, and then to a production environment because the application moves from an environment that's wide open into a more tightly controlled setting, which prevents the application from functioning correctly.

You start by creating a SQL login for either a SQL account or a Windows account (user or group). You then add that login to a database user role and assign permissions to that role.

To set up a data access account for SQL

Create a new user account and add the account to a Windows group. If you are managing multiple users, you would use a group. If you are dealing with a single application account (such as a duplicated ASP.NET process account), you may choose not to add the account to a Windows group.

Enter the Windows group name into the Name field, and then click OK to close the SQL Server Login Properties dialog box.

Create a new database user in the database of interest that is mapped to the SQL server login.

Use Enterprise Manager and expand the Databases folder, and then expand the required database for which the login requires access.

Right-click Users, and then click New Database User.

Select the previously created Login name.

Specify a user name.

Configure permissions as discussed below.

Grant the database user Select permissions on the tables that need to be accessed and Execute permissions on any relevant stored procedures.

Note If the stored procedure and the table are owned by the same person, and access the table only through the stored procedure (and do not need to access the table directly), it is sufficient to grant execute permissions on the stored procedure alone. This is because of the concept of ownership chaining. For more information, see SQL Server Books online.

If you want the user account to have access to all the views and tables in the database, add them to the db_datareader role.

Storing Database Connection Strings Securely

There are a number of possible locations and approaches for storing database connection strings, each with varying degrees of security and configuration flexibility.

The Options

The following list represents the main options for storing connection strings:

Using DPAPI

Windows 2000 and later operating systems provide the Win32® Data Protection API (DPAPI) for encrypting and decrypting data. DPAPI is part of the Cryptography API (Crypto API) and is implemented in Crypt32.dll. It consists of two methodsCryptProtectData and CryptUnprotectData.

DPAPI is particularly useful in that it can eliminate the key management problem exposed to applications that use cryptography. While encryption ensures the data is secure, you must take additional steps to ensure the security of the key. DPAPI uses the password of the user account associated with the code that calls the DPAPI functions in order to derive the encryption key. As a result the operating system (and not the application) manages the key.

Why not LSA?

Many applications use the Local Security Authority (LSA) to store secrets. DPAPI has the following advantages over the LSA approach:

To use the LSA, a process requires administrative privileges. This is a security concern because it greatly increases the potential damage that can be done by an attacker who manages to compromise the process.

The LSA provides only a limited number of slots for secret storage, many of which are already used by the system.

Machine store vs. user store

DPAPI can work with either the machine store or user store (which requires a loaded user profile). DPAPI defaults to the user store, although you can specify that the machine store be used by passing the CRYPTPROTECT_LOCAL_MACHINE flag to the DPAPI functions.

The user profile approach affords an additional layer of security because it limits who can access the secret. Only the user who encrypts the data can decrypt the data. However, use of the user profile requires additional development effort when DPAPI is used from an ASP.NET Web application because you need to take explicit steps to load and unload a user profile (ASP.NET does not automatically load a user profile).

The machine store approach is easier to develop because it does not require user profile management. However, unless an additional entropy parameter is used, it is less secure because any user on the computer can decrypt data. (Entropy is a random value designed to make deciphering the secret more difficult). The problem with using an additional entropy parameter is that this must be securely stored by the application, which presents another key management issue.

Note If you use DPAPI with the machine store, the encrypted string is specific to a given computer and therefore you must generate the encrypted data on every computer. Do not copy the encrypted data across computers in a farm or cluster.

If you use DPAPI with the user store, you can decrypt the data on any computer with a roaming user profile.

DPAPI implementation solutions

This section presents two implementation solutions that show you how to use DPAPI from an ASP.NET Web application to secure a connection string (or a secret of any type). The implementation solutions described in this section are:

Using DPAPI from Enterprise Services. This solution allows you to use DPAPI with the user store.

Using DPAPI directly from ASP.NET. This solution allows you to use DPAPI with the machine store, which makes the solution easier to develop as DPAPI can be called directly from an ASP.NET Web application.

Using DPAPI from Enterprise Services

An ASP.NET Web application can't call DPAPI and use the user store because this requires a loaded user profile. The ASPNET account usually used to run Web applications is a non-interactive account and as such does not have a user profile. Furthermore, if the ASP.NET application is impersonating, the Web application thread runs as the currently authenticated user, which can vary from one request to the next.

This presents the following issues for an ASP.NET Web application that wants to use DPAPI:

Calls to DPAPI from an ASP.NET application running under the default ASPNET account will fail. This is because the ASPNET account does not have a user profile, as it is not used for interactive logons.

If an ASP.NET Web application is configured to impersonate its callers, the ASP.NET application thread has an associated thread impersonation token. The logon session associated with this impersonation token is a network logon session (used on the server to represent the caller). Network logon sessions do not result in user profiles being loaded.

To overcome this issue, you can create a serviced component (within an out-of-process Enterprise Services (COM+) server application) to call DPAPI. You can ensure that the account used to run the component has a user profile and you can use a Win32 service to automatically load the profile.

Note It is possible to avoid the use of a Win32 service by placing calls to Win32 profile management functions (LoadUserProfile and UnloadUserProfile) within the serviced component.

There are two drawbacks to this approach. First, calls to these APIs on a per-request basis would severely impact performance. Second, these APIs require that the calling code have administrative privileges on the local computer, which defeats the principle of least privilege for the Enterprise Services process account.

The Windows service control manager starts the Win32 service and automatically loads the user profile associated with the account under which the service runs. The same Windows account is used to run the Enterprise Services application.

The Win32 service calls a launch method on the serviced component that starts the Enterprise Services application and loads the serviced component.

The Web application retrieves the encrypted string from the Web.config file.

You can store the encrypted string by using an <appSettings> element within Web.config as shown below. This element supports arbitrary key-value pairs.

Note You can use Web.config or Machine.config to store encrypted connection strings. Machine.config is preferred as it is in a system directory outside of a virtual directory. This is discussed further in the next section, "Using Web.config and Machine.config."

The application calls a method on the serviced component to decrypt the connection string.

The serviced component interacts with DPAPI using P/Invoke to call the Win32 DPAPI functions.

The decrypted string is returned to the Web application.

Note To store encrypted connection strings in the Web.config file in the first place, write a utility application that takes the connection strings and calls the serviced component's EncryptData method to obtain the encrypted string. It is essential that you run the utility application while logged on with the same account that you use to run the Enterprise Services server application.

Using DPAPI directly from ASP.NET

If you use the machine store (and call the DPAPI functions with the CRYPTPROTECT_LOCAL_MACHINE flag) you can call the DPAPI functions directly from an ASP.NET Web application (because you don't need a user profile).

However, because you are using the machine store, any Windows account that can log on to the computer has access to the secret. A mitigating approach is to add entropy but this requires additional key management.

As alternatives to using entropy with the machine store, consider the following options:

Use Windows ACLs to restrict access to the encrypted data (whether the data is stored in the file system or registry).

Using Web.config and Machine.config

Storing plain text passwords in Web.config is not recommended. By default, the HttpForbiddenHandler protects the file from being downloading and viewed by malicious users. However, users who have access directly to the folders that contain the configuration files can still see the user name and password.

Machine.config is considered a more secure storage location than Web.config because it is located in a system directory (with ACLs) outside of a Web application's virtual directory. Always lock down Machine.config with ACLs.

More information

For more information about securing Machine.config, see Chapter 8, ASP.NET Security.

Using UDL Files

The OLE DB .NET Data Provider supports UDL file names in its connection string. To reference a UDL file, use "File Name=name.udl" within the connection string.

Important This option is only available if you use the OLE DB .NET Data Provider to connect to the database. The SQL Server .NET Data Provider does not use UDL files.

It is not recommended to store UDL files in a virtual directory along with other application files. You should store them outside the Web application's virtual directory hierarchy and then secure the file or the folder containing the file with Windows ACLs. You should also consider storing UDL files on a separate logical volume from the operating system to protect against possible file canonicalization and directory traversal bugs.

ACL granularity

UDL files (or indeed any text file) offer added granularity when you apply ACLs in comparison to Machine.config. The default ACLs associated with Machine.config grant access to a wide variety of local and remote users. For example, Machine.config has the following default ACLs:

By contrast, you can lock down your own application's UDL file much further. For example, you can restrict access to Administrators, the System account, and the ASP.NET process account (which requires read access) as shown below.

BUILTIN\Administrators:F
MachineName\ASPNET:R
NT AUTHORITY\SYSTEM:F

Note Because UDL files can be modified externally to any ADO.NET client application, connection strings that contain references to UDL files are parsed every time the connection is opened. This can impact performance and it is therefore recommended, for best performance, that you use a static connection string that does not include a UDL file.

To create a new UDL file

Open the folder in which you want to create the UDL file.

Right-click within the folder, point to New, and then click TextDocument.

Consider storing files on a separate logical volume from the operating system to protect against possible file canonicalization and directory traversal bugs.

Protect the file with a restricted ACL that grants read access to your application's process account.

Avoid storing the connection string in clear text in the file. Instead, consider using DPAPI to store an encrypted string.

Using the Registry

You can use a custom key in the Windows registry to store the connection string. This information stored can either be stored in the HKEY_LOCAL_MACHINE (HKLM) or HKEY_CURRENT_USER (HKCU) registry hive. For process identities, such as the ASPNET account, that do not have user profiles, the information must be stored in HKLM in order to allow ASP.NET code to retrieve it.

More information

Using the COM+ Catalog

If your Web application includes serviced components, you can store connection strings in the COM+ catalog as constructor strings. These are easily administered (by using the Component Services tool) and are easily retrieved by component code. Enterprise Services calls an object's Construct method immediately after instantiating the object, and passes the configured construction string.

The COM+ catalog doesn't provide a high degree of security, because the information is not encrypted; however, it raises the security bar in comparison to configuration files because of the additional process hop.

To prevent access to the catalog through the Component Services tool, include only the desired list of users in the Administrator and Reader roles in the System application.

The following example shows how to retrieve an object constructor string from a serviced component.

More information

For a complete code sample provided by the .NET Framework SDK, see the object constructor sample located in \Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Samples\Technologies\ComponentServices\ObjectConstruction.

Authenticating Users Against a Database

If you are building an application that needs to validate user credentials against a database store, consider the following points:

Store one-way password hashes (with a random salt value).

Avoid SQL injection when validating user credentials.

Store One-way Password Hashes (with Salt)

Web applications that use Forms authentication often need to store user credentials (including passwords) in a database. For security reasons, you should not store passwords (clear text or encrypted) in the database.

You should avoid storing encrypted passwords because it raises key management issuesyou can secure the password with encryption, but you then have to consider how to store the encryption key. If the key becomes compromised, an attacker can decrypt all the passwords within your data store.

The preferred approach is to:

Store a one way hash of the password. Re-compute the hash when the password needs to be validated.

Combine the password hash with a salt value (a cryptographically strong random number). By combining the salt with the password hash, you mitigate the threat associated with dictionary attacks.

Creating a salt value

The following code shows how to generate a salt value by using random number generation functionality provided by the RNGCryptoServiceProvider class within the System.Security.Cryptography namespace.

More information

SQL Injection Attacks

If you're using Forms authentication against a SQL database, you should take the precautions discussed in this section to avoid SQL injection attacks. SQL injection is the act of passing additional (malicious) SQL code into an application which is typically appended to the legitimate SQL code contained within the application. All SQL databases are susceptible to SQL injection to varying degrees, but the focus in this chapter is on SQL Server

You should pay particular attention to the potential for SQL injection attacks when you process user input that forms part of a SQL command. If your authentication scheme is based on validating users against a SQL database, for example, if you're using Forms authentication against SQL Server, then you must guard against SQL injection attacks.

If you build SQL strings using unfiltered input, your application may be subject to malicious user input (remember, never trust user input). The risk is that when you insert user input into a string that becomes an executable statement, a malicious user can append SQL commands to your intended SQL statements by using escape characters.

The code fragments in the following sections use the Pubs database that is supplied with SQL Server to illustrate examples of SQL injection.

The Problem

Your application may be susceptible to SQL injection attacks when you incorporate user input or other unknown data into database queries. For example, both of the following code fragments are susceptible to attack.

In this example, an INSERT statement is injected (but any statement that is permitted for the account that's used to connect to SQL Server could be executed). The code can be especially damaging if the account is a member of the sysadmin role (this allows shell commands using xp_cmdshell) and SQL Server is running under a domain account with access to other network resources.

In this case, the ' (single quotation mark) character that starts the rogue input terminates the current string literal in your SQL statement. It closes the current statement only if the following parsed token doesn't make sense as a continuation of the current statement, but does make sense as the start of a new statement.

SELECT au_lname, au_fname FROM authors WHERE au_id = ' '

The ; (semicolon) character tells SQL that you're starting a new statement, which is then followed by the malicious SQL code:

Note The semicolon is not necessarily required to separate SQL statements. This is vendor/implementation dependent, but SQL Server does not require them. For example, SQL Server will parse the following as two separate statements:

SELECT * FROM MyTable DELETE FROM MyTable

Finally, the -- (double dash) sequence of characters is a SQL comment that tells SQL to ignore the rest of the text, which in this case, ignores the closing ' (single quote) character (which would otherwise cause a SQL parser error).

The full text that SQL executes as a result of the statement shown above is:

If you use the Parameters collection, no matter what a malicious user includes as input, the input is treated as a literal. An additional benefit of using the Parameters collection is that you can enforce type and length checks. Values outside of the range trigger an exception. This is a healthy example of defense in depth.

Filter user input for SQL characters. The following method shows how to ensure that any string literal used in a simple SQL comparison statement (equal to, less than, greater than) is safe. It does this by ensuring that any apostrophe used in the string is escaped with an additional apostrophe. Within a SQL string literal, two consecutive apostrophes are treated as an instance of the apostrophe character within the string rather than as delimiters.

Additional best practices

The following are some additional measures you can take to limit the chance of exploit, as well as limit the scope of potential damage:

Prevent invalid input at the gate (the front-end application) by limiting the size and type of input. By limiting the size and type of input, you significantly reduce the potential for damage. For example, if your database lookup field is eleven characters long and comprised entirely of numeric characters, enforce it.

Run SQL code with a least privileged account. This significantly reduces the potential damage that can be done.

For example, if a user were to inject SQL to DROP a table from the database, but the SQL connection used an account that didn't have appropriate permissions, the SQL code would fail. This is another reason not to use the sa account or database owner account for your application's SQL connections.

When an exception occurs in your SQL code, do not expose the SQL errors raised by the database to the end user. Log error information and show only user friendly information. This prevents exposing unnecessary detail that could help an attacker.

Protecting Pattern Matching Statements

If input is to be used within string literals in a 'LIKE' clause, characters other than apostrophe also take on special meaning for pattern matching.

For example, in a LIKE clause the % character means "match zero or more characters." In order to treat such characters in the input as literal characters without special meaning, they also need to be escaped. If they are not handled specially, the query can return incorrect results; a non-escaped pattern matching character at or near the beginning of the string could also defeat indexing.

For SQL Server, the following method should be used to ensure valid input:

Auditing

Auditing of logons is not on by default within SQL Server. You can configure this either through SQL Server Enterprise Manager or in the registry. The dialog box in Figure 12.7 shows auditing enabled for both the success and failure of user logons.

Log entries are written to SQL log files which are by default located in C:\Program Files\Microsoft SQL Server\MSSQL\LOG. You can use any text reader, such as Notepad, to view them.

Figure 12.7. SQL Server Properties dialog with Audit level settings

You can also enable SQL Server auditing in the registry. To enable SQL Server auditing, create the following AuditLevel key within the registry and set its value to one of the REG_DWORD values specified below.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\AuditLevel

You can choose from one of the following values, which allow you to capture the level of detail you want:

3captures both success and failed login attempts.

2captures only failed login attempts.

1captures only success login attempts.

0captures no logins.

It is recommended that you turn on failed login auditing because this is a way to determine if someone is attempting a brute attack into SQL Server. The performance impacts of logging failed audit attempts are minimal unless you are being attacked, in which case you need to know anyway.

You can also script against SQL Database Management Objects (DMO). The following code fragment shows some sample VBScript code.

Process Identity for SQL Server

Run SQL Server using a least-privileged domain account. When you install SQL Server, you have the option of running the SQL Server service using the local SYSTEM account, or a specified account.

Don't use the SYSTEM account or an administrator account. Instead, use a least-privileged domain account. You do not need to grant this account any specific privileges, as the installation process (or SQL Server Enterprise Manager, if you are reconfiguring the SQL Service after installation) grants the specified account the necessary privileges.

Summary

The following is a summary that highlights the recommendation for data access in your .NET Web applications:

Use Windows authentication to SQL Server when possible.

Use accounts with least privilege in the database.

Use least-privileged, local accounts for running ASP.NET/Enterprise Services when connecting to SQL Server.

If you are using SQL authentication, take the following steps to improve security:

Use custom accounts with strong passwords.

Limit the permissions of each account within SQL Server using database roles.

Add ACLs to any files used to store connection strings.

Encrypt connection strings.

Consider DPAPI for credential storage.

When you use Forms authentication against SQL, take precautions to avoid SQL injection attacks.

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.