Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Asp Net 2.0 Security Membership And Role Management

.pdf
Скачиваний:
48
Добавлен:
17.08.2013
Размер:
12.33 Mб
Скачать

Chapter 4

<SecurityClasses>

<!--other classes snipped for brevity -->

<SecurityClass

Name=”ConfigurationPermission”

Description=”System.Configuration.ConfigurationPermission, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”/>

</SecurityClasses>

<NamedPermissionSets>

<PermissionSet class=”NamedPermissionSet” version=”1” Name=”ASP.Net”>

<!-- other permissions snipped for brevity -->

<IPermission

class=”ConfigurationPermission” version=”1” Unrestricted=”true” />

</PermissionSet>

</NamedPermissionSets>

The High trust policy file defines the necessary security class for ConfigurationPermission and then grants unrestricted permission on ConfigurationPermission to any ASP.NET application running in High trust. When running at Full trust (the default for all ASP.NET applications), the demand for ConfigurationPermission always succeeds. If you look in the trust policy files for Medium, Low, and Minimal trust, you will see that these policy files do not define a <SecurityClass /> for ConfigurationPermission and thus do not grant this permission in the ASP.NET

NamedPermissionSet.

With this behavior, you might be wondering how any of the ASP.NET 2.0 features that depend on configuration even work in lower trust levels. For example, the Membership feature clearly depends heavily on a variety of configuration information. You can definitely use the Membership feature in Medium trust without any SecurityExceptions being thrown, so what is going on to make this work? ASP.NET 2.0 features that retrieve their configuration sections use an internal helper class that asserts unrestricted ConfigurationPermission. Because the core of ASP.NET 2.0 lives in the GAC’d System.Web.dll assembly, the assertion is allowed. At runtime when various ASP.NET features retrieve their configuration information, the ConfigurationPermission demand from the configuration system succeeds when the demand encounters the assertion during the stack crawl.

The combination of the configuration system’s demand and the assertion within ASP.NET is why in many places in this book I note that strongly typed configuration information is not something that can be depended on when running in partial trust (Medium trust or lower to be specific). This is also why most of the ASP.NET features mirror their configuration information through some portion of their API. For example almost all of the configuration attributes found on the <membership /> configuration element and its provider <add /> elements can be found on read-only properties, either read-only properties on the static Membership class or exposed as read-only properties from

MembershipProvider.

The design approach of echoing back configuration properties on a feature class is one you should keep in mind when designing configuration driven features. If you design a feature intending that aspects of its configuration be available to developers, then you can do the following:

162

Configuration System Security

1.Author the feature to live in the GAC. Follow the design guidelines in Chapter 3 for writing a sandboxed GAC-resident assembly.

2.Within your feature code, assert the ConfigurationPermission when your feature reads its configuration information.

3.Create one or more read-only properties on your feature classes that echo back the appropriate portions of your configuration information.

Of course, there is one flaw with this approach: You may not be allowed to deploy your feature into the GAC. Especially if you write code for use by customers running on shared hosting servers, it is likely that your customers will be unable to deploy your feature’s assembly into the GAC. There is a workaround for this scenario though.

The requirePermission Attribute

The <section /> configuration element in the 2.0 Framework supports a new attribute requirePermission. By default this attribute is set to true, which triggers the configuration system to demand the ConfigurationPermission. However, if you set it to false, the configuration system bypasses the permission demand. For example if you tweak the definition of the <membership /> configuration section to look like the following:

<section name=”membership” type=”System.Web.Configuration.MembershipSection, System.Web, ...” allowDefinition=”MachineToApplication”

requirePermission=”false” />

the sample shown earlier using GetSection will work when running Medium trust or below. However, even though you can add the requirePermission attribute, it is not a recommended approach for the built-in ASP.NET features.

The ConfigurationPermission is intended to close the following loophole. Because the configuration system is fully trusted (it lives in the various GAC’d assemblies), and the configuration system is usually invoked initially without any user code on the stack, the configuration system ends up loading configuration data that is potentially sensitive. The theory is that the configuration data should be treated in such a way that only fully trusted code is allowed read and write access to it. If the configuration system allowed partially trusted code (that is, partial trust ASP.NET pages) to read and write configuration data, then the configuration system theoretically opens itself to a luring attack. Partially trusted code would be able to gain access to some configuration data that it normally would not be able to read.

Of course, one quirk with this theory is that even in Medium and Low trust you can write code in your pages that opens up the application’s web.config as a raw text file, at which point you can parse through it and find the configuration information. However, configuration information is hierarchical, so it is likely that some of your application’s configuration information lives in the parent configuration files. Using simple file I/O you won’t be able to discover the settings stored in either the root web

