Keeping secrets in ASP.NET 2.0.
Code download available at:ExtremeASPNET05.exe(118 KB)
Wait, You Can’t Do That
ASP.NET 1.1—A Better Confidant
ASP.NET 2.0 Secrets
Tell Smarter Secrets
Storing data securely in a configuration system is not an easy problem to solve. While I was on the ASP.NET team, this particular feature, secure connection string storage, looked as if it wouldn’t get done. A whole host of problems surrounding it, such as key storage, were in the way. The good news is that not only was it eventually completed, but it has become part of the powerful new set of APIs in ASP.NET 2.0 that allow you to manage the ASP.NET configuration file programmatically.
Before diving into ASP.NET 2.0, though, let’s take a look at the problem and various solutions in ASP.NET 1.x. If you have used ASP.NET for any time at all, you are no doubt aware of the recommendation to store shared settings in the web.config file. For example, rather than specifying your connection string each time you create a new database connection, you store the string in the ASP.NET configuration file’s <appSettings /> section. The connection string is then accessible through the ConfigurationSettings.AppSettings property. Here’s an example <appSettings/> section:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="ConnectionString" value="server=.;database=demo;uid=db;pwd=*u%a" /> </appSettings> </configuration>
Then, whenever you need to change the connection string you simply open the file, make your changes, and you’re finished.
This feature struck a chord with many developers moving from classic ASP to ASP.NET where most global values were stored as application variables. In fact, for ASP.NET 1.x, it is a recommended practice to store connection strings in <appSettings/>. It’s also worth noting that you can store other common data in <appSettings/>, too, including LDAP paths, common application settings, and other miscellaneous data your application needs. The goal of <appSettings/> is to simplify the writing of custom configuration section handlers—a more advanced technique of interacting with the ASP.NET configuration system. Custom configuration section handlers allow you to author and process your own XML sections within the configuration system.
You may have noticed that the contents stored in <appSettings/> are not encrypted but rather are stored as plain text. The same is true of the <sessionState/> section, which enables the out-of-process storage of Session data. One of the storage options is to use SQL Server™ and have the credentials stored in plain text in the <sessionState/> configuration slot.
Wait, You Can’t Do That
A shortcoming of storing the connection string in <appSettings/> was that this was not secure because the file was neither encrypted nor compiled. Not that compiling the configuration information would do anything to improve security; DVD manufacturers used key-based encryption to protect intellectual property, only to have DVD player software vendors store the decryption key in their compiled code. A little code spelunking by some hackers easily turned up the decryption key. It’s also common to read blog posts about individuals trying to figure out how something works in the Microsoft® .NET Framework, when someone makes the suggestion: "Just use Reflector."
ASP.NET 1.1—A Better Confidant
In ASP.NET 1.0, it was not possible to securely store the connection string in the configuration file without extra custom code. The ASP.NET team addressed this problem in ASP.NET 1.1 by enabling encryption for several configuration entries. The solution was accomplished through the Windows Data Protection APIs (DPAPI), resulting in the ability to encrypt the following configuration entries:
<identity/> Used to store the Windows® identity of the ASP.NET worker process for impersonation.
<processModel/> Used to control the Windows account that the ASP.NET worker process executed under. Not used in IIS 6.0 (see the following note).
<sessionState/> Specifically contains the stateConnectionString and sqlConnectionString attributes used to control how ASP.NET authenticated to the out-of-process state servers.
It’s important to note that IIS 6.0, the Web server included with Windows Server™ 2003, provides its own worker process management subsystem to which ASP.NET defers. Thus some worker process settings found in the configuration system are not used when ASP.NET is hosted by IIS 6.0.
A tool called aspnet_setreg.exe was provided with ASP.NET 1.1 to encrypt data in the configuration files and store the decryption key in a Windows registry entry. The resulting registry key had an Access Control List (ACL) configured to limit the Windows accounts that could access the key. This technique is explained, in detail, in Knowledge Base article Q329290, "How to use the ASP.NET Utility to Encrypt Credentials and Session State Connection Strings".
However, this solution had some shortcomings, too. It broke what the ASP.NET team lovingly referred to as xcopy deployment, which allowed an ASP.NET application to be deployed without access to the server. With the technique outlined earlier, the developer or system administrator had to have local machine access to run the command-line tool to encrypt the configuration data and store the key in the registry.
ASP.NET 2.0 Secrets
This brings me to all of the work the team did to address this problem for ASP.NET 2.0. Yet again, there is a command-line tool for managing the encryption of configuration data: aspnet_regiis.exe. Aspnet_regiis.exe existed in previous versions of ASP.NET and was used primarily for manually registering ASP.NET with IIS. For example, it was used to add the aspnet_isapi.dll to IIS as well as configure the script directories that ASP.NET applications used. You can find this tool in the \Windows\Microsoft.NET\Framework\version#\ directory.
Using aspnet_regiis.exe for encrypting configuration sections is as cryptic as the results it generates! Take a look at the results of executing aspnet_regiis.exe /help in Figure 1 and you’ll see what I mean.
Figure 1** Encrypting Configuration Data **
As you can see, unless you are intimately familiar with security terminology, you’ll be quickly overwhelmed by the number of options and various settings. Unfortunately this is a case of powerful capabilities and confusing tools. Fortunately, the Microsoft patterns & practices team wrote a great in-depth article, "How To: Encrypt Configuration Sections in ASP.NET 2.0 Using DPAPI," that walks you through aspnet_regiis.exe.
Instead of examining how to use aspnet_regiis.exe, let’s look at an example ASP.NET page that uses the new configuration APIs to accomplish the encryption of configuration sections. The single page, ConnectionEncryption.aspx (available from the MSDN®Magazine Web site), contains a GridView which is populated with a list of all configuration sections. It looks like Figure 2.
Figure 2** Using the New Configuration APIs **
Before examining how the internals of ConnectionEncryption.aspx work, I’ll look at the results from this page. But first a warning: use of this tool requires that the process hosting ASP.NET has the permission to write to the web.config file of the current application. By default, an ASP.NET application running in IIS will not have the necessary permissions. However, an application hosted in the ASP.NET Development Web Server runs with the permission set of the logged-on user. All uses of this tool you see here are shown within the ASP.NET Development Web Server. It is recommended that you do not change permission settings for IIS without a thorough understanding of the effects.
Following is a sample entry found in the new <connectionStrings> section of web.config used to store connection strings. The <connectionStrings> section is nearly identical to <appSettings> and is now the recommended place to store connection string data as there are new APIs specifically for working with connection strings sprinkled throughout ASP.NET:
<connectionStrings> <add name="Northwind" providerName="System.Data.SqlClient" connectionString="Server=localhost;Integrated Security=True;Database=Northwind" /> </connectionStrings>
Note, in this case Windows authentication is still used for connecting to the database.
Clicking on the Encrypt link in ConnectionEncryption.aspx changes the connection string value in web.config to something like that shown in Figure 3. Once encrypted, the ConnectionEncryption.aspx page reports the state of the entry as encrypted (the link changes to "Decrypt", as you see in Figure 4).
Figure 3 New Encrypted Connection String
<connectionStrings> <EncryptedData> <CipherData> <CipherValue>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAALJZXjMW9c0G4r/2N3hAQAAAACAAAAAAADZgAAqAAAABAAAAD7l2fNIXMFeuAohZ1FjRP+AAAAAASAAACgAAAAEA AAALK0hmPPfWgWJRVr69wbkSWAAQAAr+idwrf3qkg5NPzXa4HnpbeMy1ngAQyn5GlIO99JT40 FC8bY3keUqVkDlUhwj1WrHV/i7+tMbkj5MJPvRV4z33tp4d6H1PVsMVpjsbmRb/9YdY50kRCN tWxWye/chGbDDoNePcQhdidPmdH5Yf2lDdpMT3hX1K8JCiWXTXa7sk0/Vu6oQARZpqWMxXJAU GGY4G/WNAUchQ0Rw3U09ygn3aykMb4loqBwvhdlW7EuuGB3OXCrZ2SfrGGwiw0CD3BWHoOdKV 2yH6TQYUED7yLYHPATEUyj42T2H1J0B6SaQLIA9N61lThZQWdLbeWCpBAkOcUpivO9jhPJtB2 zNs3RnCaPOKyhpX650nBH/qRzO8D5B7PmJ4RHFN97ePoIZ0nz5LnOWUGWra+kQBD3/F9Dl9XO al6pVvO/EEXbJZFhIobAzynjoec1msAPcIWxzx3PypoLUQEjMHeEkk1TtKiZbmznnPu8lPzlJ SECfbyajQLTQOtetxCmpOKdhEFgsnHRFAAAAEcgWPEHOEF32JkblJNOzL/Sh4ux </CipherValue> </CipherData> </EncryptedData> </connectionStrings>
Figure 4** The New Page **
Now that you have seen the page in action, let’s take a look at the code. On Page_Load, the GridView is populated when the new ASP.NET 2.0 WebConfigurationManager class retrieves an instance of the Configuration class for the local path on line 18 in ConnectionEncryption.aspx.cs:
Configuration config = WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);
An ArrayList is populated with data retrieved from the configuration variable and is then bound to the GridView. A majority of the other methods in the source (see the code download) are used for business logic rules around the actual data binding operation, such as determining the scope and state of a section (encrypted or unencrypted). The magic happens in the GridView1_RowCommand event when the link for Encrypt or Decrypt is clicked. When the GridViewCommandEvent.CommandName value is "Encrypt", the following code is executed:
section.SectionInformation.ProtectSection( "DataProtectionConfigurationProvider"); config.Save();
When the GridViewCommandEvent.CommandName value is "Decrypt", the following code is executed:
Thus the actual work of encrypting and decrypting the data is handed off to a provider. One built-in provider is the DataProtectionConfigurationProvider. It uses the built-in DPAPI to store secure data, which is the same one used by the ASP.NET command-line tool.
You should note that when encrypting data, a provider can be named, but when decrypting it’s not required (if null or an empty string is provided to ProtectSection, the default provider specified in the configProtectedData section of the configuration file will be used). This is because the APIs write another entry into the configuration file that names the encryption protection provider used:
<connectionStrings configProtectionProvider= "DataProtectionConfigurationProvider"> <EncryptedData>...</EncryptedData> </connectionStrings>
This configuration entry is used not only by APIs to determine how to decrypt sections, but also by ASP.NET internally when it needs to read values, such as the connection string, into memory which first must be decrypted.
The great news about the new encryption functionality of ASP.NET 2.0 is that not only can you encrypt many of the built-in configuration sections, but you can also write custom encryption providers! Providers are an awesome new extensibility model in ASP.NET 2.0 that enable developers to provide their own implementation of core functionality such as membership, personalization, and so forth. Additionally, due to the way Configuration Encryption was implemented, custom configuration sections can easily be encrypted as well, so protection of data is not limited to a few configuration sections as was the case in ASP.NET 1.1.
Tell Smarter Secrets
A column on encrypting secrets wouldn’t be complete without several warnings. First, if you can avoid secrets, do so. If you’re using SQL Server and you don’t want to store sensitive connection string information in the configuration system, use integrated authentication with SQL Server. Windows authentication will be used to connect from your application server to the database. With this technique, the connection to SQL Server is authenticated and authorized through Windows directly. Using Windows authentication, SQL Server requests a token from the authenticating server (whether local or remote), which contains a security identifier (SID) for the user along with other information and is compared with a list maintained by SQL Server to determine if access is granted or denied. No passwords or usernames are stored in the configuration file.
Another thing to keep in mind is that just because data in the configuration file is stored securely doesn’t mean you’re safe from all kinds of attacks. Any attacker worth his salt (security pun) could do plenty of harm if he got far enough to access your web.config file on your server. An attacker who obtained access to the system to manipulate the web.config could perform other actions to manipulate the database without requiring knowledge of the connection string. Thus, securely storing your connection string or other application data is simply one line of defense. Another effective defense is using stored procedures and placing more fine-grained control over what the account accessing your database has permissions to. Case in point, an encrypted connection string is worthless if you have SQL injection vulnerabilities (for more on SQL injection attacks, see msdn.microsoft.com/msdnmag/issues/04/09/SQLInjection).
Having built and run a number of high-visibility sites, such as www.asp.net, I’ve seen my fair share of attack attempts. One common attribute is that the attack is often completely unrelated to the initial vulnerability. By not attacking through the original vulnerability, the attacker is attempting to protect the backdoor created through the original vulnerability.
Protecting secrets in your ASP.NET applications is possible in any version of ASP.NET, though it’s made easier in ASP.NET 1.1, and much more so in ASP.NET 2.0. With ASP.NET 2.0, rather than configuration encryption being an afterthought, it was something that was built into the new configuration APIs. With ASP.NET 2.0, not only can you use the aspnet_regiis.exe tool to encrypt configuration sections, but you can also write your own custom code—along with custom providers if you wish—to encrypt and decrypt configuration data.
For more information on storing secrets in ASP.NET 1.1, see "Security Considerations for Hosting ASP.NET version 1.1."
Send your questions and comments for Rob to firstname.lastname@example.org.
Rob Howard is a founder of Telligent Systems, specializing in high-performance Web apps, knowledge management, and collaboration systems. Previously, Rob was employed by Microsoft where he helped design the infrastructure features of ASP.NET 1.0, 1.1, and 2.0. You can contact Rob at email@example.com.