Sponsored Links


Resources

.NET Research Library
Get .NET related white papers, case studies and webcasts

ASP.NET Security in a Sandbox - Part 2ASP.NET Security in a Sandbox - Part 2ASP.NET Security in a Sandbox - Part 2 Discuss DiscussDiscuss Printer friendly Printer friendlyPrinter friendly
ASP.NET Security in a Sandbox - Part 2

January 27, 2005

In my last article (Part 1) I discussed how to design ASP.NET applications that run with least privilege, explaining how to elevate privileges when components require access to protected resources such as the database or file system. Hardening an ASP.NET application implies leveraging Windows security and .NET security policy. Application code should run with a low-privilege Windows account and with fewer security permissions. Part 1 explained how these security layers both prevent code from accessing protected resources until additional rights are granted, how threads can impersonate a privileged account on demand, and how code can assert security permissions to prevent permission demands from failing when restricted functionality is invoked.

These principles are still important to this article (Part 2) however this time I’ll be discussing an alternate approach to ASP.NET application architecture. I’ll show you a more elegant and maintainable solution that leverages Enterprise Services to incorporate serviced components with appropriate security boundaries, runtime security identities, configurability and support for secure and scalable distribution.

Sandboxing without Enterprise Services

In Part 1, all assemblies participating in a request were hosted by the ASP.NET worker process. All code executed with the identity of the worker process unless the executing request thread impersonated another account before invoking restricted resources. All components, therefore, were sandboxed to run within the restrictions of the worker process and specific blocks of code were executed outside of this sandbox thanks to privilege elevation techniques demonstrated.

Part 1 illustrated a couple of important practices that should be adopted by any ASP.NET application that requires security, let alone the flexibility of loose coupling and simplified maintenance:

  • Always use code-behind
  • Always decouple middle-tier components from the code-behind and application assembly
  • Strongly name ASP.NET supporting assemblies (not the application assembly) and install them into the Global Assembly Cache (GAC)
  • Run each component with the lowest possible set of privileges, delay elevation of privileges as long as possible and restore original permissions as soon as possible

Several benefits are realized by these practices. Permissions are elevated only for the duration required, thus reducing the attack surface. Separation of components offered better opportunities for code re-use and maintainability, in addition to isolating higher privilege code from the presentation layer. Installing middle-tier assemblies in the GAC makes it possible to assert permissions for necessary calls, since GAC assemblies are granted FullTrust.

It is worth pointing out that the simplicity of the application architecture from Part 1 delivers some performance and configuration benefits. Application code does not cross process or machine boundaries; inter-component communications therefore do not require authentication and encryption; and configuration and deployment do not involve other technologies such as COM+ and MSMQ. The benefits of this last statement can be dissected further: small business sites are sometimes hosted in environments where administrative access to operating system services such as COM+ and MSMQ are not available; large organizations sometimes impose restrictions on the use of services like these, limiting the technologies developers can leverage. Excluding these limitations, however, there are also advantages to employing serviced components with COM+ and MSMQ. Additional configuration and deployment efforts are offset by flexible distribution, loosely coupled configurations and reduction of custom code necessary to achieve similar goals. Per call performance impact is offset by aggregate performance gains provided through object pooling, just-in-time activation and bottleneck distribution.

Application deployment and configuration can be complex even without serviced components. For example, impersonation credentials must be securely stored and encrypted. Furthermore, as shown in the sample for Part 1, the code required to impersonate and assert security permissions is typically tightly coupled to the component. This reduces the component’s ability to be reused, makes component configuration complex and distribution awkward. Using EnterpriseServices, application architecture can be restructured so that credentials are configurable without the need to persist and encrypt them manually, removing the vulnerability of potentially weak encryption and storage techniques. Cumbersome code required to impersonate on demand and assert security permissions can be removed, resulting in a more elegant solution. In addition to this, serviced components can be more easily and securely distributed while continuing to provide important runtime functionality for transactions, encryption, security, and reliable message queuing.

NOTE: Though this article is not an exhaustive resource on Enterprise Services, it will give you some perspective on the application of the technology. For more background information on these concepts read COM and .NET, by author Juval Lowy.

The Sandboxing View with Enterprise Services

Let’s take a comparative look at the application described in Part 1 (Figure 1), and the same application with serviced components introduced into the architecture (Figure 2). Figure 1 illustrates the system as designed in Part 1.

Figure 1: As needed, the request thread impersonates a higher privilege account to access database and file system resources, prior to calling code to access them.

