
Asp Net 2.0 Security Membership And Role Management
.pdf
Chapter 4
The aspnet_regiis tool really has only two modes of operation when working with protected configuration providers:
The tool has rich support for the RSA based provider that ships in the framework. Aspnet_ regiis includes many configuration switches to carry out various operations that are specific to the RSA-based provider.
The tool can invoke any arbitrary provider, but it cannot support any special behavior that may required by the provider. You can see that the command line (the -pe and -pd options) does not include any special switches beyond the basics that are required to identify a specific configuration section to protect.
This means that if you use a different protected configuration provider, and if you need to support special operations related to that provider (for example, the key container setup that is required when using RSA), you will need to write your own code to carry out these types of provider-specific tasks.
Using Protected Configuration Providers in Partial Trust
You have seen how protected configuration works transparently with the features that depend on the underlying configuration data. However, because protected configuration relies on providers, and these providers are public, there isn’t anything preventing you from just creating an instance of either the RSA or the DPAPI provider and calling the methods on these providers directly. The Decrypt method on a
ProtectedConfigurationProvider accepts a System.Xml.XmlNode as an input parameter and returns the decrypted version as another XmlNode instance.
Combining the simplicity of this method with the fact that most ASP.NET trust levels allow some read access to the file system means that malicious developers could potentially attempt the following steps:
1.Open the application’s web.config file as a text file or through a class like System.Xml
.XmlTextReader.
2.Get a reference to the appropriate DPAPI or RSA provider based on the provider name in the configProtectionProvider attribute that is on the configuration element being protected.
3.Pass the contents of the <EncryptedData /> element for a protected configuration section to the Decrypt method of the protected configuration provider obtained in the previous step.
In some scenarios, you don’t want any piece of code to be able to accomplish this. Even in High trust where your code has access to read the machine.config and root web.config files, you probably don’t want this loophole to exist.
If a feature is written to mirror configuration properties in a public API, then that is where developers should access the values. In some cases, if you author a feature so that certain pieces of configuration information are read, but are never exposed from a feature API, then you don’t want random code that out flanks your feature and decrypts sensitive data directly from configuration.
To prevent this, the DPAPI and the RSA providers include the following class-level demand on their class signatures:
[PermissionSet(SecurityAction.Demand, Name=”FullTrust”)]
182

