ASP.NET Under the Hood
LANGUAGES: VB.NET | C#
ASP.NET VERSIONS: 2.0
ASP.NET Security Design Guidelines
Beyond Credentials: Component Design and Deployment Decisions
By Michele Leroux Bustamante
Welcome back ASP.NET architects and developers! This month we tackle component design and deployment. As always, samples are provided in C# and VB.NET (see end of article for download details). Please send your questions and requests to [email protected] let me unravel some ASP.NET mysteries for you!
Q. Could you write a summary of the design guidelines you often talk about, related to identities, roles, components, and distribution?
A. This question has come to me on several occasions, based on conference talks, articles, and classes I teach on various aspects of ASP.NET security. There are many resources out there describing various aspects of ASP.NET security, but not many that collate the information related to architecture design and deployment. Beyond the many detailed aspects of security (validation, SQL injection protection, custom error pages, and more), there are also many design decisions that affect your approach to component allocation and deployment. These are the concepts I will focus on in this article.
First, let me establish a high-level list of design influences that relate to security; then I ll attack each separately. The following topics should be considered in each and every Web site deployment:
- Role-based Security
- Strongly Named Assemblies
- Code Access Security
- Run-time Identities
- Component Distribution
Let s dig in!
Of course, the first stop for securing a Web application is to authenticate users and authorize their access to content, functionality, and other resources. The most common forms of authentication are to let IIS authenticate users by gathering Windows credentials, or to configure IIS for anonymous access and gather your own credentials from a login page. After authenticating the user and gathering their roles, we can later authorize their access to content, resources, functionality, and more. So, the most important aspect of the authentication step is that it attaches a security principal to the request thread (and the HttpContext) so that role-based checks can do their job.
Security principals come in the form of WindowsPrincipal or GenericPrincipal. You can also implement a custom security principal by implementing the IPrincipal interface. Each principal holds information about the user s identity (WindowsIdentity, GenericIdentity, custom IIdentity implementation) and its roles. How the security principal is attached to the thread is usually handled for you through Windows authentication, or the forms authentication process. The ASP.NET Login control automatically attaches a GenericPrincipal to the thread after authenticating the user through the configured ASP.NET membership and role providers. Regardless of the role provider you use, you can use role-based authorization to do the following:
- Restrict access to pages, directories, and other
file resources protected by ASP.NET security using the
section of the web.config.
- Control what content is presented within a page or control using role-aware controls like SiteMapProvider or LoginView, or by programmatically checking the security principal s role.
- Limit access to component functionality for types, operations, or more granular code segments using permission demands.
The first two of these are well described in numerous Web resources. The latter, surprisingly, is not applied often enough, so I will review the concept here.
Business components supporting your Web application represent an API layer to access a specific set of functionality. At the business tier, business rules drive access to features and functionality. So, you can (and should) protect access to those components using permission demands. Let s take a look at the example illustrated in Figure 1.
Figure 1: Role-based security applied at several levels in an ASP.NET application.
The PhotoUploadManager component provides three functions:
- UploadPhoto, accessible only to Administrators.
- GetPhotos, accessible to Users and Guests.
- GetPhotoDetail, accessible only to Users.
PrincipalPermissionAttribute is applied to each method to prevent unauthorized access to the feature, as shown in Figure 2.
Public Shared Sub UploadPhoto(ByRef imageInfo as ImageInfo)
Public Shared Function GetPhotos() As ImageInfo()
Public Shared Function GetPhotoDetail(ByVal id as Long) As ImageInfo
Figure 2: PrincipalPermissionAttribute is applied to each method to prevent unauthorized access to the feature.
You can also see from Figure 1 that the role-based security constraints placed at the user interface tier are less restrictive. Any authenticated user is entitled to access default.aspx and display.aspx, but the code behind the default page checks the user s role before forwarding them to the details page:
If System.Web.Security.Roles.IsUserInRole("User") Then
By applying role-based security to your components, you can ensure that they are not misused. This also enables you to have an entry point for auditing user access to specific features. In fact, even with Windows authentication, if the business layer has controls in place that restrict access based on roles, and if the security principal is attached to the thread, impersonation can be avoided without sacrificing security and auditing measures.
Strongly Named Assemblies
Before ASP.NET 2.0 you could not sign the application assembly. That means that Web application pages and user control code could be compromised through a malicious attack because friendly named assemblies are not verified for integrity when the runtime loads them. Now, all source code that comprises the ASP.NET application can be precompiled into strongly named assemblies prior to deployment. For example, the following instructions tell the ASP.NET compiler to pre-compile a Web site, give it a strong name using mlbkey.snk, and apply AllowPartiallyTrustedCallersAttribute to generated assemblies:
aspnet_compiler -v /MyWebSite -u -f c:\MyWebSiteDeploy
snk -fixednames -aptca
An ASP.NET application deployment should have the following source tiers:
- Inline page code for Web forms, user controls, and master pages, plus other files, such as resources (.resx), graphics, and XML.
- Supporting code for pages (formerly known as code-behind) and other source code placed in the App_Code directory.
- Other assembly dependencies, including your business and data access components (although the latter should be accessed through business components, in a service-oriented environment).
In an enterprise site, inline page code should be avoided and supporting page code (that is compiled to assemblies) should not include business logic. Instead, they should coordinate calls to business components that are separately signed and deployed to the global assembly cache (GAC). GAC-deployed assemblies load more quickly, but they are also verified for integrity upon insertion to the GAC. If you properly protect the private key used to sign the application assemblies, the risk of those assemblies being tampered with is reduced significantly. The GAC must also be protected through proper management of the administrative accounts with access to the machine. Not to mention, skip verification should never be enabled for assemblies, with the exception of your development machine when you are using delay-signing techniques.
Code Access Security
Code access security makes it possible to restrict what a component is allowed to do, regardless of the logged-in user or security principal. There are a number of places where code access security must be considered in an ASP.NET application, including:
- At the Web resource level, for inline or binding code executed by Web forms, user controls, and master pages.
- At the application assembly level, for the code supporting each Web resource.
- At the business and data tier, for assemblies that support the application.
You should reduce the privileges of the ASP.NET resource
tier (really, the application domain) by setting the
This means that no malicious code added to .aspx files will be able to invoke business components or .NET assemblies that require permissions beyond the setting. This implies that the application assemblies generated when you pre-compile should be signed, and include AllowPartiallyTrustedCallersAttribute (otherwise, the lower trust pages can t invoke the code, and that wouldn t be much use).
Your business and data tier components should be signed, installed to the GAC, and granted FullTrust permission using the Microsoft .NET Framework 2.0 Configuration MMC snap-in (see Figure 3). In fact, your machine policy should only include My_Computer_Zone with Microsoft_Strong_Name and ECMA_Strong_Name and YOURCOMPANY_Strong_Name. All other code groups should be deleted from the policy; therefore, other assemblies outside this range will not be trusted. If you require third-party assemblies for your site to function, you can add a code group for them, as well. (NOTE: Any third party without a strong name is a third party to avoid.)
Figure 3: Code access security elevation for trusted assemblies based on their strong name.
By granting FullTrust to assemblies, your application depends on (and trusts) they will be able to function: access the file system or registry, perform network activities, decrypt configuration settings (DPAPI), and so on. The snag you might run into is when one of those assemblies doesn t allow partially trusted callers. Of course, any stack walk will fail at the ASP.NET resource tier because you reduced the code access security configuration to thwart attacks.
To work around this issue, you can assert FullTrust for a very short period (long enough to make the call that requires it). You can only assert permissions you have been granted, and it will prevent the stack walk from failing:
Dim namedSet as NamedPermissionSet = new NamedPermissionSet(
...do restricted work
This reduces the window of opportunity for a malicious attack.
I often hear people confused by the concept of process identity and security principal, so I d like to review that here. First of all, each process runs under a particular process identity. In the case of ASP.NET application code, it is hosted by the ASP.NET worker process, and therefore runs with the assigned ASP.NET identity (usually ASPNET or NETWORK SERVICE). This identity has nothing to do with the user who authenticated into the application.
When a Windows user is authenticated by IIS, or when the Login control authenticates a user, a security principal is attached to the thread, and the User object is set for the HttpContext. The security principal is used for role-based security checks. That s it. The Windows principal, however, governs what the process has access to in terms of resources, like registry, file system, network, database, and more. Regardless of the code access rights granted to the application assemblies, if the process identity is not entitled to access a particular resource, an exception will occur. Of course! Windows security is first and foremost. Code access security is an additional measure of protection that helps in cases where a hacker may have gained control of a Windows account.
You always want the ASP.NET worker process to run with restricted rights. It should not be entitled to access any database, network storage ... really, any useful resource. The way to gain access to protected resources is to a) impersonate an account for a short duration to make the call, or b) call a component that runs in a separate process that has those rights. Figure 4 illustrates the first scenario; Figure 5 illustrates the second.
Figure 4: Run-time impersonation for short durations narrow the window for an attacker to access the protected resource.
Figure 5: Distributing functionality across process and machine boundaries.
To achieve the first scenario you would leverage LogonUser and ImpersonateWindowsIdentity from the Win32 API, to handle impersonation. This requires asserting code access permissions to make the call, and then reverting the assertion, and the impersonation context, when you are finished (see Figure 6).
Dim principal as WindowsPrincipal = Nothing
Dim context as WindowsImpersonationContext = Nothing
Dim secPerm as SecurityPermission = new SecurityPermission(
principal = GetLogonUser(domain, userName, password);
...access protected resources
Figure 6: Leverage LogonUser and ImpersonateWindowsIdentity.
The problem with the first scenario in the previous section is that, in order to assert and impersonate, it litters your code with a lot of clutter unrelated to the business logic. For smaller Web applications where the entire site is hosted in a single physical tier, this at least provides a reduced attack surface by limiting the impersonation window. But, in a larger service-oriented application, you would design your business tier so that each major chunk of business functionality could function whether it is accessed in-process, out-of-process, or across machine boundaries. Both Enterprise Services and Windows Communication Foundation (WCF) make it possible to wrap your business logic in this way, and, without modification to client code, distribute and configure as you wish.
Process and machine boundaries also imply security boundaries. They put another layer between your resources and the entry point to the application. Serviced components and services can play host for business components that must execute with a particular process identity, authenticate callers to that process, and successfully access resources that can authorize the calling process identity no impersonation is required (see Figure 5, which illustrates this point).
Crossing process and machine boundaries does carry a performance penalty, and yet can on a wider scale increase the overall throughput of the application by allowing it to scale out more flexibly. Of course, performance is part of a different conversation altogether, so regarding security, this model of hosting services with a process identity appropriate for their functionality can help you decouple security requirements from the application code, and leave configuration more flexible, at that.
As important as more granular efforts to secure your application are, the concept of designing with security in mind is equally important. It is through the combined effort of sound network and infrastructure security, best practice coding efforts, component design, and deployment configuration that your ASP.NET applications will be secured. This article focused on some specific items related to design, which may also pique your interest in the subjects of code access security, role-based permission demands, service orientation, and component distribution. The readme file in the code download provides several resources to follow up on from this overview, including reference to the more granular security measures you can take to protect your application. Enjoy!
If you have questions or comments regarding this column, or any other ASP.NET topics, please drop me a line at [email protected]. Thanks for reading!
C# and VB.NET code examples are available for download.
Michele Leroux Bustamante is Chief Architect at IDesign Inc., Microsoft Regional Director for San Diego, Microsoft MVP for XML Web services, and a BEA Technical Director. At IDesign Michele provides training, mentoring, and high-end architecture consulting services, specializing in scalable and secure .NET architecture design, globalization, Web services, and interoperability with Java platforms. She is a board member for the International Association of Software Architects (IASA), a frequent conference presenter, conference chair of SD s Web Services track, and a frequently published author. She is currently writing a book for O Reilly on the Windows Communication Foundation. Reach her at http://www.idesign.net or http://www.dasblonde.net.
Michele s blog: http://www.dasblonde.net
Michele s WCF book: http://www.thatindigogirl.com