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

Asp Net 2.0 Security Membership And Role Management

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

Chapter 4

This behavior occurs because the strongly typed configuration class for the <add /> element includes a collection used to contain feature-specific provider attributes. When you place a lockAttributes or lockAllAttributesExcept attribute on a provider <add /> element, the configuration system considers the feature-specific provider attributes lockable along with the “name” and “type” attributes. (These two attributes are required on a provider <add /> definition, so they are always lockable).

This still leaves the question as to how you actually lock a specific provider definition. Provider configuration always uses Add-Remove-Clear (ARC) collections, meaning that the provider definitions are built up through a series of <add /> elements, with optional <remove /> and <clear /> elements in child configuration sections. However, there is no such thing as a <modify /> element. Without a modification element, what use are the locking attributes?

If you define a provider with an <add /> element and then subsequently use <remove > and then add the provider in another configuration file, the configuration system remembers the original set of locked attributes from the first <add /> definition. It enforces the attribute lock when the provider is redefined. To see an example of this, you can define a membership provider in machine.config as follows:

<membership>

<providers>

<add lockAttributes=”passwordFormat” name=”AspNetSqlMembershipProvider”

.../> </providers>

</membership>

Then in the web.config for an application, you can redefine the provider as follows:

<membership>

<providers>

<remove name=”AspNetSqlMembershipProvider” /> <add name=”AspNetSqlMembershipProvider”

passwordFormat=”Encrypted”

.../> </providers>

</membership>

If you attempt to run any pages in the sample application at this point, you end up with an error saying that the passwordFormat attribute was already defined and locked in a parent configuration file. Unfortunately, you can easily “fake out” the configuration system by using a <clear /> element instead. If you substitute a <clear /> element for the <remove /> element, the web application will run without a problem. Basically in ASP.NET 2.0 the configuration system lacks the “smarts” to retain attribute lock information when a <clear /> element is used.

Hopefully, in a future release of ASP.NET, this problem will be resolved. For ASP.NET 2.0 though, this means that you can only lockdown provider definitions with the following approaches:

Use a <location /> tag to lock the entire provider-based feature. For example, configure the <membership /> section in a parent configuration file and disallow any type of redefinition in child configuration files.

152

Configuration System Security

Use the lockElements and lockAllElementsExcept attributes to control whether child configuration files are allowed to use the <add />, <remove />, and <clear /> elements. You might allow for child configuration files to add new provider definitions or you might allow child configuration files to remove previously defined providers.

Use the lockElements=’providers’ attribute to prevent any kind of changes to the <provider /> element, while still allowing child configuration files the leeway to change attributes on the feature’s configuration element (for example, allow edits to the attribute contained in <membership /> or <roles />).

Reading and Writing Configuration

Before diving into specifics on ACL requirements for reading and writing configuration, a quick primer on using the strongly typed configuration API is useful. Even though a detailed discussion of the new strongly typed configuration API is out of the scope of this book, it is helpful for you to understand the basic coding approaches for manipulating configuration before you see the various security requirements that are enforced when using these APIs.

You may never end up using the strongly typed configuration API. For example, if you use the Membership feature, almost all of the configuration information about the feature itself (the <membership /> configuration element) and the individual providers (the various <add /> elements) are available from the Membership and various MembershipProvider-derived classes. Other features like Forms Authentication follow a similar approach.

However, some features, such as session state, don’t mirror every configuration setting via a property from a well-known feature class. Also for administrative-style applications, it makes sense to deal with configuration information using the configuration APIs as opposed to using different feature classes that are potentially scattered through different namespaces.

Reading configuration for a web application can be accomplished in two different ways. If you want to use the configuration APIs that are available to all Framework applications, you use the

ConfigurationManager class as shown here:

...

using System.Web.Configuration; using System.Configuration;

...

protected void Page_Load(object sender, EventArgs e)

{

SessionStateSection sts = (SessionStateSection)

ConfigurationManager.GetSection(“system.web/sessionState”); Response.Write(“The session state mode is: “ + sts.Mode.ToString() + “<br/>”);

}

The ConfigurationManager class has a static GetSection method that you can use to obtain a reference to a strongly typed configuration class representing a configuration section. You tell the ConfigurationManager which section you want by specifying an XPath-like syntax to the configuration section you want. Because in this case the sample is showing how to access the configuration

153

Chapter 4

information for the session state configuration information, and this configuration section is nested within the <system.web> configuration section, the path that you pass is system.web/sessionState. The path information is case-sensitive because configuration files are XML files.