.config or in machine.config when running in Medium trust or below.

The use of the ConfigurationPermission is a code access security (CAS)-based approach to ensuring that partial trust code can’t use the configuration system to gain access to these parent configuration files when a simple file I/O based approach would fail. The ConfiguartionPermission is granted to High

163

Chapter 4

trust because High trust applications also have the necessary FileIOPermission to read the root web.config and machine.config files. So, the default High trust policy file ensures that the configuration system and the permissions for performing raw file I/O are in sync. Of course as with all security policies defined using trust policy files you can create a trust policy file that breaks this; you could for example grant ConfigurationPermission in the Medium trust policy file, although this isn’t something you should do.

So, when should you use the requirePermission attribute to override the default demand for ConfigurationPermission? If you author a configuration driven feature that won’t live in the GAC, it makes sense to include the requirePermission attribute on the <section /> definition for your custom configuration section. A feature that doesn’t live in the GAC is basically a partially trusted feature itself; conceptually, it wouldn’t be considered any more sensitive than the partially trusted code that calls it. Hence, it is reasonable to allow partially trusted code access to the strongly typed configuration class for such a feature. Of course, if partially trusted code attempts to write changes for the feature back to the underlying configuration files, it still needs the appropriate FileIOPermission and the appropriate NTFS permissions. With these additional security requirements required for updating configuration, setting the requirePermission attribute on your custom configuration sections for non-GAC’d features doesn’t open any security holes.

The behavior of the requirePermission attribute suggests that you should ensure that all GAC’d features have <section /> definitions in machine.config or web.config because after a <section /> is defined in a configuration file, child configuration files cannot override the definition. Even if a child configuration file like an application web.config attempts to add the requirePermission=’false’ attribute, the configuration system disallows this redefinition of the configuration section.

When setting up the configuration section for a feature, you should do one of the following:

For GAC based features, define <section /> in machine.config or the root web.config file.

For non-GAC’d features running in shared hosting environments, define the <section /> in the application’s web.config file, and set requirePermission to false. This also means that you will only be able to include the feature’s configuration section in the application’s web. config file. If you place the feature’s configuration in a higher level configuration file you get an exception because the <section /> has not been defined yet.

For non-GAC’d features running in some type of trusted environment (such as an internal corporate web server), you can define the <section /> wherever it makes sense for manageability. You may define your <section /> in machine.config or root web.config to allow multiple web applications to take advantage of the feature. This is one case where it is reasonable for a non-GAC’d feature to have its <section /> definition in a parent configuration file while still setting requirePermission to false.

There are two configurations sections defined in machine.config that set “equirePermission to false: <connectionStrings /> and <appSettings />. Because these configuration sections are typically used directly by application code, locking them down for partial trust applications does not make sense. As a result, these two configuration sections are the exception to the rule that GAC’d configuration sections disallow strongly typed configuration access to partial trust applications.

164

Configuration System Security

Demanding Permissions from a Configuration Class

There is little known capability in the configuration system that you can use for supporting partial trust applications. You can use a custom configuration class as a kind of gatekeeper to a feature and prevent the feature from being used in a partial trust application. If you remember back to the Chapter 3 on trust levels, and the discussion on the “processRequestInApplicationTrust” attribute, there is a subtle issue with features and code being called when only trusted code is on the stack.

Custom configuration classes are part of this issue because when configuration is being loaded, it isn’t guaranteed that there will be any user code on the stack. More importantly, the feature that carries out work and that consumes the configuration information may itself always be called with trusted code on the stack. Scenarios like GAC’d classes that are HttpModules have this problem. An HttpModule only has the ASP.NET pipeline code sitting above it, so any demands a custom HttpModule located in the GAC makes always succeed.

A feature can indirectly work around this problem by taking advantage of the fact that the configuration system calls PermitOnly on the named permission set for the current trust level. This behavior is the same approach that the page handler takes when it calls PermitOnly prior to running a page. The configuration system makes this call just before attempting to deserialize a configuration section. As a result, a custom configuration class that overrides ConfigurationSection.PostDeserialize can demand an appropriate permission in an override of this method.

using System;

using System.Data.SqlClient; using System.Security.Permissions; using System.Configuration;

public class SampleConfigClass : ConfigurationSection

{

public SkeletalConfigClass() {}

protected override void PostDeserialize()

{

SqlClientPermission scp =

new SqlClientPermission(PermissionState.Unrestricted); scp.Demand();

}

//the rest of the configuration class...

}