Figure 2: A COM+ server application is hosted by dllhost.exe, which can be configured in Component Services to run with a specific Windows identity.

Before I talk about the reasons behind the architecture choices in Figure 2, let me provide you with an overview of the changes from a higher level. In the revised architecture ASP.NET application assemblies (the code behind) and their associated configuration remain consistent with the previous architecture. What I mean is, application code is executed in a Low trust ASP.NET security setting, and each request thread is processed with the ASP.NET worker process identity. The most obvious change in Figure 2 is that components belonging to middle-tier assemblies are now registered with COM+. Specifically, the file IO component (mlb.Util.IO.FileManager) is registered as part of a COM+ server application that runs with the fsreadwrite Windows identity; and the data access component (mlb.Photos.Dalc.PhotosTable) is registered with a separate COM+ server application running with the dbreadwrite Windows identity. These components are still invoked synchronously from the application assembly, as part of request processing.

Supporting assemblies are still installed into the GAC and granted FullTrust. When shared assemblies (i.e., mlb.Util.Security, mlb.DataProtection) are consumed by the application assembly, they are hosted in the worker process within the caller’s application domain. Shared assemblies consumed by either serviced component will be loaded into the caller’s dllhost.exe process (also inside an application domain, of course).

The new architecture does require some changes to serviced component design, something I’ll elaborate on later in this article. At minimum, serviced components must derive from System.EnterpriseServices.ServicedComponent as shown here:

using System.EnterpriseServices;

public class PhotosTable: ServicedComponent
{ ... }

In sections to follow I’ll talk about other changes made to components and additional work required to support this new application architecture. I’ll also discuss how specific aspects of this architecture compare to or improve on its predecessor.

Component Allocation to Processes

COM+ applications can be configured as server or library applications. A library application is hosted in the caller’s process; a server application in a separate process (dllhost.exe). Both types of applications can leverage COM+ services such as transactions, authentication, encryption and message queuing. Though COM+ applications can include more than one component, I elected to allocate the file IO component and data access component into separate COM+ server applications.

When components are registered with COM+, they are registered as part of an application. This can be done using the Component Services console, or the command line; however with .NET we can decorate the assembly and serviced components with attributes to help with this process. When the assembly is registered using regsvcs.exe these declarative attributes are inspected and used to initialize the COM+ application and component, instead of using the defaults. I applied the following attributes to the mlb.Photos.Dalc assembly to specify server application properties:

[assembly: ApplicationName("PhotoGalleryDALC")]
[assembly: Description("Application handles data access for PhotoGallery. ")]
[assembly: ApplicationActivation(ActivationOption.Server)]

And these to mlb.Util.IO:

[assembly: ApplicationName("PhotoGalleryIO")]
[assembly: Description("Application handles file IO access for PhotoGallery. ")]
[assembly: ApplicationActivation(ActivationOption.Server)]

Now let me explain why I didn’t install these components into the same application. Each server application is configured to run with a specific Windows identity, according to the resources the application components will access. File system resources and database resources are accessed by different roles in the system, so installing each of the respective components in different applications makes it easy to configure the required identity for each. This means that all of the previous code to impersonate users can be removed, simplifying code and decoupling functionality from security requirements.

But identity is not the only reason to allocate the components in this way, and shouldn’t necessarily be the driving reason. You also have to consider how components interact with one another, how they will be deployed, and the performance impact of your choices. In this case, these components and their resources are really subsystems to the overall application. Couldn’t file access logic act as a service to multiple applications, or to several workflows through this application? Couldn’t photo-related database access also be treated as a subsystem that services other applications using the same data? If we take a service-oriented approach to application design then you can visualize several subsystems that each control a particular resource or data store, and should be freely distributable even to multiple physical tiers if necessary for scalability and security reasons. The point is to design components in such a way that they are decoupled from one another and can operate independently as a subsystem that can operate autonomously or as part of an aggregated system.

Regarding performance, invoking serviced components from the ASP.NET application tier will incur some overhead: crossing process (and possibly machine) boundaries; authenticating callers; and possibly encrypting packets. This can be offset with benefits of securing component access without the need to write custom (difficult to write, test and bullet proof) code; the ability to very easily configure components in various service/library application and runtime identity configurations; and the ability to distribute components to remote servers to improve Web server throughput for bottleneck operations. All this without modifying the calling code starts to sound like a pretty sweet deal!