After ConfigurationManager finds the section, you cast the returned object to the correct type. ASP.NET includes several strongly typed configuration section classes within the System.Web

.Configuration namespace. In the sample code you cast to an instance of SessionStateSection, which is the strongly typed configuration class used for the Session State feature. With the reference to SessionStateSection in hand, you can access any properties exposed by the class — the sample uses the Mode property to write the session state mode for the current application.

The ConfigurationManager class is scoped only to the current application though, so it isn’t flexible enough for applications that need to edit arbitrary configuration files for different web applications. As a result, there is a companion configuration class called WebConfigurationManager, which includes additional overloads for its methods to allow loading of arbitrary web application configuration files.

...

using System.Web.Configuration; using System.Configuration;

...

protected void Page_Load(object sender, EventArgs e)

{

MembershipSection ms = MembershipSection)

WebConfigurationManager.GetSection(“system.web/membership”, “~/web.config”);

Response.Write(“The default provider as set in config is: “ + ms.DefaultProvider + “<br/>”);}

}

In this sample, the GetSection method includes a second parameter specifying the virtual path to the current application’s web.config file. You can change the value of this parameter to point at other web application configuration files, or at configuration files located in subdirectories within a web application. Various overloads let you use physical file paths as well as virtual file paths when referencing configuration files.

Writing to configuration requires that you actually open the entire configuration file, as opposed to just getting a reference to an individual configuration section. This returns a reference to an instance of the System.Configuration.Configuration class. (It’s not a typo; the class that represents a configuration file is really called Configuration within the System.Configuration namespace.) As with read operations, you can use the ConfigurationManager or the WebConfigurationManager to accomplish this. However, the available methods on the ConfigurationManager are not intuitive from the perspective of a web application developer because the various overloads refer to variations of configuration files for client executables. As a result, you will probably find the WebConfigurationManager makes more sense when you edit web.config for your web applications.

After you programmatically open a configuration file, you get a reference to the specific configuration section you want to edit from the Configuration instance. You can set various properties on the strongly typed configuration section as well as manipulate any writable collections exposed on the configuration class. After all the edits are made you call the Save method on the Configuration instance to commit the changes to disk. The following code demonstrates using the WebConfigurationManager to load and update a <membership /> configuration section.

154

Configuration System Security

...

using System.Web.Configuration;

...

protected void Page_Load(object sender, EventArgs e)

{

Configuration config = WebConfigurationManager.OpenWebConfiguration(“~”);

MembershipSection ms = (MembershipSection)config.GetSection(“system.web/membership”);

ms.DefaultProvider = “someOtherProvider”;

config.Save();

}

Several overloads to the OpenWebConfiguration method allow you to specify the exact configuration file you want to open for editing. As shown in the sample, the “~” shorthand can be used for loading the current application’s web.config file.

The configuration system does not enforce any kind of concurrency or locking if multiple threads attempt to update the same configuration file. For this reason, you should ensure that any code that edits configuration files serializes access to the configuration file, or is written to handle the exception that is returned from the configuration system if it detects that changes occurred to the underlying configuration file. If you write console applications for editing configuration files, you probably won’t run into this issue. However, an administrative website that allows editing of any web.config file located on a web server should be written with concurrency in mind.

Permissions Required for Reading Local Configuration

The most common scenario is reading configuration information for a web application that is located on the same server as the code that performing the read operation. For example, each time a web application starts up, ASP.NET is reading configuration information down the entire inheritance chain of configuration files. Furthermore, as you use various features, such as Membership, Role Manager, Session State, and so on, your code triggers additional reads to occur from the various configuration files.

As mentioned in Chapter 1, when an application domain first starts up, the identity that is used is either the process identity or the application impersonation identity. So under normal conditions the Read ACL on web directories that is granted to IIS_WPG allows the default process identity to read configuration information.

Looking up the configuration inheritance chain, the default ACLs on the various configuration files are:

The web application’s directory grants Read access to IIS_WPG, so IIS_WPG has Read access to the application’s web.config file.

The root web.config file located at %windir%\Microsoft.NET\Framework\v2.0.XYZ\ CONFIG\web.config grants Read access to IIS_WPG.

The machine.config located in the same CONFIG subdirectory also grants Read access to IIS_WPG.

155

Chapter 4

This set of ACLs allows the configuration system to merge configuration sections up the inheritance chain. If you remove these Read ACLs from any one of these configuration files, ASP.NET would be unable to read configuration during application startup so your web application will fail to start.