The previous configuration class demands the SqlClientPermission. Because the configuration system restricts the set of allowed permissions to whatever is defined for the application’s current trust level prior to the deserialization process, the sample configuration class is usable only if the current trust level grants the SqlClientPermission. If a feature living in the GAC attempts to read its configuration information and the current trust level doesn’t grant this permission, the feature initialization fails because any attempt to read its configuration always fails with a SecurityException.

Given this capability, when would you actually use it? Should you always demand something from your custom configuration class? If you know your GAC’d code is going to be called in scenarios where only trusted code exists on the stack, you should make use of the PostDeserialize method. It is the only point when you will have a chance to enforce a CAS restriction. Identifying these scenarios can be difficult though. If your feature includes a GAC’d HttpModule, this is one obvious case. A custom handler

165

Chapter 4

that is deployed in the GAC would be another example where using PostDeserialize as a surrogate trust enforcement mechanism makes sense.

However, it may impossible to make an intelligent demand in PostDeserialize if you depend on the code that consumes your feature to supply dynamic information. For example, if your feature reads and writes to the file system, you may not know which path to demand permission against until after some consumer code sets some properties on your feature. As a result the PostDeserialize method is appropriate only for demanding permissions that always need to be statically configured in a trust policy file.

FileIOPermission and the Design-Time API

Unlike the runtime portion of the configuration API (for example GetSection), the design-time API always results in physical file I/O operations occurring up the chain of parent configuration files. Because in Medium trust an ASP.NET application only has rights to read and write files within the application’s directory structure, partial trust code doesn’t have rights to open files outside the application. For this reason, the design-time API is basically useless when running in Medium trust or below. Although you could theoretically tweak the lower trust levels’ policy files to get the design-time API working, it is better to consider the design-time API suitable only for full trust or High trust applications.

If you attempt to use one of the design-time APIs such as

WebConfigurationManager.OpenWebConfiguration in partial trust, you will run into an exception like the following:

SecurityException: Request for the permission of type ‘System.Security.Permissions.FileIOPermission, ...’ failed.]

...snip...

System.Security.CodeAccessPermission.Demand() System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32

rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy) System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)

...snip...

System.Configuration.UpdateConfigHost.OpenStreamForRead(String streamName) System.Configuration.BaseConfigurationRecord.InitConfigFromFile()

This stack trace shows that the open attempt eventually results in the use of the FileStream object. Attempting to open a FileStream on top of a file always results in a demand for a FileIOPermission. So, long before the configuration system ever gets around to demanding ConfigurationPermission, the file I/O that occurs during a call to OpenWebConfiguration in a partial trust application will fail. This behavior is another reason the design-time APIs are useful only in High and Full trust web applications.

Protected Configuration

Since ASP.NET 1.0 a common request has been for a way to safely store sensitive configuration information and shield it from prying eyes. The most common information that developers want to protect are connection strings because these frequently contain username-password pairs. But sorts of interesting information beyond connection strings is contained within ASP.NET configuration files. If you use the

166

Configuration System Security

<identity /> section, you again have credentials stored in configuration. If you use classes in the System.Net namespace, you may have configuration elements listing out SMTP servers or other network endpoints, and so on.

The 2.0 Framework introduces a new feature to deal with this problem called protected configuration. Protected configuration is a way to take selected pieces of any configuration file and store the configuration information instead in a secure and encrypted format. The great thing about the protected configuration feature is that it can be used with just about any configuration section — both ASP.NET and non-ASP.NET configuration sections. As with other features in ASP.NET, protected configuration is provider-based, so you can buy or write alternative protected configuration providers instead of using the built-in providers.

Out of the box, the .NET Framework ships with two protected configuration providers:

System.Configuration.DPAPIProtectedConfigurationProvider

System.Configuration.RsaProtectedConfigurationProvider

As the class names suggest, the first provider uses the data protection API (DPAPI) functionality in Windows to encrypt and decrypt configuration sections. The second provider uses the public-key RSA algorithm for performing the same functionality.

The basic idea behind protected configuration is that you use the aspnet_regiis command-line tool, or the configuration API (the SectionInformation.ProtectSection and SectionInformation

.UnprotectSection methods to be precise) to encrypt selected pieces of your configuration information prior to putting an application into production. Then at runtime the configuration system decrypts the protected configuration information just prior to handing the configuration information back to the requesting code. The important thing is that protecting a configuration section is transparent to the features that rely on the configuration section. No feature code has to change just because an underlying configuration section has been encrypted.

When you use protected configuration you start with some configuration section that might look like the following:

<machineKey

validationKey=”123456789012345678901234567890123456789012345678” decryptionKey=”123456789012345678901234567890123456789012345678” />