It is also true that performance overhead can be incurred if server applications communicate with one another. This overhead increases further if there are authentication boundaries between applications. In this example, however, file and data access operations are not dependent on one another - therefore no additional application or process boundaries are crossed. The application code must cross those boundaries to invoke each component, but this would be true even if they lived in the same application. In a possibly bold statement I’d like to say that if application design is approached from a service-oriented perspective, components and services will be loosely coupled so that cross communication between server applications is limited to well-defined service boundaries.

Runtime Identities and Call Authorization

Given that this example will deploy two server applications, let’s take a look at how the applications will be configured to run with a specific Windows identity. In Figure 3, the properties dialog for the PhotoGalleryDALC application is shown configured to run with a specific account.

Figure 3 – Each service application can be configured to run with a particular account within the domain. In this example the data access application runs as dbuser.

The dbreadwrite account identity will govern how data access components interact with database resources. Similarly, the PhotoGalleryIO server application executes with fsreadwrite identity. In Part 1 both components impersonated an externally configured account on demand, but in this case each component immediately runs with its configured identity for its lifetime. This removes complexity of components by decoupling the burden of elevating runtime rights as functionality is invoked, increasing configurability and reuse prospects. The trade-off for this flexibility is a loss of fine-grained control over the duration of time this higher privilege identity is active. If all components installed within their respective server application did not require higher privileges, this distribution would have to be reconsidered. In this case only data access components will belong to PhotoGalleryDALC and the Web application’s file access components will be hosted by PhotoGalleryIO. Respectively they will require additional privileges for the majority of work performed.

To counteract the risk of unauthorized callers invoking these server application components, we can authorize callers on the way in. We want to make sure that the components are invoked by a trusted caller. Applications can be configured as shown in Figure 4, to enforce access checks to the application, verify the identity of callers and protect wire communications with those components.

Figure 4: Application’s can authorize callers and even impersonate their identity if appropriate to the calling pattern. The authentication level can be increased to PacketIntegrity or PacketPrivacy to add integrity checking and encrypt communications with serviced components.

In Figure 4 authentication level is set to use Packet authentication. This means that the component at least verifies that the entirety of the message has been received if it were broken into multiple packets. This level of security can be improved on by cranking the authentication level all the way up to PacketPrivacy. This offers the highest level of protection afforded binary messaging - verifying that messages are not tampered with en-route, and encrypting messages to prevent visibility in the event of unauthorized interception in transit. The expense of this heightened security is performance, but this costs is minimal compared to the cost of the system is compromised. Application benchmarking should be used to evaluate latency issues so that they can be accommodated, possibly through alternate hardware and network configurations.

Access checks are also performed against the Role Based Security settings for the application - located in the COM+ catalog. In Figure 4 the Impersonation Level is set to Identify so that the application will restrict callers based on the identity of the call context, which must belong to one of those roles. In this example, the call context will come from the ASP.NET worker process, thus the ASP.NET runtime identity will be identified. To allow calls in from this context, the ASP.NET runtime identity can be configured in the catalog, as shown in Figure 5 in the Component Services console.

Figure 5: Applications roles are associated with Windows identities. The ASP.NET worker process identity (for Windows XP in this case) is configured as an approved caller to both server applications.

With impersonation level set to Identify, each application authenticates the identity of the call context and attempts to authorize the identity. If the ASP.NET worker process identity is configured this way, calls from the application assembly will be able to successfully invoke service application components. Authorizing the ASP.NET identity means that in theory, any code injected into the worker process runtime will have the rights to call each of the server application components. This doesn’t necessarily leave the application vulnerable since malicious code would first have to successfully cross a process boundary. However, as a further measure of security an alternate identity can be impersonated prior to invoking these components. I will discuss this hybrid configuration in a later section.

Authorizing calls to the application is the first stop in allowing access to application components. Components can also individually be configured to restrict access to specific roles as shown in Figure 6.

Figure 6: Component level access means calls are verified for each method call. Specific roles can limit this access.

Role-based checks at the component level are used here to allow the call context, which is the ASP.NET identity, rights to invoke the component.

.NET attributes can simplify the initial configuration of these security settings. The attributes that initialized the two application settings related to security are:

[assembly: ApplicationAccessControlAttribute(<span class="style1">true</span>)]
[assembly: SecurityRoleAttribute("Execute", SetEveryone="false")]

The SecurityRoleAttribute above will create a role entry for the application, named Execute, the actual users or groups associated with each role cannot be automated declaratively. This should be configured during through deployment scripts (best option) or manually set up through the console shown in Figure 5.

To indicate that components will also authenticate callers the following attributes decorate the serviced component type:

[ComponentAccessControlAttribute(true)]
[SecurityRoleAttribute("Execute")]
public class PhotosTable: ServicedComponent
{ ... }