Either the process identity or the application impersonation identity is also used when reading configuration information during normal runtime processing, specifically when using the GetSection method on WebConfigurationManager or ConfigurationManager. For example, if you use Windows authentication in a web application and enable client impersonation, even if the impersonated account does not have access to read the application’s web.config file, the web application still runs and configuration information is still successfully read.

If you think about it, this behavior makes sense. It would be a pretty onerous security requirement if every possible Windows user of an application with client impersonation turned on was required to have Read access up the configuration inheritance chain. Although the default ACLs on the CONFIG subdirectory do grant Read access to the local Users group (and hence any authenticated user on the machine has read access), it is not uncommon to remove this ACL on hardened servers.

The GetSection call succeeds because GetSection is considered to be a “runtime” configuration API. When you call GetSection the configuration system accesses cached configuration information that was previously loaded while running as either the process identity or the application impersonation identity. From a runtime perspective, loading configuration information is a service that the configuration system provides to running code.

This behavior becomes clearer when you compare the difference between the runtime configuration API and the design-time configuration API. Earlier you saw that an alternative approach for getting a configuration section was to use a method such as WebConfigurationManager.OpenWebConfiguration or ConfigurationManager.OpenExeConfiguration. These Open* methods are considered “designtime” configuration APIs, and as a result they have different security semantics when accessing configuration information.

When you call an Open* method the configuration system attempts to open one or more physical configuration files on disk. For example, if you attempt to open a web application’s configuration, a file open attempt will occur up the entire inheritance chain of configuration files. These file open operations are like any other call to the File.Open method. The security token on the operating system thread must have Read access to one or more configuration files.

If you have a web application using Windows authentication with client impersonation enabled, and you write the following line of code:

Configuration config = WebConfigurationManager.OpenWebConfiguration(“~”);

. . . the open attempt will fail unless the impersonated client identity has Read access to the application’s web.config as well as the root web.config and machine.config files located in the Framework’s CONFIG subdirectory. You can see this behavior if you add an explicit Deny ACE to the application’s web.config that disallows Read access to the application’s web.config. The call to OpenWebConfiguration will fail with an Access Denied error. You will have the same failure if you add a Deny ACE on the root web.config or on machine.config. However, if you change your code to call

WebConfigurationManager.GetSection, your code will run without a problem.

156

Configuration System Security

The following list summarizes the security requirements for the runtime and design-time configuration APIs:

GetSection — Regardless of whether this is called from WebConfigurationManager or ConfigurationManager, the process identity or the application impersonation identity (if application impersonation is being used) required Read access to the application’s web.config file, the root web.config file and the machine.config file. If you are attempting to read configuration at a path below the level of the root of a web application, Read access is also required on the lower-level configuration files. This level of access will normally exist because without it the web application would fail to startup.

GetWebApplicationSection — This is just another variation of GetSection available on WebConfigurationManager. It has the same security requirements as GetSection.

OpenWebConfiguration — This method is available only on WebConfigurationManager. The operating system thread identity at the time the call is made requires Read access to the application’s web.config file, the root web.config file and the machine.config file. If you are attempting to read configuration at a path below the level of the root of a web application, the operating system thread identity also requires Read access to the lower level configuration files.

Other Open* methods — Both WebConfigurationManager and ConfigurationManager have a variety of methods starting with Open that provide different overloads for opening configuration files at different levels of the inheritance chain (that is, open just machine.config) as well as different ways for referencing virtual directories in a web application. No matter which Open* method you use, the operating system thread identity requires Read access to all configuration files that contribute to the configuration for the desired application or virtual path. When only machine.config is being opened, Read access is required only on machine.config because the lower level configuration files will not be opened (for example root web.config and application-specific configuration files have no effect on determining machine level configuration information).

Permissions Required for Writing Local Configuration

Writing configuration is not something that a web application would normally attempt. Hence, the default ACLs up the configuration hierarchy don’t grant any Write access to commonly used ASP.NET accounts. Looking up the configuration inheritance chain, the Write ACLs on the various configuration files are as follows:

Only the local Administrators group and SYSTEM have write access to files (including web

.config files) located beneath inetpub\wwwroot.

The root web.config file located at %windir%\Microsoft.NET\Framework\v2.0.XYZ\ CONFIG\web.config grants Write access only to the local Administrators group as well as SYSTEM.

The machine.config located in the same CONFIG subdirectory also grants Write access only to the local Administrators group as well as SYSTEM.