This is a perfect example of the type of section you probably would like to protect. You would rather not have any random person with read access to your web.config walking away with the signing and validation keys for your application.

You can encrypt this configuration section from the command line using the aspnet_regiis tool:

aspnet_regiis -pe system.web/machineKey -app /Chapter4/ConfigurationSample -prov DataProtectionConfigurationProvider

After you use the protected configuration feature, the <machineKey /> section looks something like the following:

<machineKey configProtectionProvider=”DataProtectionConfigurationProvider”> <EncryptedData>

167

Chapter 4

<CipherData>

<CipherValue>encrypted data here</CipherValue> </CipherData>

</EncryptedData>

</machineKey>

Of course, instead of the text “encrypted data here,” the actual result has about five lines of text containing the base-64 encoded representation of the encrypted blob for the <machineKey /> section. When you run the application everything still works normally though because internally the configuration system transparently decrypts the section using the extra information added to the <machineKey /> element.

Depending on whether you use the RSAor the DPAPI-based provider, different information will show up within the <machineKey /> element. In the previous example, the configuration system added the configProtectionProvider attribute to the <machineKey/> element. This is a pointer to one of the protected configuration providers defined in machine.config. At runtime, the configuration system instantiates the specified provider and asks it to decrypt the contents of the <EncryptedData /> element. This means that custom protected configuration providers can place additional information within the <EncryptedData /> element containing any extra information required by the provider to successfully decrypt the section. In the case of the DPAPI provider, no additional information behind the encrypted blob that is necessary.

What Can’t You Protect?

Protected configuration sounds like the final answer to the age-old problem of encrypting connection strings. However, due to the interaction between app-domain startup and configuration you cannot blindly encrypt every single configuration section in your configuration files. In some cases, you have a “chicken-and-egg” effect where ASP.NET or the Framework needs to read configuration information to bootstrap itself, but it has to do this prior to having read the configuration information that defines the protected configuration providers.

The following list names some configuration sections (this is not an exhaustive list) that you may have in your various configuration files that can’t be encrypted with protected configuration:

processModel — ASP.NET needs to be able to read this just as it is starting up. Furthermore, for IIS5 and IIS 5.1 it controls the identity of the worker process, so you would be in a Catch-22 situation if you needed the correct worker process identity in order to read protected configuration.

startup and runtime — These configuration sections are used by the Framework to determine things such as which version of the Framework to load as well as information on assembly redirection.

cryptographySettings — This configuration section defines the actual cryptography classes used by the framework. Because protected configuration depends on some of these classes, you can’t encrypt the configuration section that contains information about the algorithms used by the protected configuration feature.

configProtectedData — This is the configuration section that contains the definition of the protected configuration providers on the machine. This would also be a Catch-22 if the section were encrypted because the configuration system needs to be able to read this section to get the appropriate provider for decrypting other configuration sections.

168

Configuration System Security

Selecting a Protected Configuration Provider

Now that you know you have at least two different options for encrypting configuration information, you need to make a decision about which one to use. Additionally, you need to determine how you want to use each provider. The criteria for selecting and then configuring a provider revolve around two questions:

Do you need to share configuration files across machines?

Do you need to isolate encrypted configuration data between applications?

The first question is relevant for those of you that need to deploy an application across multiple machines in a web farm. Obviously in a load-balanced web farm, you want an application that is deployed on multiple machines to use the same set of configuration data. You can use either the DPAPI provider or the RSA provider for this scenario.

Both providers require some degree of setup to work properly in a web farm. Of the two providers, the RSA provider is definitely the more natural fit. With the DPAPI provider, you would need to do the following to deploy a web.config file across multiple machines:

1.Deploy the unencrypted configuration file to each web server.

2.On each web server, run aspnet_regiis to encrypt the desired configuration sections.

The reason for this is that the DPAPI provider relies on machine-specific information, and this information it not portable across machines. Although you can make the DPAPI provider work in a web farm, you will probably get tired of constantly reencrypting configuration sections each time you push a new configuration file to a web farm.

The RSA provider depends on key containers that contain the actual key material for encrypting and decrypting configuration sections. For a web farm, you would perform a one-time setup to synchronize a key container across all the machines in a web farm. After you create a common key container across all machines in the farm, you can encrypt a configuration file once on one of the machines — perhaps even using a utility machine that is not part of the web farm itself but that still has the common key container. When you push the encrypted configuration file to all machines in the web farm, each web server is able to decrypt the protected configuration information because each machine has access to a common set of keys.

The second question around isolation of encryption information deals with how the encryption keys are protected from other web applications. Both the DPAPI and the RSA providers can use keys that are accessible machine-wide, or use keys that are accessible to only a specific user identity. RSA has the additional functionality of using machine-wide keys that only grant access to specific user accounts.

