方法1,参数化:
string name = textbox.text;
string SqlStr = "select * from product where name= @name";
SqlCommand cmd = new SqlCommand(SqlStr, conn);
cmd.Parameters.Add(new SqlParameter("@name", SqlDbType.Char,20)).Value =name;
SqlDataReader sqlReader = cmd.ExecuteReader();
就不用担心sql错误或者漏洞了。这时如果用户输入非法的字符,asp.net会返回一个HttpRequestValidationException异常,捕获之:
catch(Exception exp)
{
if(exp.GetBaseException() is System.Web.HttpRequestValidationException)
errMsg.Text = "您输入的信息中含有未被允许的特殊字符,请输入正确的信息";
}
方法2,存储过程。
方法3,两层(或以上)校验。js,request,sp校验
参照微软的文章:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/SecNetch12.asp
Data Access Security
J.D. Meier, Alex Mackman, Michael Dunner, and Srinath Vasireddy
Microsoft Corporation
Published: November 2002
Last Revised: January 2006
Applies to:
See the "patterns & practices Security Guidance for Applications Index" for links to additional security resources.
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)
Introducing Data Access Security
Authentication
Authorization
Secure Communication
Connecting with Least Privilege
Creating a Least Privilege Database Account
Storing Database Connection Strings Securely
Authentication Users against a Database
SQL Injection Attacks
Auditing
Process Identity for SQL Server
Summary
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:
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.
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:
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.
Figure 12.2 highlights the key gatekeepers for SQL server data access.
Figure 12.2. SQL Server gatekeepers
The key gatekeepers are:
Permissions may be assigned to users, groups, or roles.
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).
Figure 12.3 shows the two models.
Figure 12.3. The trusted subsystem and impersonation/delegation models for database access
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:
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.
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 is more secure than SQL authentication for the following reasons:
Use Windows authentication in the following scenarios:
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).
Consider the following key points when you use Windows authentication to connect to SQL Server:
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.
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):
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.
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:
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.
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:
For more information, see How To: Create a Custom Account to Run ASP.NET 1.1 in the Reference section of this guide.
For more information about creating a custom local account in ASP.NET 2.0, see "How To: Create a Service Account for an ASP.NET 2.0 Application."
For more information, see the Sans Top 20, Accounts with No Passwords or Weak Passwords.
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.
In order to use mirrored accounts to connect from ASP.NET to a database, you need to perform the following actions:
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.
<processModel userName="machine" password="YourStrongPassword" .
For more information, see Creating a Least Privilege Database Account later in this chapter.
To connect to SQL Server using Windows authentication
"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.
With this approach, you configure your ASP.NET application to impersonate a specified, fixed identity, by using the following element in Web.config.
<identity impersonate="true" userName="YourAccount" password="YourStrongPassword" />
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:
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.
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.
Note In ASP.NET 2.0, you can use Protocol Transition and Constrained Delegation instead of LogonUser. For more information, see " How To: Use Protocol Transition and Constrained Delegation in ASP.NET 2.0."
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.
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.
Certain application scenarios may prevent the use of Windows authentication. For example:
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:
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.
If your application needs to use SQL authentication, you need to consider the following key points:
If you connect to a SQL Server database using credentials (user name and password) then your connection string looks like this:
SqlConnectionString = "Server=YourServer; Database=YourDatabase; uid=YourUserName;pwd=YourStrongPassword;"
If you need to connect to a specific instance of SQL Server (a feature available only in SQL Server 2000 or later) installed on the same computer then your connection string looks like this:
SqlConnectionString = "Server=YourServer/Instance; Database=YourDatabase;uid=YourUserName; pwd=YourStrongPassword;"
If you want to connect to SQL Server using your network credentials, use the Integrated Security attribute (or Trusted Connection attribute) and omit the username and password:
SqlConnectionString = "Server=YourServer; Database=YourDatabase; Integrated Security=SSPI;"
- or -
SqlConnectionString = "Server=YourServer; Database=YourDatabase; Trusted_Connection=Yes;"
If you are connecting to an Oracle database by using explicit credentials (user name and password) then your connection string looks like this:
SqlConnectionString = "Provider=MSDAORA;Data Source=YourDatabaseAlias; User ID=YourUserName;Password=YourPassword;"
For more information about using Universal Data Link (UDL) files for your connection, see article Q308426, HOW TO: Use Data Link Files with the OleDbConnection Object in Visual C# .NET, in the Microsoft Knowledge Base.
Don't use the built-in sa or db_owner accounts for data access. Instead, use least-privileged accounts with a strong password.
Avoid the following connection string:
SqlConnectionString = "Server=YourServer/Instance; Database=YourDatabase; uid=sa; pwd=;"
Use least-privileged accounts with a strong password, for example:
SqlConnectionString= "Server=YourServer/Instance; Database=YourDatabase; uid=YourStrongAccount; pwd=YourStrongPassword;"
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.
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.
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.
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).
SQL Server provides a number of role-based approaches for authorization. These revolve around the following thee types of roles supported by SQL Server:
Application roles allow database administrators to grant selected applications access to specified database objects. This is in contrast to granting permissions to users.
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.
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.
To accommodate these scenarios, you have two main options for role-based authorization within SQL Server:
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.
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.
If you elect to use user-defined database roles, you must:
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.
Figure 12.4 illustrates this approach.
Figure 12.4. Connecting to SQL Server using multiple SQL user database roles
To use the preferred Windows authentication for this scenario, you develop code (using the LogonUser API) in an out of process serviced component to impersonate one of a set of Windows identities.
With SQL authentication, you use a different connection string (containing different user names and passwords) depending upon role-based logic within your application.
For more information about securely storing database connection strings, see Storing Database Connection Strings Securely later in this chapter.
With SQL application roles, you must:
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.
If you use this approach, you must securely store the role name and password credentials. For further advice and secret storage techniques, see Storing Database Connection Strings Securely later in this chapter.
The following are the key points that you must be aware of before you choose to use SQL application roles:
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.
For more information, see article Q229564, PRB: SQL Application Role Errors with OLE DB Resource Pooling, in the Microsoft Knowledge Base.
In most application scenarios you need to secure the communication link between your application server and database. You need to be able to guarantee:
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:
You have two options for securing the network link between an application server and database server:
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.
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 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:
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.
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:
Note This LogonUser restriction is lifted in Windows Server 2003.
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.
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
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.
There are a number of possible locations and approaches for storing database connection strings, each with varying degrees of security and configuration flexibility.
The following list represents the main options for storing connection strings:
Note In ASP.NET 2.0, you can secure the database connection string by using the Protected Configuration feature. 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."
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.
Many applications use the Local Security Authority (LSA) to store secrets. DPAPI has the following advantages over the LSA approach:
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.
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:
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:
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.
Figure 12.6 shows the Enterprise Services DPAPI solution.
Figure 12.6. The ASP.NET Web application uses a COM+ server application to interact with DPAPI
In Figure 12.6, the runtime sequence of events is as follows:
You can store the encrypted string by using an <appSettings> element within Web.config as shown below. This element supports arbitrary key-value pairs.
<configuration> <appSettings> <add key="SqlConnString" value="AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAABcqc/xCHxki3" /> </appSettings> </configuration>
You can retrieve the encrypted string with the following line of code:
string connString = ConfigurationSettings.AppSettings["SqlConnString"];
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."
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.
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:
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.
For more information about securing Machine.config, see Chapter 8, ASP.NET Security.
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.
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:
MachineName/ASPNET:R BUILTIN/Users:R BUILTIN/Power Users:C BUILTIN/Administrators:F NT AUTHORITY/SYSTEM:F
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
For more information about using UDL files from Microsoft C#® development tool programs, see article Q308426, HOW TO: Use Data Link Files with OleDbConnection Object in VC#, in the Microsoft Knowledge Base.
Many applications use custom text files to store connection strings. If you do adopt this approach consider the following recommendations:
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.
If you do use this approach, you should:
For more information about encrypting data for storage in the registry, see How To: Store an Encrypted Connection String in the Registry in ASP.NET 1.1 in the Reference section of this guide.
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.
[ConstructionEnabled(Default="Default Connection String")] public class YourClass : ServicedComponent { private string _ConnectionString; override protected void Construct(string s) { _ConnectionString = s; } }
For added security, you can add code to encrypt the construction string prior to storage and decrypt it within the serviced component.
If you are building an application that needs to validate user credentials against a database store, consider the following points:
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:
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.
public static string CreateSalt(int size) { RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); byte[] buff = new byte[size]; rng.GetBytes(buff); return Convert.ToBase64String(buff); }
The following code fragment shows how to generate a hash value from a supplied password and salt value.
public static string CreatePasswordHash(string pwd, string salt) { string saltAndPwd = string.Concat(pwd, salt); string hashedPwd = FormsAuthentication.HashPasswordForStoringInConfigFile( saltAndPwd, "SHA1"); return hashedPwd; }
For the full implementation details of this approach, see How To: Use Forms Authentication with SQL Server 2000 in ASP.NET 1.1 in the Reference section of this guide.
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.
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.
SqlDataAdapter myCommand = new SqlDataAdapter( "SELECT au_lname, au_fname FROM authors WHERE au_id = '" + Login.Text + "'", myConnection);
SqlDataAdapter myCommand = new SqlDataAdapter("LoginStoredProcedure '" + Login.Text + "'", myConnection);
When you accept unfiltered user input values (as shown above) in your application, a malicious user can use escape characters to append their own commands.
Consider a SQL query that expects the user's input to be in the form of a Social Security Number, such as 172-32-xxxx, which results in a query like this:
SELECT au_lname, au_fname FROM authors WHERE au_id = '172-32-xxxx'
A malicious user can enter the following text into your application's input field (for example a text box control).
' ; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100) -
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.
The command above results in the following combined SQL string:
SELECT au_lname, au_fname FROM authors WHERE au_id = '';INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100) --
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:
; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100)
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:
SELECT au_lname, au_fname FROM authors WHERE au_id = '' ; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100) --'
The following approaches can be used to call SQL safely from your application.
SqlDataAdapter myCommand = new SqlDataAdapter( "SELECT au_lname, au_fname FROM Authors WHERE au_id= @au_id", myConnection); SqlParameter parm = myCommand.SelectCommand.Parameters.Add( "@au_id", SqlDbType.VarChar, 11); parm.Value= Login.Text;
// AuthorLogin is a stored procedure that accepts a parameter // named Login SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", myConnection); myCommand.SelectCommand.CommandType = CommandType.StoredProcedure; SqlParameter parm = myCommand.SelectCommand.Parameters.Add( "@LoginId", SqlDbType.VarChar,11); parm.Value=Login.Text;
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.
private string SafeSqlLiteral(string inputSQL) { return inputSQL.Replace("'", "''"); } ... string safeSQL = SafeSqlLiteral(Login.Text); SqlDataAdapter myCommand = new SqlDataAdapter( "SELECT au_lname, au_fname FROM authors WHERE au_id = '" + safeSQL + "'", myConnection);
The following are some additional measures you can take to limit the chance of exploit, as well as limit the scope of potential damage:
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.
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:
private string SafeSqlLikeClauseLiteral(string inputSQL) { // Make the following replacements: // ' becomes '' // [ becomes [[] // % becomes [%] // _ becomes [_] string s = inputSQL; s = inputSQL.Replace("'", "''"); s = s.Replace("[", "[[]"); s = s.Replace("%", "[%]"); s = s.Replace("_", "[_]"); return s; }
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.
Sub SetAuditLevel(Server As String, NewAuditLevel As SQLDMO_AUDIT_TYPE) Dim objServer As New SQLServer2 objServer.LoginSecure = True 'Use integrated security objServer.Connect Server 'Connect to the target SQL Server 'Set the audit level objServer.IntegratedSecurity.AuditLevel = NewAuditLevel Set objServer = Nothing End Sub
From SQL Server Books online, the members of the enumerated type, SQLDMO_AUDIT_TYPE are:
SQLDMOAudit_All 3 Log all authentication attempts regardless of success or failure SQLDMOAudit_Failure 2 Log failed authentication SQLDMOAudit_Success 1 Log successful authentication SQLDMOAudit_None 0 Do not log authentication attempts
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.