In this case we are initializing the component properties to allow members of the catalog’s Execute role to call the component. This configuration is applied to both applications in the sample code.

Approaches to Code Access Security

To retain the hardening of the ASP.NET application tier, the <trust> element should be set to Low as described in Part1:

<trust level="Low" originUrl="" />

This also means that assemblies will not have the permissions required to invoke serviced components directly. Permission demands for UnmanageCode security permission and RegistryPermission rights will fail the stack walk unless an approach similar to that shown in Figure 1 is taken to elevate runtime code access permissions before invoking COM+ components.

The options include:

  • Create a custom ASP.NET security configuration that allows the application tier to run with Execution and UnmanagedCode security permission, plus RegistryPermission
  • Create a façade component that drives interaction from the application assemblies to the serviced components, install it in the GAC to grant it full trust, and assert permissions on demand as described in Part 1

Although it means invoking security elevation in one of our application assemblies, option b) is the better choice since it will not raise the permission set for the overall Web application tier. Alas, we cannot completely decouple security from our components. So Figure 7 illustrates the resulting architecture, introducing PhotoGallery.Facade.dll to the application tier.

Since the façade is installed to the GAC, it has FullTrust and can assert the following permission before invoking serviced components:

SecurityPermission secPerm = new SecurityPermission(
	SecurityPermissionFlag.UnmanagedCode|
	SecurityPermissionFlag.Assertion);

RegistryPermission regperm = new RegistryPermission(PermissionState.Unrestricted);

PermissionSet pset = new PermissionSet(PermissionState.Unrestricted);
pset.AddPermission(regperm);
pset.AddPermission(secPerm);
pset.Assert();

This assembly must also include the AllowPartiallyTrustedCallersAttribute.

Generally speaking the code-behind files should be removed from the flow of business logic interacting with the page - so a façade component is a useful layer of abstraction regardless of the need to elevate security permissions.

Other than this, the application architecture no longer requires security assertions previously required for mlb.Photos.Dalc.PhotosTable and mlb.Util.IO.FileManager to execute on their respective functionality. All security assertions and identity impersonation have been stripped of these components thanks to the ability to configure these as server applications running in a separate process. Specifically on the subject of security permissions, since permission demands do not traverse process boundaries components hosted by a server application are granted FullTrust and will satisfy all permission demands.

Component Design Considerations

Although there are many benefits to this updated application architecture, additional consideration must now be given to component design and configuration. The goal is to maintain an independent configuration for each major feature set or subsystem.

Application Configuration

The first issue to address is the need to access application configuration settings from each of the server applications. Unfortunately the web.config for the overall Web application is not directly accessible to server applications running in dllhost.exe. Though the configuration of impersonation accounts is no longer necessary, as used in the previous article sample, there is still the matter of path configuration for persisting uploaded images, and database configurations such as localized table names and connection strings.

Several options exist for externally configuring serviced components:

  1. Place configuration settings in dllhost.exe.config
  2. Supply an initialization constructor for components that require configuration
  3. Supply isolated application configuration for each server application
  4. Custom configuration code

Option c) is the superior solution however let’s review the reasoning. Option a) and c) both allow components to use typical syntax to access application configuration settings at runtime, through System.Configuration.ConfigurationSettings. However, using dll.exe.config means sharing the same application settings for all COM+ applications. This is not only unmanageable from a deployment perspective (how to update the configuration file without stomping on pre-existing settings on the machine) but also from an organization perspective (naming conventions for key/value pairs must include an application identifier). Option b) means modifying the component to support initialization on construction, which tightly couples clients to components. Furthermore if the component is later deployed differently, how can it revert back to using standard .NET configuration? Option d) is always possible, but requires extra work that can be avoided with option c).

Providing an isolated application configuration file requires a few steps. First, the Application Root Directory must reflect the location of the configuration file, as shown in Figure 8:

Figure 8: We can configure application level configuration for each server component, by providing a value for Application Root Directory in application properties.

This directory should include an application.manifest and application.config file. In fact the application.manifest need not contain any specifics:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
</assembly>