Currently, the recommendation is that if you want to isolate key material by user account, you should separate your web applications into different application pools in IIS6, and you should use the RSA provider. This allows you to specify a different user account for each worker process. Then when you configure the RSA protected configuration providers, you take some extra steps to ensure that encryption succeeds only while running as a specific user account. At runtime, this means that even if one application can somehow gain access to another application’s configuration data, the application will not be able to decrypt it because the required key material is associated with a different identity.

169

Chapter 4

Both the DPAPI and RSA have per-user modes of operation that can store encryption material directly associated with a specific user account. However, both of these technologies have the limitation that the Windows user profile for the process identity needs to be loaded into memory before it can access the necessary keys. Loading of the Windows user profile does not happen on IIS6 (it will occur though for other reasons in IIS5/5.1). As a result the per-user modes for the DPAPI and RSA providers really aren’t useful for web applications.

There is another aspect to isolating encryption data for the DPAPI provider because the provider supports specifying an optional entropy value to use during encryption and decryption. The entropy value is essentially like a second piece of key material. Two different applications using different entropy values with DPAPI will be unable to read each other’s data. However, using entropy is probably more suitable when you want the convenience of using the machine-wide store in DPAPI, but you still want some isolation between applications.

The following table summarizes the provider options that you should consider before setting up protected configuration for use in ASP.NET:

 

Need to Support Multiple Machines

Only Deploy on a Single Machine

Sharing key

RSA provider.

Either the RSA or the DPAPI

material is acceptable

 

provider will work

 

Use the default machine-wide

Use the machine-wide options

 

key container, and grant Read

for either provider.

 

access to all accounts.

Can optionally use key entropy

 

 

with DPAPI provider

 

 

Can optionally use RSA key

 

 

containers with different ACLs.

Key material should

RSA provider.

RSA provider.

be isolated

 

 

Use machine-wide RSA key containers, but ACL different key containers to different user identities.

Use machine-wide RSA key containers, but ACL different key containers to different identities.

DPAPI per-user key containers require a loaded user profile and thus should not be used.

RSA per-user key containers also require a loaded user profile and thus should not be used.

170

Configuration System Security

Caveat When Using Stores That Depend on User Identity

If you choose to use either provider with their per-user mode of operation or if you use machine-wide RSA key containers that are ACL’d to specific users, you need to be aware of an issue with using protected configuration. The sequence in which ASP.NET reads and then deserializes configuration sections is not fixed. Although ASP.NET internally obtains configuration sections in a certain sequence during app-domain startup, this sequence may very well change in the future.

One very important configuration section that is read early on during app-domain startup is the <identity /> section. You can use <identity /> to configure application impersonation for ASP.NET. However, if you use RSA key containers for example that depend on specific user identities you can end up in a situation where ASP.NET starts initially running as a specific process identity (NETWORK SERVICE by default on IIS6), and then after reading the <identity /> section it switches to running as the defined application impersonation identity.

This can lead to a situation where you have granted permission on an RSA key container to an IIS6 worker process account, and suddenly other configuration sections are no longer decrypting properly because they are being decrypted after ASP.NET switches over to the application impersonation account. As a result, you should always configure and ACL key stores on the basis of a known process identity.

For IIS6 this means setting up protected configuration based on the identity that will be used for an individual worker process. If your applications need to run as different identities, instead of using application impersonation on IIS6 you should separate the applications into different application pools (aka worker processes). This guarantees that at runtime ASP.NET will always be running with a stable identity, and thus regardless of the order in which ASP.NET reads configuration sections during appdomain startup, protected configuration sections will always be capable of being decrypted using the same identity.

For older versions like IIS5 and IIS 5.1, you can choose a different process identity using the <processModel /> element. However, application impersonation is really the only way to isolate applications by identity on these older versions of IIS. Although you could play around with different configuration sections to determine which ones are being read with the identity defined in <processModel /> and which ones are read using the application impersonation identity in <identity />, you could very well end up with a future service pack subtly changing the order in which configuration sections are deserialized.

As a result, the recommendation for IIS5/5.1 is to upgrade to IIS6 if you want to use a feature like RSA key containers with user-specific ACLs. Granted that this may sound a bit arbitrary, but using key storage that depends on specific identities with protected configuration gets somewhat complicated as you will see in a bit. Attempting to keep track of the order of configuration section deserialization adds to this complexity and if depended on would result in a rather brittle approach to securing configuration sections. Separating applications with IIS6 worker processes is simply a much cleaner and more maintainable approach over the long term.

171