From an application point of view, security is mostly a matter of authenticating users and authorizing actions on the system's resources. ASP.NET provides a range of authentication and authorization mechanisms implemented in conjunction with IIS, the .NET Framework, and the underlying security services of the operating system. The overall security context of an ASP.NET application is composed of three distinct levels:
The IIS level associates a valid security token with the sender of the request. The security token is determined according to the current IIS authentication mechanism.
The ASP.NET worker process level determines the identity of the thread in the ASP.NET worker process serving the request. If enabled, impersonation settings can change the security token associated with the thread. The identity of the process model is determined by settings in the configuration file or the IIS metabase, according to the process model in use.
The ASP.NET pipeline level gets the credentials of the application-specific user who is using the application. The way this task is accomplished depends on the application settings in the configuration files for authentication and authorization. A common setting for most ASP.NET applications is using Forms authentication.
Among other things, the identity of the ASP.NET worker process influences access to local files, folders, and databases.
When an ASP.NET request arrives at the Web server machine, IIS picks it up and assigns the request to one of its pooled threads. IIS runs under the SYSTEM account the most powerful account in Microsoft Windows. From this point forward when processing this request, the three security contexts of ASP.NET applications I mentioned execute, one after the other.
The thread that physically handles the request impersonates an identity according to the current IIS authentication settings: Basic, Digest, Integrated Windows, or anonymous. If the site is configured for anonymous access, the identity impersonated by the thread is the one you set through the dialog box shown in Figure 15-1. By default, it is named IUSR_xxx, where xxx stands for the machine name. (The dialog box is the Properties dialog box of the IIS administrative manager application.)
Figure 15-1: Enabling anonymous access and editing authentication methods for the site.
Basic authentication is an HTTP standard supported by virtually any browser. With this type of authentication, a request bounces back with a particular HTTP status code that the browser understands as a demand to display a standard dialog box to request the user's credentials. The information gathered is sent to IIS, which attempts to match it with any of the Web server's accounts. Because credentials are sent out as Base64 encoded text (essentially in clear text) Basic authentication is recommended only for use over HTTPS secure channels.
Digest authentication differs from Basic authentication mostly because it hashes credentials before sending. Digest authentication is an HTTP 1.1 feature and, as such, is not supported by all browsers. In addition, on Windows 2000 it requires the password to be stored on the server with reversible encryption. This is no longer a requirement with Windows Server 2003. Both Basic and Digest authentication work well through firewalls and proxy servers.
Integrated Windows authentication sets up a conversation between the browser and the Web server. The browser passes the credentials of the currently logged-on user, who is not required to type anything. The user needs to have a valid account on the Web server or in a trusted domain to be successfully authenticated. The authentication can take place through the NTLM challenge/response method or by using Kerberos. The technique has limited browser support and is impractical in the presence of firewalls.
Note | Yet another type of authentication mode exists and is based on certificates. You can use the Secure Sockets Layer (SSL) security features of IIS and use client certificates to authenticate users requesting information on your Web site. SSL checks the contents of the certificate submitted by the browser for the user during the logon. Users obtain client certificates from a trusted third-party organization. In an intranet scenario, users can also get their certificates from an authority managed by the company itself. |
After authentication, the thread dispatches the request to the appropriate external module. For an ASP.NET request, what happens depends on the process model in use within the application.
In the IIS 5.0 process model, the IIS thread hands the request out to aspnet_isapi.dll which, in turn, starts the aspnet_wp.exe worker process. In the IIS 6.0 process model, the request is queued to the application pool that hosts the ASP.NET application and picked up by the copy of w3wp.exe IIS worker process that serves the application pool. What is the identity of the worker process?
If the IIS 5.0 process model is used, the worker process runs under the guise of the ASPNET account. ASPNET is a local account created when the .NET Framework is installed. It has the least set of privileges required for its role and is far less powerful than the SYSTEM account. You can change the identity to an existing account through the following section in machine.config:
<processModel userName="..." password="..." />
If the IIS 6.0 process model is used, the worker process has the identity of the NETWORK SERVICE account. You can change it through the dialog box shown in Figure 15-2. In the IIS administrative manager, you select the application pool of choice and click to see its properties. NETWORK SERVICE has the same limited set of privileges as ASPNET and is a new built-in account in Windows Server 2003 and Windows XP.
Figure 15-2: Setting the default identity of the worker process serving an application pool in IIS 6.0.
Inside the worker process, a pooled thread will pick up the request to serve it. What's the identity of this thread? If impersonation is disabled in the ASP.NET application, this thread will inherit the identity of the worker process. This is what happens by default. If impersonation is enabled, the worker thread will inherit the security token passed by IIS. You enable impersonation in the web.config file as follows:
<impersonation enabled="true" />
There's more to say about ASP.NET impersonation, particularly about the extended form of impersonation where a fixed identity is specified. We'll discuss this further in a moment.
When impersonation is active, the worker process account doesn't change. The worker process still compiles pages and reads configuration files using the original account. Impersonation is used only with the code executed within the page and not for all the preliminary steps that happen before the request is handed to the page handler. For example, this means that any access to local files or databases occurs using the impersonated account, not the worker process's account.
The third security context indicates the identity of the user making the request. The point here is authenticating the user and authorizing access to the page and its embedded resources. Obviously, if the requested page is freely available, no further step is performed; the page output is generated and served to the user.
To protect pages against unauthorized access, an ASP.NET application needs to define an authentication policy Windows, Passport, or Forms. Authentication modules hook up requests for protected pages and manage to obtain the user's credentials. The user is directed to the page only if the credentials are deemed valid and authorize access to the requested resource.
In a situation in which you want to change the default ASP.NET account to give it more privileges, how should you proceed? Is it preferable to create a custom account and use it for the worker process, or should you opt for the worker process to impersonate a fixed identity?
Note | You can hardly create a new, functional account with less than the privileges granted to ASP.NET or NETWORK SERVICE. If you give it a try, make sure you pass through a careful testing phase and ensure that it really works for your application. |
Using the <processModel> section (for the IIS 5.0 process model) or using the dialog box shown in Figure 15-2 (for the IIS 6.0 process model) are the only ways to change the real identity of the ASP.NET process. If you change the process identity, all threads in the process will use this as the base identity and no extra work is needed on thread switches. More important, you should make sure the new account has at least full permissions on the Temporary ASP.NET Files folder. (Review carefully the list of permissions granted to the standard ASP.NET accounts, which you can find in the "Privileges of the ASP.NET Default Account" section.)
Alternately, you could require the worker process to impersonate a fixed identity through the <identity> section of the web.config file. Note that when fixed impersonation is used, every worker thread processing a request needs to impersonate the specified account. Impersonation must be performed for each thread switch because a thread switch event takes the thread identity back to the process identity.
To impersonate a fixed identity, you first define the user account and then add a setting to the web.config file. The following snippet shows an example:
<identity impersonate="true" userName="MyAspNetAccnt" password="ILoveA$p*SinceVer3.0" />
As mentioned earlier, impersonation doesn't really change the physical identity of the process running ASP.NET. More simply, all threads serving in the context of the ASP.NET worker process will always impersonate a given user for the duration of the application.
Impersonating a fixed identity is different from classic, per-request impersonation such as impersonating the identity of the Windows user making the request. Per-request impersonation refers to the situation in which you enable impersonation without specifying a fixed identity. In this case, the security token with identity information is created by IIS and inherited by the worker process. When a fixed identity is involved, the security token must be generated by the ASP.NET worker process. When running under a poorly privileged account, though, the ASP.NET worker process sometimes lacks the permission to do that.
A process running under a nonadministrator account cannot impersonate a specific account on Windows 2000 unless you grant it appropriate privileges. Under Windows 2000, a process requires the Act As Part Of The Operating System privilege to impersonate a fixed identity. This is indeed a strong and powerful privilege that, for security reasons, nonadministrator process accounts such as ASPNET and NETWORK SERVICE generally will not have.
The requirement disappears with Windows XP and Windows Server 2003, which will make it possible for processes lacking the Act As Part Of The Operating System privilege to impersonate a given identity.
In ASP.NET 1.1, though, impersonating a fixed identity is possible also under Windows 2000 machines and IIS 5.0. The ASP.NET 1.1 runtime plays some tricks to re-vector the call back to the aspnet_isapi.dll module, which is running inside IIS 5.0 and under the SYSTEM account. Basically, the ASP.NET 1.1 ISAPI extension creates the security token and duplicates it in the memory space of the worker process. In this way, ASP.NET 1.1 supports fixed impersonation without requiring the Act As Part Of The Operating System privilege on the worker process account.
In the end, expect to have troubles with fixed impersonation only if you're still running ASP.NET 1.0 applications under Windows 2000. In this case, for the impersonation to work you need to run your ASP.NET applications under the SYSTEM account, with all the security repercussions that this entails.
A third possibility to change the identity of the ASP.NET worker process is by impersonating through the anonymous account. The idea is that the ASP.NET application grants access to anonymous users, and the anonymous account is configured to be the desired account for the application.
In this case, the application uses per-request impersonation and the ASP.NET code executes as the impersonated account. The process account remains set to ASPNET or NETWORK SERVICE, which means that you don't have to worry about replicating into the new account the minimum set of permissions on folders that allow ASP.NET to work.
Of all the possible user-rights assignments, ASPNET and NETWORK SERVICE are granted only the following five:
Access this computer from the network
Deny logon locally
Deny logon through Terminal Services
Log on as a batch job
Log on as a service
In addition, the accounts are given some NTFS permissions to operate on certain folders and create temporary files and assemblies. The folders involved are these:
.NET Framework Root Folder This folder contains some .NET Framework system assemblies that ASP.NET must be able to access. The physical folder is normally Microsoft.NET\Framework\[version] and is located under the Windows folder. ASP.NET has only read and list permissions on this folder.
Temporary ASP.NET Files This folder represents the file system subtree in which all temporary files are generated. ASP.NET is granted full control over the entire subtree.
Global Assembly Cache ASP.NET needs to gain read permissions on the assemblies in the global assembly cache (GAC). The GAC is located in the Windows\Assembly\GAC folder. The GAC folder is not visible in Windows Explorer, but you can view the installed assemblies by opening the Windows\Assembly folder.
Windows System Folder The ASP.NET process needs to access and read the System32 Windows folder to load any necessary Win32 DLLs.
Application Root Folder The ASP.NET process needs to access and read the files that make up the Web application. The folder is typically located under Inetpub\Wwwroot.
Web Site Root ASP.NET might have the need to scan the root of the Web server typically, Inetpub\Wwwroot looking for configuration files to read.
An ASP.NET application running under an account that lacks some of these permissions might fail. Granting at least all these permissions is highly recommended for all accounts used for fixed account impersonation.
ASP.NET applications are made of managed code and run inside the common language run-time (CLR). In the CLR, running code is assigned to a security zone based on the evidence it provides about its origin for example, the originating URL. Each security zone corresponds to a set of permissions. Each set of permissions correspond to a trust level. By default, ASP.NET applications run from the MyComputer zone with full trust. Is this default setting just evil?
An ASP.NET application runs on the Web server and doesn't hurt the user that connects to it via the browser. An ASP.NET application cannot be consumed in other ways than through the browser. So why do several people feel cold shivers down their spine as they think of ASP.NET full trust?
The problem is not with the ASP.NET application itself, but with the fact that it is publicly exposed over the Internet one of the most hostile environments for computer security you can imagine. Should a fully trusted ASP.NET application be hijacked, a hacker could perform restricted actions from within the worker thread. In other words, a publicly exposed fully trusted application is a potential platform for hackers to launch attacks. The less an application is trusted, the most secure that application happens to be.
By default, ASP.NET applications run unrestricted and are allowed to do whatever their account is allowed to do. The actual security restrictions that sometimes apply to ASP.NET applications (for example, the inability to write files) are not a sign of partial trust, but more simply the effect of the underprivileged account under which ASP.NET applications normally run.
By tweaking the <trust> section in the root web.config file, you can configure code access security permissions for a Web application and decide whether it has to run fully or partially trusted:
<trust level="Medium" originUrl="" />
Table 15-2 describes the levels of trust available.
Level | Description |
---|---|
Full | Applications run fully trusted and can execute arbitrary native code in the process context in which they run. This is the default setting. |
High | Code can use most permissions that support partial trust. This is appropriate for applications you want to run with least privilege to mitigate risks. |
Medium | Code can read and write its own application directories and can interact with data-bases. |
Low | Code can read its own application resources but can't interact with resources located outside of its application space. |
Minimal | Code can't interact with any protected resources. Appropriate for nonprofessional hosting sites that simply intend to support generic HTML code and highly isolated business logic. |
Note | Web applications and Web services built using .NET Framework version 1.0 always run with unrestricted code access permissions. Configurable levels listed in Table 15-2 do not apply to ASP.NET 1.0 applications. |
Admittedly, restricting the set of things an application can do might be painful at first. However, in the long run (in other words, if you don't just give up and deliver the application) it produces better and safer code.
Note | The <trust> section supports an attribute named originUrl. The attribute is a sort of misnomer. If you set it, the specified URL is granted the permission to access an HTTP resource using either a Socket or WebRequest class. The permission class involved with this is Web-Permission. Of course, the Web permission is granted only if the specified <trust> level supports that. Medium and higher trust levels do. |
Let's review in more detail the permissions granted to ASP.NET applications when the various trust levels are applied. Key ASP.NET permissions for each trust level are outlined in Table 15-3.
High | Medium | Low | Minimal | |
---|---|---|---|---|
FileIO | Unrestricted | Read/Write to application's space | Read | None |
IsolatedStorage | Unrestricted | ByUser | ByUser (maximum of 1 MB) | None |
Printing | DefaultPrinting | Same as High | None | None |
Security | Assertion, Execution, ControlThread, ControlPrincipal | Same as High | Execution | Execution |
SqlClient | Unrestricted | Unrestricted (no blank password allowed) | None | None |
Registry | Unrestricted | None | None | None |
Environment | Unrestricted | None | None | None |
Reflection | ReflectionEmit | None | None | None |
Socket | Unrestricted | None | None | None |
Web | Unrestricted | Connect to origin host, if configured | Same as High | None |
More detailed information about the permissions actually granted to the default trust levels is available in the security configuration files for each level. The name of the file for each level is stored in the <trustLevel> section.
In the end, full-trust applications run unrestricted. High-trust applications have read/write permission for all the files in their application space. However, the physical access to files is still ruled by the NTFS access control list on the resource. High-trust applications have unrestricted access to Microsoft SQL Server but not, for example, to OLE DB classes. (The OleDbPermission and other managed provider permissions are denied to all but fully trusted applications.) Reflection calls are denied with the exception of those directed at classes in the System.Reflection.Emit namespace.
Medium applications have unrestricted access to SQL Server, but only as long as they do not use blank passwords for accounts. The WebPermission is granted to both medium-and low-trust applications, but it requires that the URL be configured in the <trust> section through the originUrl attribute. Low-trust applications have read-only permission for files in their application directories. Isolated storage is still permitted but limited to a 1-MB quota.
A rule of thumb is that Medium trust should be fine for most ASP.NET applications and applying it shouldn't cause significant headaches, provided that you don't need to access legacy COM objects or databases exposed via OLE DB providers.
What if one of the tasks to perform requires privileges that the trust level doesn't grant? There are two basic approaches. The simplest approach is to customize the policy file for the trust level and add any permissions you need. This solution is easy to implement and doesn't require code changes. It does require administrator rights to edit the security policy files. From a pure security perspective, it is not a great solution, as you're just adding to the whole application the permissions you need for a particular method of a particular page or assembly.
The second approach requires a bit of refactoring but leads to better and safer code. The idea is to sandbox the server-side code and make it delegate to external components (for example, serviced components or command-line programs) the execution of any tasks that exceeds the application's permission set. Obviously, the external component will be configured to have all required permissions.
Note | Code sandboxing is the only option you have if your partially trusted ASP.NET application is trying to make calls into an assembly that doesn't include the AllowPartially-TrustedCallers attribute. For more information on programming for medium trust, check out the contents at the following URL: http://msdn.microsoft.com/library/en-us/dnpag2/html/PAGHT000020.asp. |
Depending on the type of the requested resource, IIS might or might not be able to handle the request itself. If the resource needs the involvement of ASP.NET (for example, if it is an .aspx file), IIS hands the request over to ASP.NET along with the security token of the authenticated, or anonymous, user. What happens next depends on the ASP.NET configuration.
ASP.NET supports three types of authentication methods: Windows, Passport, and Forms. A fourth possibility is None, meaning that ASP.NET does not even attempt to perform its own authentication and completely relies on the authentication already carried out by IIS. In this case, anonymous users can connect and resources are accessed using the default ASP.NET account.
You choose the ASP.NET authentication mechanism using the <authentication> section in the root web.config file. Child subdirectories inherit the authentication mode chosen for the application. By default, the authentication mode is set to Windows. Let's briefly examine the Windows and Passport authentication and reserve wider coverage for the most commonly used authentication method Forms authentication.
When using Windows authentication, ASP.NET works in conjunction with IIS. The real authentication is performed by IIS, which uses one of its authentication methods: Basic, Digest, or Integrated Windows. When IIS has authenticated the user, it passes the security token on to ASP.NET. When in Windows authentication mode, ASP.NET does not perform any further steps and limits its use of the IIS token to authorizing access to the resources.
Typically, you use the Windows authentication method in intranet scenarios when the users of your application have Windows accounts that only can be authenticated by the Web server. Let's assume that you configured the Web server to work with the Integrated Windows authentication mode and that you disabled anonymous access. The ASP.NET application works in Windows authentication mode. What happens when a user connects to the application? First, IIS authenticates the user (popping up a dialog box if the account of the local user doesn't match any accounts on the Web server or in a trusted domain) and then hands the security token over to ASP.NET.
In most cases, Windows authentication is used in conjunction with file authorization via the FileAuthorizationModule HTTP module. User-specific pages in the Web application can be protected from unauthorized access by using access control lists (ACLs) on the file. When ASP.NET is about to access a resource, the FileAuthorizationModule HTTP module is called into action. File authorization performs an ACL check on ASP.NET files using the caller's identity. This means that the user Joe will never be able to access an .aspx page whose ACL doesn't include an entry for him.
Note, though, that file authorization does not require impersonation at the ASP.NET level and, more important, works regardless of whether the impersonation flag is turned on. There's good news and bad news in this statement. The good news is that once you've set an appropriately configured ACL on an ASP.NET resource, you're pretty much done. Nobody will be able to access the resource without permission. But what about non-ASP.NET resources such as local files?
For example, how can you protect an HTML page from unauthorized access? The first consideration is that an HTML page rarely needs to be protected against access. If you need to protect a page, the page very likely also executes some server-side code. Implementing it as .aspx is more useful anyway. That said, by design HTML pages are not protected from unauthorized access because they are not processed by the ASP.NET pipeline. If you want to protect it, the best thing you can do is rename the page to .aspx so that it is processed by the ASP.NET runtime.
Note | HTML pages and other resources can still be protected using NTFS permissions. If particular constraints are set on a file using NTFS permissions, there's no way the file can be accessed by anyone lacking proper rights. |
Alternately, you can rename the page to a custom extension and write a lightweight Internet Server Application Programming Interface (ISAPI) extension (or a managed HTTP handler) to implement a made-to-measure authorization mechanism. I discuss HTTP handlers in my other recent book, Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics (Microsoft Press, 2005).
Note | Windows authentication also works with URL authorization implemented by the HTTP module named URLAuthorizationModule. This module allows or denies access to URL resources to certain users and roles. (We'll talk more about URL authorization later while discussing Forms authentication.) |
Passport authentication is a Microsoft-centralized authentication service. Passport provides a way to authenticate users coming across all the sites that participate in the initiative. Users need to do a single logon and, if successfully authenticated, they can then freely move through all the member sites. In addition to the single logon service, Passport also offers core profile services for member sites.
ASP.NET provides the PassportAuthenticationModule HTTP module to set up authentication for Web applications hosted by Passport member sites. When an HTTP request hits a Passportenabled Web site, the HTTP module verifies whether the request contains a valid Passport ticket. If not, the Web server returns the status code 302 and redirects the client to the Passport logon service. The query string contains properly encrypted information about the original request. The client issues a GET request to the logon server and passes the supplied query string. At this point, the Passport logon server prompts the client with an HTML logon form. After the user has filled out the form, the form is posted back to the logon server over an SSL-secured channel.
The logon server uses the form information to authenticate the user and, if successful, creates a Passport ticket. Next, the user is redirected to the original URL and the ticket is passed, encrypted, in the query string. Finally, the browser follows the redirect instruction and requests again the original Passport protected resource. This time, though, the request contains a valid ticket so that the PassportAuthenticationModule will allow the request to pass.