This set of ACLs shows that the default privileges pretty much expect only interactive editing of configuration files by a machine administrator using Notepad.

157

Chapter 4

However, Write access alone is not sufficient for editing configuration files using the configuration API. Updating configuration information results in the following file operations:

1.A temporary file is created in the appropriate directory where the updated configuration file will be written. For example, if you are updating a configuration section in a web application’s configuration file, the configuration system will create a temporary file with a random file name in the web application’s root directory.

2.The original configuration file is deleted.

3.The temporary file is renamed to either web.config or machine.config, depending on which type of configuration file is being edited.

From this list it is pretty obvious that editing and updating configuration files requires very powerful privileges.

Because of the creation and deletion of configuration files, the operating system thread identity that is updating configuration effectively requires Full Control to the directory containing the configuration file that will ultimately be rewritten (technically, you can get away with just Write and Modify access on the directory — but realistically there isn’t much difference between Full control and Write+Modify). Although you could go out of your way and attempt to grant Full Control on a directory but restrict the rights on all files except the configuration file located within a directory, such a security lockdown doesn’t buy you much. Full Control on a directory gives an account wide latitude to make changes in it, and arguably the ability to change the configuration file means an account also has broad privileges to change the behavior of an application.

An important side note here is that because local administrators do have Full Control to directories, a website with Windows authentication and client impersonation enabled could “accidentally” write to any of these configuration files. If a user account that was a member of the local Administrators group happened to surf to a web application that included malicious code that attempted to rewrite configuration, the malicious code would succeed. This type of subtle attack vector is another reason users with elevated privileges in a domain should never perform routine day-to-day work logged in with “super” privileges; its far too easy for someone to slip a piece of interesting code into an unsuspecting web application that maliciously makes use of such elevated privileges.

Unlike the read-oriented methods in configuration that are split between a set of runtime and designtime APIs, write operations are considered design-time APIs. There is no equivalent to GetSection for writing configuration. In fact, if you obtain a configuration section via GetSection, although you can call the property setters on the strongly typed configuration section that is returned, no methods are available to commit the changes to the underlying configuration file.

Instead, you commit changes to disk with a call to the Save or SaveAs method available on System

.Configuration.Configuration. The Configuration instance can be obtained via a call to one of the Open* methods available on ConfigurationManager or WebConfigurationManager. Remember that the operating system thread identity requires Read access to successfully load a configuration file (or files) from disk; loading these files is always the first step whenever you want to edit configuration. After a call to WebConfigurationManager.OpenWebConfiguration, you have a Configuration object that is a reference to an in-memory representation of the loaded configuration file.

Subsequently calling Configuration.Save or Configuration.SaveAs results in the file creation and deletion operations listed earlier. The following code snippet loads a web application’s configuration, modifies the configuration information in memory, and then writes the results to disk:

158

Configuration System Security

Configuration config =

WebConfigurationManager.OpenWebConfiguration(“~”);

MembershipSection ms = (MembershipSection)config.GetSection(“system.web/membership”);

ms.DefaultProvider = “someOtherProvider”;

config.Save();

In the sample code, the configuration information being edited is the web.config file for a web application; thus, Full Control is required only on the root of the web application’s directory. The configuration information represented by the Configuration instance is loaded by reading all the configuration files up the configuration inheritance chain. In an application using Windows authentication and client impersonation, the resulting operating system thread identity needs Read access on each of these configuration files. However, because the web application’s configuration was loaded (as opposed to the root web.config or the machine.config), Full Control is needed only on the web application’s root directory when the call to Save is made.

The requirements for Full Control raise the question of exactly when it makes sense to use the designtime APIs. The safest approach would be to never deploy code to a production web server that calls Configuration.Save. The design-time aspect of configuration makes a lot of sense to use in a development environment or in an automated build process. However, after you have programmatically generated the desired configuration file, you would copy it to a production server.

If the need to edit the configuration files used in production arises, it still makes sense to have the code that performs the configuration updates run on some type of staging or test server. After you verify that the updated configuration works, the updated configuration file can be staged and copied to production. I think having code that writes to configuration sitting on a production server, along with a set of file permissions granting Full Control, is simply a hacker attack waiting to happen.

There is no escaping the fact that you need Full Control to save configuration changes to disk. The idea of having Full Control ACLs for anything other than local Administrators placed on the directories of various application folders is pretty scary. Although there will surely be many elegant and powerful configuration editing UIs created for ASP.NET 2.0 (IIS7 for that matter also will have such tools), such tools should be tightly controlled. Setting up a website or a Web Service that allows for remote editing of configuration files on a production server is just a security incident waiting to happen.