The application.config follows the .NET configuration schema. In this example, I use it to retrieve encrypted database connection string and table names for the data access component, mlb.Photos.Dalc.PhotoTable. The contents of the configuration file are show here:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="photosConnection" value="AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAACAYpj5
		hmfkuKVp8417ROagQAAAACAAAAAAADZgAAqAAAABAAAADoZhOKUN/3Rk9a6SuqWot/AAA
		AAASAAACgAAAAEAAAAMB6hp4JNiR3jr4dKopeH7GQAAAAczT2A3qfTqtsrqW5eoLyOc
		qqNmrUmc2LXvF4U/UBcyWZBW7eTD25zEc35dOgniuY4UAn5R9zYzmvn3YSijr0syGTc
		nEsdRJoRxv3bKR/QL7x40UKskGbQhGNLw/YaFHHmrouEz3EJH7cLXgBw1RoHjIx4rTpjk
		BI51BUir5nH2mf4lYQMzz9jIp+1tS+8mRHFAAAAHRkg52EGMDWfVE/+0zPhSlIpbgc" />
<add key="photosTable" value="Photos" />
<add key="photosDir" value="~/Photos" />
</appSettings>
</configuration>

One of the advantages of this approach are that the code to access these configuration settings remains the same as that we use to access any application configuration in .NET:

this.sqlConnectionPhotos.ConnectionString =
DPAPIUtil.DecryptFromMachineStore(
ConfigurationSettings.AppSettings["photosConnection"]);

Type Serialization

Another issue to consider with component design are the data types being passed between serviced components and managed code. Consider the following method exposed by the mlb.Util.IO.FileManager type in Part 1:

public void SaveFile(System.IO.Stream fileStream, string filename)
{...}

Unfortunately the Stream type (in this case the type is an HttpInputStream) cannot be marshaled to the component via DCOM. Since it is a reference type, and does not implement the IPersistStream interface, the method signature must be modified to support a type that can be marshaled. By default, value types and serializable objects (marked by the SerializableAttribute and optionally implement the ISerializable interface) can be marshaled between managed code and serviced components.

I made a change to the method signature and related code to accommodate new plans to distribute the type as a serviced component. Here’s the resulting method signature:

public void SaveFile(byte[] fileStream, string filename)

This change was not altogether unnatural since the SaveFile code was in fact formerly converting the Stream into a byte array. So, this change moved the logic for the translation into the calling client, making the FileManager type more generic. What this should tell you, however, is that you must consider component design if you will be employing serviced components in the application.

Using serializable types for component design also promotes the ability to later distribute related components as independent subsystems. For example, data access and/or file IO services may later be moved to another physical tier in the system, and even become shared services to multiple applications within an enterprise. As such, the inputs and outputs to the respective subsystems must be flexible enough so that references need not be passed around (less scalable), and so clients are not required have knowledge of specific complex types (tight coupling). A more generic approach accommodates reuse and the possibility of exposing Web service interfaces in front of such subsystems.

But Wait…There’s More!

This article (both Part 1 and 2) aggregates a number of independently complex subjects to provide you with a global view for securing ASP.NET applications. Regardless if you are new to the concepts discussed, after reading both parts you should have a good idea of the steps required to sandbox components and reduce the attack surface of your applications. Developing applications with security in mind clearly requires discipline and many layers of consideration including Windows security, code access security and application architecture. Consider this a kick-start in the right direction, and be sure to consult the resources section for deeper guidance on each subject.

Resources

Get the code sample here:

http://www.dotnetdashboard.net/sessions/securitysummit.aspx

Related books:

The .NET Developer's Guide to Windows Security, by Keith Brown
http://www.amazon.com/exec/obidos/tg/detail/-/0321228359/qid=1106467635/sr=8-1/ref=pd_csp_1/102-7782149-0234537?v=glance&s=books&n=507846

COM and .NET Component Services, by Juval Lowy
http://www.oreilly.com/catalog/comdotnetsvs/

Authors

Michele is a Chief Architect with IDesign, Microsoft Regional Director for San Diego, Microsoft MVP for Web Services and a BEA Technical Director. In addition, Michele is a member of the board of directors for the International Association of Software Architects (IASA). At IDesign Michele provides high-end architecture consulting services, training and mentoring. Her specialties include architecture design for robust, scalable and secure .NET architecture; localization; Web applications and services; and interoperability between .NET and Java platforms. Michele is a member of the INETA; a frequent conference presenter at major technology conferences such as Tech Ed, PDC, SD and Dev Connections; conference chair for SD’s Web Services track; and a regularly published author. Michele’s next book is Windows Communication Framework Jumpstart for O’Reilly, due out in early 2006. Reach her at www.idesign.net or visit her blog at www.dasblonde.net.

News | Blogs | Discussions | Tech talks | White Papers | Downloads | Articles | Media kit | About
All Content Copyright ©2007 TheServerSide Privacy Policy
Site Map