Configuration System Security
This declarative demand requires that all callers up the call stack must be running in full trust. The FullTrust value for the Name property is actually a reference to one of the built-in .NET Framework permission sets that you can see if you use a tool like the .NET Framework Configuration MMC. As a result all, code in the call stack needs to be running in the GAC or the entire ASP.NET application needs to be running in the ASP.NET Full trust level. For a partial trust application, any attempt to directly call the providers will fail with a SecurityExpcetion.
You can see how this works by writing some sample code to load an application’s web.config file, extract an encrypted section out of it, and then pass it to the correct provider.
using System.Configuration; using System.Xml;
...
protected void Page_Load(object sender, EventArgs e)
{
XmlDocument xd = new XmlDocument(); xd.Load(Server.MapPath(“~/web.config”));
XmlNamespaceManager ns = new XmlNamespaceManager(xd.NameTable);
ns.AddNamespace(“u”, “http://schemas.microsoft.com/.NetConfiguration/v2.0”); XmlNode ec =
xd.SelectSingleNode(“//u:configuration/u:system.web/u:machineKey”,ns);
RsaProtectedConfigurationProvider rp = (RsaProtectedConfigurationProvider)
ProtectedConfiguration.Providers[“AppSpecificRSAProvider”]; XmlNode dc = rp.Decrypt(ec);
}
The sample code uses an XPath query to extract get an XmlNode reference to the encrypted <machineKey /> section. It then uses the ProtectedConfiguration class to get a reference to the correct provider for decryption. If you run this code in a Full trust ASP.NET application it will work. However, if you drop the trust level to High or lower, a SecurityException occurs when the call to Decrypt occurs.
Even though the protected configuration providers demand full trust, you can still protect your own custom configuration sections in partial trust applications when using either the DPAPI or the RSA providers. At runtime when a call is made to GetSection from ConfigurationManager or WebConfigurationManager, internally the configuration system asserts full trust on your behalf prior to decrypting the contents of your custom configuration section. This behavior makes sense because the assumption is that if a piece of code can successfully call GetSection (for example, if ConfigurationPermission has been granted to the partial trust application, or requirePermission has been set to false, or your code is running in the GAC and asserts ConfigurationPermission), there is no reason why access to configuration via a strongly typed configuration class should fail even if the underlying data requires decryption.
If you have a sample application running in High trust (High trust is necessary for this sample because the “runtime” configuration APIs fail by default when called below High trust), you can attempt to open the protected <machineKey /> section with the following code:
MachineKeySection mk =
(MachineKeySection)WebConfigurationManager.GetSection(“system.web/machineKey”);
183

Chapter 4
The preceding code will work in both High and Full trust. In High trust, the code succeeds because it makes it over the hurdle of the two following security checks:
The application is running in High trust, so the configuration system demand for
ConfigurationPermission succeeds.
The configuration system internally asserts full trust when deserializing the configuration section, so the declarative security demand from the protected configuration provider passes as well.
However, if you use the design-time configuration API as follows in High trust, the same logical operation fails:
//This will fail in High trust or below with a protected config section Configuration config = WebConfigurationManager.OpenWebConfiguration(“~”);
MachineKeySection mk = (MachineKeySection)config.GetSection(“system.web/machineKey”);
In this scenario, three security checks occur, and the last one fails:
The configuration system opens the file using file I/O, which generates a FileIOPermission demand. The demand passes because High trust has rights to read all configuration files in the inheritance chain.
The NTFS ACLs on machine.config, root web.config, and the application’s web.config also allow read access.
The protected configuration provider demands full trust. The demand fails because the sample code is running in the Page_Load method of a partial trust ASP.NET application. Internally, the configuration does not assert full trust on your behalf when calling the Open* methods.
The interaction of trust levels with protected configuration can be a bit mind-numbing to decipher. Excluding intervention on your part with configuration files or sandboxed GAC assemblies, the following list summarizes the behavior of the RSA and DPAPI protected configuration providers:
Protected configuration providers work in partial trust applications that load configuration using the GetSection method. This method is the normal way a custom feature that you author would load configuration.
Protected configuration providers fail in partial trust when using the design-time configuration APIs (that is, the various Open* methods). Normally, you won’t call these methods from anything other than administrative applications or command-line configuration tools.
Redirecting Configuration with a Custom Provider
So far, all of the discussion on protected configuration has revolved around the idea of encrypting and decrypting configuration sections. Given the feature’s heritage with the old aspnet_setreg.exe tool, this is understandable. Traditionally, when customers asked for a way to secure sensitive pieces of configuration data, they were looking for a way to encrypt the information. However, there is no reason that the concept of “protection” can’t be interpreted differently.
A common problem some of you probably have with your web applications is with promoting an application through various environments. Aside from development environments you may have test servers, staging servers, live production servers, and potentially warm backup servers. Encrypting your configura-
184

Configuration System Security
tion data does make it safer, but it also increases your management overhead in attempting to synchronize configuration data properly in each of these environments. This overhead is even more onerous if you work in a security sensitive environment where only a limited number of personnel are allowed to encrypt the final configuration information prior to pushing it into production.
Protected configuration is probably manageable with manual intervention for a few servers and is tolerable with the help of automated scripts in environments that deal with dozens if not hundreds of servers. However, you can kill two birds with one stone if you think about “protected” actually being a problem of getting important configuration data physically off your web servers. If you store selected configuration sections in a central location (such as a central file share or a central configuration database), you have a more manageable solution and, depending on how you implement this, a more secure solution as well.
You can write a custom protected configuration provider that determines information about the current server and the currently running application. Because a protected configuration provider controls the format of the data that is written into a protected configuration section, you can store any additional information you need in this format. For example, you could have a custom XML format that includes hints to your provider so that it knows if a configuration section for machine.config, the root web.config, or an application web.config is requesting. Even though the DPAPI and RSA providers use the W3C XML Encryption Recommendation, this is not a hard requirement for the format of encrypted data that is used by a custom provider.
A custom provider can then reach out to a central repository of configuration information and return the appropriate information. Depending on how stringent your security needs are you can layer extra protection in the form of transport layer security (such as an SSL connection to a SQL Server machine as well as IPSEC connection rules) and encrypt the configuration data prior to storing it in a central location. When you have a select group of individuals who manage the configuration data for live production servers, it is probably much easier to have such a group manage updates to a single database as opposed to encrypting a file and then having to worry about getting the synchronization of said file correct across multiple machines.
Implementing a custom protected configuration provider requires you to derive from the System
.Configuration.ProtectedConfigurationProvider class. As you can see, the class signature is very basic:
public abstract class ProtectedConfigurationProvider : ProviderBase
{
public abstract XmlNode Encrypt(XmlNode node);
public abstract XmlNode Decrypt(XmlNode encryptedNode);
}
For a sample provider that demonstrates redirecting configuration to a database, you implement only the Decrypt method because this is the method used at runtime to return configuration data to caller. If you store more complex data inside your protected configuration format, implementing the Encrypt method will make life easier when storing configuration sections in a custom data store.
First look at what a “protected” configuration section in a web.config file will look like using the custom provider:
<membership configProtectionProvider=”CustomDatabaseProvider”> <EncryptedData>
<sectionInfo name=”membership” /> </EncryptedData>
</membership>
185

Chapter 4
As with previous snippets of protected configuration, the <membership /> section references a protected configuration provider. Instead of the actual definition of the <membership /> section though, the <EncryptedData /> element is common to all protected configuration sections. However, what is enclosed within this element is determined by each provider. In this case, to keep the sample provider very simple, the protected data consists of only a single element: a <sectionInfo /> element.
Unlike protected configuration providers that blindly encrypt and decrypt data, this provider needs to know the actual configuration section that is being requested. The RSA and DPAPI providers actually have no idea what they are operating against. Both providers work against a fixed schema and consider the encrypted blob data to be opaque from a functionality standpoint. The custom provider, however, needs to know what section is really being requested because its purpose is to store configuration data in a database for any arbitrary configuration section. The name attribute within the <sectionInfo /> element gives the custom provider the necessary information. Although this is just a basic example of what you can place with <EncryptedData />, you can encapsulate any kind of complex data your provider may need within the XML.
The custom provider will store configuration sections in a database, keying off of a combination of the application’s virtual path and the configuration section. The database schema that follows shows the table structure for storing this:
create table ConfigurationData ( ApplicationName nvarchar(256) NOT NULL, SectionName nvarchar(150) NOT NULL, SectionData ntext
)
go
alter table ConfigurationData
add constraint PKConfigurationData PRIMARY KEY (ApplicationName,SectionName)
Go
Retrieving this information will similarly be very basic with just a single stored procedure pulling back the SectionData column that contains the raw text of the requested configuration section:
create procedure RetrieveConfigurationSection @pApplicationName nvarchar(256), @pSectionName nvarchar(256)
as
select |
SectionData |
|
from |
ConfigurationData |
|
where |
ApplicationName |
= @pApplicationName |
and |
SectionName |
= @pSectionName |
go |
|
|
Because the custom protected configuration provider needs to connect to a database, a connection string must be included within the definition of the provider. Writing and configuring custom providers is the subject of a Chapter 9 — the important point for this sample is that ASP.NET allows you to add arbitrary information to the configuration element for providers.
186

Configuration System Security
<configProtectedData>
<providers>
<add name=”CustomDatabaseProvider” type=”CustomProviders.DatabaseProtectedConfigProvider,CustomProviders” connectionStringName=”ConfigurationDatabase”
/>
</providers>
</configProtectedData>
The provider configuration looks similar to the configurations for the RSA and DPAPI providers. In this case, however, the custom provider requires a connectionStringName element so that it knows which database and database server to connect to. The value of this attribute is simply a reference to a named connection string in the <connectionStrings /> section, as shown here:
<connectionStrings>
<add name=”ConfigurationDatabase” connectionString=”server=.;Integrated _
Security=true;database=CustomProtectedConfiguration”/>
</connectionStrings>
When creating your own custom providers, you have the freedom to place any provider-specific information you deem necessary in the <add /> element.
Now that you have seen the data structure and configuration related information, take a look at the code for the custom provider. Because a protected configuration provider ultimately derives from
System.Configuration.Provider.ProviderBase, the custom provider can override portions of
ProviderBase as well as ProtectedConfigurationProvider. Chapter 9 goes into more detail on
ProviderBase — for now though the custom provider will override ProviderBase.Initialize so that the provider can retrieve the connection string from configuration:
using System; using System.Data;
using System.Data.SqlClient; using System.Configuration;
using System.Configuration.Provider; using System.Web;
using System.Web.Hosting; using System.Web.Configuration; using System.Xml;
namespace CustomProviders
{
public class DatabaseProtectedConfigProvider : ProtectedConfigurationProvider
{
private string connectionString;
public DatabaseProtectedConfigProvider() { }
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
string connectionStringName = config[“connectionStringName”]; if (String.IsNullOrEmpty(connectionStringName))
throw new ProviderException(“You must specify “ +
187

Chapter 4
“connectionStringName in the provider configuration”);
connectionString = WebConfigurationManager.ConnectionStrings[connectionStringName] _
.ConnectionString; if (String.IsNullOrEmpty(connectionString))
throw new ProviderException(“The connection string “ + “could not be found in <connectionString />.”);
config.Remove(“connectionStringName”);
base.Initialize(name, config);
}
//Remainder of provider implementation
}
}
The processing inside of the Initialize method performs a few sanity checks to ensure that the connectionStringName attribute was specified in the provider’s <add /> element, and that furthermore the name actually points at a valid connection string. After the connection string is obtained from the ConnectionStrings collection, it is cached internally in a private variable.
Of course, the interesting part of the provider is its implementation of the Decrypt method:
public override XmlNode Decrypt(XmlNode encryptedNode)
{
//Application name
string applicationName = HostingEnvironment.ApplicationVirtualPath; XmlNode xn = encryptedNode.SelectSingleNode(“/EncryptedData/sectionInfo”); //Configuration section to retrieve from the database
string sectionName = xn.Attributes[“name”].Value;
using (SqlConnection conn = new SqlConnection(connectionString))
{
SqlCommand cmd =
new SqlCommand(“RetrieveConfigurationSection”, conn); cmd.CommandType = CommandType.StoredProcedure;
SqlParameter p1 = new SqlParameter(“@pApplicationName”, applicationName); SqlParameter p2 = new SqlParameter(“@pSectionName”, sectionName);
cmd.Parameters.AddRange(new SqlParameter[] { p1, p2 });
conn.Open();
string rawConfigText = (string)cmd.ExecuteScalar(); conn.Close();
//Convert string from the database into an XmlNode XmlDocument xd = new XmlDocument(); xd.LoadXml(rawConfigText);
return xd.DocumentElement;
}
}
188

Configuration System Security
The Decrypt method’s purpose is take information about the current application and information available from the <sectionInfo /> element and use it to retrieve the correct configuration data from the database.
The provider determines the correct application name by using the System.Web.Hosting
.HostingEnvironment class to determine the current application’s virtual path. The name of the configuration section to retrieve is determined by parsing the <EncryptedData /> section to get to the name attribute of the custom <sectionInfo /> element. With these pieces of data the provider connects to the database using the connection string supplied by the provider’s configuration section.
The configuration data stored in the database is just the raw XML fragment for a given configuration section. For this example, which stores a <membership /> section in the database, the database table just contains the text of the section’s definition taken from machine.config stored in an ntext field in SQL Server. Because protected configuration providers work in terms of XmlNode instances, and not raw strings, the provider converts the raw text in the database back into an XmlDocument, which can then be subsequently returned as an XmlNode instance. Because the data in the database is well-formed XML, the provider can just return the DocumentElement for the XmlDocument.
The provider’s implementation of the Encrypt method is just stubbed out. For your own custom providers, you could implement the inverse of the logic shown in the Decrypt method that would scoop the configuration section out of the config file and stored in the database.
public override XmlNode Encrypt(XmlNode node)
{
throw new NotImplementedException(“This method is not implemented.”);
}
What is really powerful about custom protected configuration providers is that you can go back to some of the sample configuration code used earlier in the chapter and run it, with the one change being that you use the “protected” configuration section for <membership />.
MembershipSection ms =
(MembershipSection)ConfigurationManager.GetSection(“system.web/membership”);
This code works unchanged after you swap in the new <membership /> section using the custom protected configuration provider. This is exactly what you would want from protected configuration. Nothing in the application code needs to change despite the fact that now the configuration section is stored remotely in a database as opposed to locally on the file system.
Clearly, the sample provider is pretty basic in terms of what it supports. However, with a modicum of work you could extend this provider to support features like the following:
Machine-specific configuration
Environment specific configuration — separating data by terms like TEST, DEV, PROD, and so on
Encrypting the actual data inside of the database so that database administrators can’t see what is stored in the tables
Nothing requires you to store configuration data in a traditional data store like a database or on the file system. You could author a custom provider that uses a Web Service call or socket call to a configuration system as opposed to looking up data in a database.
189

Chapter 4
One caveat to keep in mind with custom protected configuration providers is that after the data is physically stored outside of a configuration file, ASP.NET is no longer able to automatically trigger an app-domain restart whenever the configuration data changes. With the built-in RSA and DPAPI providers, this isn’t an issue because the encrypted text is still stored in web.config and machine.config files. ASP.NET listens for change notifications and triggers an app-domain restart in the event any of these files change.
However, ASP.NET does not have a facility to trigger changes based on protected configuration data stored in other locations. For this reason, if you do write a custom provider along the lines of the sample provider, you need to incorporate operational procedures that force app-domains to recycle whenever you update configuration data stored in locations other than the standard file-based configuration files.
Summar y
Configuration security in ASP.NET 2.0 includes quite a number of improvements. While the original <location /> based locking approach is still supported (and is definitely still useful), ASP.NET 2.0’s configuration system now gives you the ability to enforce more granular control over individual sections. The lockAttributes attribute restricts the ability of child configuration files to override selected attributes defined on the parent. The lockElements attribute prevents entire configuration elements from being redefined in child configuration files. Both of these attributes support an alternate syntax to make it easier to configure fine-grained security when many attributes or many nested configuration elements need to be controlled.
Because configuration data exists within physical files, NTFS permissions come into play when reading or writing configuration data. Under normal conditions, configuration data only needs to be read; although it has to be read up the entire inheritance chain from the most derived web.config file all the way up to the root web.config and web.config files. Because ASP.NET reads runtime configuration data using the process account or application impersonation identity, reading configuration usually succeeds assuming the file ACLs have been set up properly. Physically writing configuration data is something that should be reserved only for administrative-style applications or command-line tools due to the need for Full Control on these files. ASP.NET also supports remote editing of configuration files, although for security reasons this functionality is turned off by default.
Because ASP.NET supports running in partial trust, the configuration system makes use of the Framework’s CAS support to limit what can be done in partial trust. Access to strongly typed configuration sections is allowed only in High and Full trust. If you need to access the configuration classes directly in Medium trust or lower, you will need to use the requirePermission attribute. For the builtin configuration sections, you should avoid doing so because most ASP.NET features expose public APIs that already give access to most of the configuration data you need.
Customers have long asked for the ability to secure configuration data so that prying eyes cannot see sensitive information such as database connection strings. The new protected configuration feature in the Framework allows you to encrypt configuration sections using either DPAPI or RSA. Because the protected configuration feature is based on the provider model, you also have the option to write or purchase custom protected configuration providers. This gives you the freedom to implement different encryption strategies or, as seen with the sample provider, different storage locations for your configuration data.
190

Forms Authentication
Forms authentication is the most widely used authentication mechanism for Internet facing ASP.NET sites. The appeal of forms authentication is that sites with only a few pages and simple authentication requirements can make use of forms authentication, and complex sites can still rely on forms authentication for the basic handling of authenticating users. In ASP.NET 2.0, the core functionality of forms authentication remains the same, but some new security scenarios have been enabled and some security features have been added.
This chapter covers the following topics on ASP.NET 2.0 forms authentication:
Reviewing how forms authentication works in the HTTP pipeline (most of this was covered in Chapter 2)
Making changes to the behavior of persistent forms authentication tickets
Securing the forms authentication payload
Securing forms authentication cookies with HttpOnly and requireSSL
Using Cookieless support in forms authentication
Using forms authentication across ASP.NET 1.1 and ASP.NET 2.0
Leveraging the UserData property of FormsAuthenticationTicket
Passing forms authentication tickets between applications
Enforcing a single login and preventing replayed tickets after logout