Permissions Required for Remote Editing

The configuration system for ASP.NET includes the ability to have code on one machine remotely bind to ASP.NET configuration data on a remote server and read or write that configuration information. For security reasons, this capability is not enabled by default. A DCOM object can be enabled on your web server to allow remote machines to connect to the web server and carry out configuration operations.

To enable remote reading and writing of a web server’s configuration information, you use the aspnet_regiis tool:

%windir%\Microsoft.NET\Framework\v2.0.5727\aspnet_regiis –config+

The config+ switch causes the Framework to register a DCOM endpoint with the following PROGID:

System.Web.Configuration.RemoteWebConfigurationHostServer_32

159

Chapter 4

If you use the DCOMCNFG tool (which is now an MMC console showing both COM+ and standard DCOM information) after running aspnet_regiis –config+, you can open the DCOM configuration node to see the newly registered DCOM endpoint, as shown in Figure 4-1.

Figure 4-1

You can subsequently disable remote editing of configuration by using aspnet_regiis -config-.

You run the aspnet_regiis tool on the web servers that you want to manage. However, it isn’t necessary to run the tool on the machine that will be running the configuration code. Within the web configuration code, whenever you attempt to open configuration information on a remote server, the configuration code attempts to create an instance of the DCOM object on the remote server. This requires that DCOM calls are able to flow across the network between the machine running the configuration editing code, and the remote server.

Due the sensitive nature of allowing code to remotely manipulate a server’s configuration information, the DCOM object on the remote web server has its launch permissions restricted to only members of the remote server’s local Administrators group. Remember that this is the same security requirement needed by default for editing local configuration information. This means that even if you call one of the Open* methods with the intent of only reading configuration information from a remote server, the operating system thread identity making the calls still needs to be a member of the remote server’s Administrators group. The more stringent security requirement is necessary because you don’t want random machines on your network trolling through your servers attempting to remotely read configuration information.

The utility of allowing remote editing of configuration is suspect due to the security risks involved. With the additional requirement of configuring DCOM to work through firewalls if you are attempting to manage web servers in a DMZ, remote configuration editing in ASP.NET is most useful for web servers

160

Configuration System Security

running inside of a corporate network. Even then you should use additional security such as IPSEC restrictions to prevent random machines on your network from attempting to launch the DCOM server on your web machines.

For additional security, you should change the access permissions on the DCOM object. Although the launch permissions are locked to the local Administrators group, after the DCOM server is launched the default DCOM access permissions control which identities can invoke methods on the DCOM server. Creating a custom set of access permissions for the configuration DCOM object ensures that only selected users or groups can invoke methods on the DCOM server after it is already started.

Using Configuration in Par tial Trust

The configuration examples you have seen so far all depended implicitly on one additional security setting in order to work: the trust level for the sample application. The sample applications have all been running in Full trust when calling into the configuration system. If you attempt to use the strongly typed configuration API, you can only do so by default when running in either Full or High trust. At lower trust levels, the strongly typed configuration API will fail.

For example, say you attempt to read the Membership configuration with code like the following:

MembershipSection ms = (MembershipSection)ConfigurationManager.GetSection(“system.web/membership”);

If your application is running in Medium trust or below, you get an exception with the following information:

Request for the permission of type ‘System.Configuration.ConfigurationPermission, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ failed.

Stack Trace:

[SecurityException: Request for the permission of type ‘System.Configuration.ConfigurationPermission, System.Configuration, ...’ failed.] System.Security.CodeAccessSecurityEngine.Check(PermissionToken permToken, CodeAccessPermission demand, StackCrawlMark& stackMark, Int32 checkFrames, Int32 unrestrictedOverride) System.Security.CodeAccessSecurityEngine.Check(CodeAccessPermission cap, StackCrawlMark& stackMark)

System.Security.CodeAccessPermission.Demand()

System.Configuration.BaseConfigurationRecord.CheckPermissionAllowed(SectionRecord sectionRecord

Chapter 3 explained that when you encounter permission-related exceptions, the exception information and stack trace can sometimes give you a clue as to what happened. In this case, it looks like the configuration system made a check for a permission, specifically the System.Configuration.ConfigurationPermission. The configuration system always demands the ConfigurationPermission whenever an attempt is made to retrieve a configuration object with a call to GetSection.

If you look in the policy file for High trust, you can see that the ConfigurationPermission is explicitly granted:

161