One of the most important security principles for software development is least privilege.

Simply put, least privilege means that an application, process, or user should have the least access to resources required to accomplish a task and no more. By following this principle, even if your application is attacked or a user goes on the payroll of your nastiest competitor, you’ll have limited the potential damage. Bottom line: implementing partial trust in ASP.NET is the single biggest thing you can do to make your applications secure.

ASP.NET has some powerful tools for implementing the principle of least privilege in your applications. But developers often do not use partial trust techniques, which never ceases to surprise me. Sure, the techniques require understanding security and how ASP.NET implements code access security, but isn’t that pretty much a prerequisite these days for secure ASP.NET development? (The answer is Yes! in case you weren’t sure.) Sure, partial trust techniques take a little more work, but by the end of this article you’ll understand that it isn’t all that much more work.

Even though the default trust level in ASP.NET is Full trust (an evil choice of Microsoft’s in my opinion), you should never deploy a real, non-trivial application that way. But what are the other options? What do you have to give up to become more secure?

In this article I’ll look at how to create partially trusted Web applications, but not by using any of the poorly fitting standard trust levels included with .NET out of the box. Instead I’ll show you two easy techniques to make your ASP.NET applications substantially more secure. In the first technique I’ll demonstrate how to create a custom trust level, tailored to the needs of a specific application to offer maximum protection. In the second technique I’ll show you how to segregate dangerous code-code that needs a higher privilege level to perform some specific action-into a protected sandbox. You’ll commonly use a combination of these techniques for the highest level of security.

Along the way I’ll explore permissions and how ASP.NET implements the Common Language Runtime’s (CLR) code access security (CAS) to implement its trust levels. I assume that you know at least the basics of code access security and .NET permissions, as well as how to put together and run ASP.NET applications. You should also be familiar with how .NET configuration files work, understand the roles of Machine.config and the machine-wide root Web.config file, and application configuration files.

I’ll begin with a short overview about partial trust, code access security, and why they are so critical to secure ASP.NET applications. Bear with me through this next section: I’ll cover some important points for understanding partial trust, and then I’ll dive deep into configurations and code.

Partial Trust and Code Access Security

I’ve spoken a lot at user groups and conferences about security in ASP.NET applications. Usually I’ll ask whether anyone has ever developed a partial trust .NET application of any project type. No matter how packed the room, rarely more than two hands go up. And as often as not, at least one of those two had just played around with it rather than deployed a production application using partial trust. The other person had to do it because their hosting company only supports medium trust applications on shared servers. Good for the hosting companies!

In one way, this does not surprise me. Despite Microsoft’s best efforts to make Windows more secure and provide developers with the tools they need to write secure apps, it is still just too damn hard. You can implement all the best practices you want, but all it takes is one junior developer downstairs (it’s always the junior developers, eh?) to make one mistake to open a gaping vulnerability. But to be fair to the junior developers, in this age of “deploy it yesterday” mania, few developers have the luxury of building features that seem to have so nebulous a benefit as security. This is the reason why every week seems to bring news of another site hacked.

In another way, I’m shocked that only two people out of dozens or hundreds have experience with partial trust. Writing partial trust applications takes some knowledge, but not that much nor is it that hard to learn. Given the immense benefits that partial trust brings to Web applications, developers really don’t have an excuse for taking security shortcuts like Full trust. This nasty shortcut is fraught with peril and gaping vulnerabilities.

So what is the real difference between full and partial trust applications that make Full trust so terrifyingly horrible? In essence, when you use Full trust you tell the CLR to ignore code access security: “Don’t check permissions, just run the code so that what the application can do is limited only by Windows security.” This reduces any .NET application to the level of security supported by ActiveX/COM components-a very unsafe level indeed!

When you use partial trust, your application gets some subset of all of the available .NET and custom permissions, meaning that your application can’t do anything and everything. It may not be able to run unmanaged code-such as COM components-or access the network, for example. It may not be able to open an Access database or write to anywhere on the local drive. It no longer has unfettered access to every resource on the local machine or network that Windows allows even to members of the Everyone Windows group.

It may sound undesirable to limit your application like this. But think of it in these terms: If an attacker gets control of your application-and they will, or at least try very hard-what will he or she be able to do? With Full trust, they’ll be able to open any Access database, write to non-protected file locations, and do a lot of other nasty stuff. Are you so confident in your perimeter security that you know that no attacker will ever breach it, even with attacks that are unknown today? No? Then Full trust is probably a bad solution for you.

This makes partial trust a good thing-a desirable security state. But how does your application get specific permissions? I won’t go into the details of how code access security works other than to make one observation about the four policy levels. Figure 1 shows the .NET Configuration Tool 2.0, displaying the three policy levels you can configure with this tool. The tool lets you configure the local machine’s policy level by matching an assembly’s evidence to one or usually more code groups. At the end of the process the assembly has a permission set assigned to it-the group of permissions it will run with.

Figure 1: The .NET Configuration Tool lets you define the security policy for the local machine and current user. It shows three of the four available policy levels.
Figure 1: The .NET Configuration Tool lets you define the security policy for the local machine and current user. It shows three of the four available policy levels.

Under the default policy installed with .NET, the three external policy levels-enterprise, machine, and user-are all a bit moot. Because you install an ASP.NET application on the local machine-it isn’t being run remotely from another location on the network-it gets Full trust by default. This is part of the reason why by default, ASP.NET apps run with Full trust. (I’ll get to the other part later in this article.)

Figure 1 does not display the fourth policy level (Application Domain policy level), which you cannot configure using the .NET Configuration Tool 2.0. The Application Domain policy level is often just called the appdomain level. Because the permission set assigned to an assembly is essentially an intersection of the permissions from the four policy levels, the appdomain permissions are the permissions assigned to the assembly. In other words, the appdomain, through the ASP.NET trust level, controls a Web application’s permissions.

ASP.NET Trust Levels

Let’s take a look at what all this means for a specific application with potential security problems. I’ll use a sample application in this article that initially runs with Full trust and make it more secure by using partial trust. Along the way I’ll explore some of the issues you might face with your own applications.

The sample application is a straightforward file-based ASP.NET application. Its single page does two potentially dangerous things: reads a file and interacts with an environment variable on the server. Figure 2 shows how it reads the eula.txt file that Microsoft installs on most Windows systems at C:\WINDOWS\system32. Microsoft doesn’t want anyone to have any excuse for not being aware of the terms of the Windows end user license agreement, so the security settings on this text file allow members of the Users and Administrators groups to read the file, as shown in Figure 3. And since the application runs with Full trust, the CLR doesn’t do any permission checking.

Figure 2: The sample application for this article performs a couple of potentially dangerous actions. Here it reads the contents of a text file in the Windows System32 directory.
Figure 2: The sample application for this article performs a couple of potentially dangerous actions. Here it reads the contents of a text file in the Windows System32 directory.
Figure 3: Microsoft allows just about everyone who can log into Windows read access to eula.txt, as shown in this properties dialog box for the file. In particular, members of the Users group can read the file.
Figure 3: Microsoft allows just about everyone who can log into Windows read access to eula.txt, as shown in this properties dialog box for the file. In particular, members of the Users group can read the file.

But now imagine that as the developer of the application I make a crucial security mistake. In order to make the application a bit more flexible, I’ve allowed the user to type in the name of the file to display. The code behind the Get Contents button, shown below, concatenates the name with the path of the System32 directory.

string path = @"C:\WINDOWS\system32\";

using (StreamReader reader = 
   new StreamReader(path + FileNameTextBox.Text))
{
   string content = reader.ReadToEnd();
   ResultsLabel.Text = content;
}

Can you see the problem? A clever user could navigate up the file hierarchy, such as by entering ....\boot.ini as the file name. This is commonly called a traversal attack, since the user is traversing the drive’s directory structure. The application will display the contents of the boot.ini file, located in the root of the C: drive. There isn’t anything particularly interesting in that file on my computer, but it’s certainly not something that I want just any user to be able to see. Traversal attacks can expose sensitive files through your Web application.

Another dangerous thing the application does is to read and write the value of the PATH environmental variable, shown in Figure 4. Here, the user can view the current value by clicking the Get Path button and add a new path by entering it in the text box and clicking the Add Path button. The code for reading the path simply uses the Environment class to get the value of the PATH:

Figure 4: The sample application also works with the PATH environment variable, displaying the current value and adding and removing a path.
Figure 4: The sample application also works with the PATH environment variable, displaying the current value and adding and removing a path.
PathLabel.Text = 
   Environment.GetEnvironmentVariable("PATH");

The code for adding to the PATH value is a bit more involved than the above snippet, but only because it includes some rudimentary checking for semicolon delimiters and to make sure that the path isn’t already part of the PATH value. The code for removing the path is similar, except that it silently does nothing if the path in the text box is not part of the PATH variable. The code for both these button Click event procedures is in Listing 1, with the statements that read and write to the PATH variable highlighted.

Okay, okay. Thanks to the magic of the .NET Framework, the code for reading files and working with environment variables is not terribly interesting, and you aren’t likely to put these features into a production application except for maybe an administrative tool. But it makes a useful example for talking about partial trust ASP.NET applications. The code in the sample application runs without problem under the default Full trust because the CLR turns off permission checking when the code runs, and permissions for both actions are allowed by Windows when you run the application on the local machine. This sample scenario shows how an attacker who gets control of your application, such as through clever cross-site scripting, could get some access to Windows resources that they have no business having. You can limit access to resources like these using code access security and thereby protect those resources. But to do that you need to impose partial trust on the application so that the CLR checks and enforces permissions. That’s what I’ll show you how to do.

I need to note one more thing about the sample application. To keep things simple, the principals I’m demonstrating assume that you run it on the local machine in Visual Studio. That way the process identity, using the Cassini development Web server, is your own identity. Presumably you’re at least a member of the Users group (and, alas, more likely the Administrators group), so you can read eula.txt and the PATH variable. If you deploy this application to a Web server, the default ASP.NET process identity may not have those permissions. I went for a simple example that ably demonstrates partial trust techniques rather than something more complicated that would require using IIS. My focus here is on partial trust techniques.

Before I get into the details of how to make this application partial trust, I need to explore how ASP.NET enables partial trust deep in the core configuration files of .NET.

ASP.NET Trust Levels

To understand how ASP.NET implements trust levels, you have to explore sections of the root .NET configuration files. You’ll normally find these in the C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG directory, although your actual name may vary a bit depending on the name of your Windows directory and the Framework version. Since this application deals only with ASP.NET features here, I won’t bother reviewing the sections of Machine.config, which contains settings for all .NET applications on the machine. Instead I’ll just review the root Web.config file, the basis for all ASP.NET applications.

Near the top of the root Web.config file you’ll find the <securityPolicy> and <trust> elements:

&lt;system.web&gt;
   &lt;securityPolicy&gt;
      &lt;trustLevel name="Full" 
         policyFile="internal" /&gt;
      &lt;trustLevel name="High" 
         policyFile="web_hightrust.config" /&gt;
      &lt;trustLevel name="Medium" 
         policyFile="web_mediumtrust.config" /&gt;
      &lt;trustLevel name="Low" 
         policyFile="web_lowtrust.config" /&gt;
      &lt;trustLevel name="Minimal" 
         policyFile="web_minimaltrust.config" /&gt;
   &lt;/securityPolicy&gt;
   &lt;trust level="Full" originUrl="" /&gt;
&lt;/system.web&gt;

securityPolicy defines the standard trust levels in ASP.NET: Full, High, Medium, Low, and Minimal. Each level grants your assemblies a set of specific permissions, going from unlimited permissions in Full trust to absurdly restrictive permissions in the Minimal trust level. The policyFile attribute specifies the file where the trust level is defined. The Full trust level uses “internal” as the policy file name; this level needs no additional configuration file because permission checking is effectively shut off.

The <trust> element sets the default trust level for all ASP.NET applications that don’t override this setting. As you can see in the code above, it is set to Full when you install the .NET Framework, which becomes the default for all Web applications on the machine. Fortunately you can-and should-override it in each application’s Web.config file.

In my opinion, a good starting level for most ASP.NET applications is the Medium trust level because it grants a reasonable set of permissions needed for most common Web applications. The High trust level has far too many permissions, but at least it is better than Full because High doesn’t allow running unmanaged code. Low and Minimal have far too few permissions to realistically run any non-trivial Web application. So I’ll use the Medium trust level as the starting point.

By the way, Microsoft’s names for these trust levels might be a bit confusing. The higher the trust level, the more you trust the code is secure, and the more permissions it has. Low and Minimal ASP.NET applications are very untrustworthy, so they have too few permissions to realistically run a Web application. High means that it is highly trusted code and it has almost all available .NET permissions. All this mumbo jumbo is yet another good reason to use a custom trust level tailored for the needs of a specific application.

If you examine the Medium trust level policy file, web_mediumtrust.config, you can see how ASP.NET defines a trust level. There are three major sections in this XML file: <SecurityClasses>, <NamedPermissionSets>, and <CodeGroup>. SecurityClasses includes a SecurityClass element for every permission, membership condition, and code group object used in this policy file. Each class is given an internal “friendly” name with the Name attribute and the Description attribute includes the strong name of the class. For example, this next code snippet shows the SecurityClass element for the permission that allows the code to connect to a SQL Server database (I’ve added line breaks to fit in the space; the entry is all on one line in the policy file):

&lt;SecurityClass Name="SqlClientPermission" 
Description=
   "System.Data.SqlClient.SqlClientPermission, 
   System.Data, Version=2.0.0.0, Culture=neutral, 
   PublicKeyToken=b77a5c561934e089"/&gt;

The policy file includes three named permission sets: FullTrust, Nothing, and ASP.Net. The FullTrust permission set is again a special case. It doesn’t have to list any permissions because the code has all permissions. Below you can see the FullTrust permission set definition:

&lt;PermissionSet
   class="NamedPermissionSet"
   version="1"
   Unrestricted="true"
   Name="FullTrust"
   Description="Allows full access to all
   resources"
/&gt;

The Unrestricted attribute is the important setting here. It tells the CLR to turn off permission checking for this assembly. The value of the Name attribute, FullTrust, is how the file assigns this set of permissions to a code group, which I’ll get to soon.

The Nothing permission set, shown below, is of no real interest here. If an assembly has this trust level, it can do absolutely nothing, not even execute, so you’re not likely to use it in most applications.

&lt;PermissionSet
   class="NamedPermissionSet"
   version="1"
   Name="Nothing"
   Description="Denies all resources, including
   the right to execute"
/&gt;

The ASP.Net permission set is what is interesting for partial trust applications. Through the <trust> element, it is the permission set assigned to the assembly, in this case at the Medium trust level. Here the policy file defines the specific permissions that will be assigned to an assembly at this trust level. This section begins with a PermissionSet element to define the name of the set:

&lt;PermissionSet
   class="NamedPermissionSet"
   version="1"
   Name="ASP.Net"&gt;

The children of PermissionSet define the permissions that make up the set, contained within IPermission elements. Here is the IPermission element for the SqlClientPermission:

&lt;IPermission
   class="SqlClientPermission"
   version="1"
   Unrestricted="true"
/&gt;

This is the unrestricted form of a permission (Unrestricted=”true”), meaning that code with a Medium trust level will have full access to SQL Server databases, without any limitations or restriction. That makes sense for database access, because once you allow code to connect to a database, it is up to the database to enforce any security restrictions.

Now let me illustrate a couple of restricted permissions:

&lt;IPermission
   class="EnvironmentPermission"
   version="1"
   Read="TEMP;TMP;USERNAME;OS;COMPUTERNAME"
/&gt;
&lt;IPermission
   class="FileIOPermission"
   version="1"
   Read="$AppDir$"
   Write="$AppDir$"
   Append="$AppDir$"
   PathDiscovery="$AppDir$"
/&gt;

You can tell that these are restricted forms of the permissions because they don’t have the Unrestricted attribute set to true and they have one or more additional attributes that define the restriction. The EnvironmentPermission here allows read-only access to the TEMP, TMP, USERNAME, OS, and COMPUTERNAME environment variables only. It can’t write to those variables, nor can it read or write any others that may exist on this machine. Note in particular that the Medium trust level grants no access to the PATH variable used by the sample application. I’ll discuss that in more detail a little later in this article.

The FileIOPermission in Medium trust is also a restricted form of the permission, with four attributes defining the restriction. In this case the permission uses variables-with names starting and ending with a dollar sign-as placeholders for runtime values. $AppDir$ is the applications root directory. You can also use $AppDirUrl$ for the URI format of the root directory name (using the file:// protocol), $CodeGen$ for the temporary ASP.NET assembly directory, and $OriginHost$ for URIs that the application can connect to.

After the NamePermissionSet section in the medium trust policy file, you’ll find the <CodeGroup> section. The policy file uses these elements to match assemblies with permission sets by assigning the assembly to one or more code groups. There is rich support for structuring a hierarchy of code groups, which gives you fine control over exactly which permissions sets an assembly gets. I’ll gloss over most of the details here and let you explore them in the documentation. (See the sidebar, Help Writing Secure ASP.NET Applications, for a reference that goes into good detail about this hierarchy.)

Here is the code group element that assigns your Web application’s assemblies to the ASP.Net permission set:

&lt;CodeGroup
   class="UnionCodeGroup"
   version="1"
   PermissionSetName="ASP.Net"&gt;
   &lt;IMembershipCondition
      class="UrlMembershipCondition"
      version="1"
      Url="$AppDirUrl$/*"
   /&gt;
&lt;/CodeGroup&gt;

The CodeGroup element is of type UnionCodeGroup, which means that its permissions are added to those in the hierarchy. The PermissionSetName attribute links it to the ASP.Net permission set defined earlier in the file, and the IMembershipCondition element defines which assemblies belong to this code group. Here the membership condition is based on the location of the assembly, specifically all those in the application directory. This is how the assemblies you create as part of an ASP.NET application get the ASP.Net permission set that is part of the trust level defined by this config file.

Now that you understand how ASP.NET implements trust levels, let’s look at how to change the trust level for an application.

Change the Default Trust Level

Recall that the root Web.config file has a setting that looks like this in a pristine installation of .NET:

&lt;trust level="Full" originUrl="" /&gt;

There is nothing stopping you from changing this setting in the root Web.config file and, in fact, that is what many Web hosting companies do to provide a layer of security between the many Web applications that might be running on a single server. By the way, the originUrl attribute is used to specify the URL of origin for the application for the WebPermission class, such as to allow connectivity back to the host of origin. You don’t need to set this for the sample application.

You can override the trust level setting in any application by adding the trust element to your application’s Web.config file. To change the trust level for this article’s sample application, add this line to its Web.config (in the sample application you can instead uncomment the line if you wish):

&lt;trust level="Medium" /&gt;

When you change from running with Full trust, the CLR and ASP.NET have to do a little more work when the assembly loads. They run through a basic five-step operation:

Make the change in the trust level and re-run the application. Click on the Get Contents or Get Path buttons and, given the absence of exception handling in the code, you’ll be greeted by the unhandled exception shown in Figure 5. The crucial part of the message is “Request for the permission of type 'FileIOPermission’ failed.” Attempting to read the PATH environment variable returns no better results as you can see in Figure 6. Here the permission that is unavailable is EnvironmentPermission. In a real application, of course, you’d catch these exceptions either at the method level or in a global exception handler.

Figure 5: When the sample application runs with Medium trust, you can no longer read eula.txt in the System32 directory.
Figure 5: When the sample application runs with Medium trust, you can no longer read eula.txt in the System32 directory.
Figure 6: In Medium trust, you also cannot read the PATH environment variable.
Figure 6: In Medium trust, you also cannot read the PATH environment variable.

So what’s going on? Aren’t the FileIOPermission and EnvironmentPermission part of the Medium trust level? They are. So what gives? The problem is that each of those permissions is granted in a restricted form, and the restrictions don’t include reading eula.txt (or any file in System32) nor reading the PATH environment variable. This means that the Medium trust level won’t work for this application.

What are the options to make the application run? You can choose from several:

  • If the protected operations-here to read eula.txt and the PATH variable-are not critical application requirements, get rid of the features. If they aren’t critical, it is better to get rid of them rather than fiddle with security and maybe open nasty vulnerabilities!
  •     Use another standard trust level. Besides Full trust, the only other more permissive option than Medium is High trust. But this is likely to have way more permissions than your application requires, which opens up potential security vulnerabilities. That pretty much eliminates this as a viable option.
    
  •     Create a custom trust level with exactly the permissions your application needs. This is often the best choice since none of the standard trust levels are likely to exactly fit your needs.
    
  •     Sandbox dangerous code into a separate, well-protected assembly. This is often a good choice when the entire application doesn’t need elevated privileges, just a small section of the code. This way the potential security vulnerabilities are strictly limited.
    

The option you select depends on the nature of your application and its requirements. For the example in this article, I’m going to alter the scenario of the sample application a bit. Let’s say that the ability to read eula.txt is something that has to happen throughout the application-Microsoft would be so proud!-and that slightly different code is needed each time. This means that you couldn’t easily partition the file access code into a separate assembly. So the entire application needs the ability to read the eula.txt file-and only read it. For this I’ll create a custom trust level to add the necessary permission.

Let’s say that reading and updating the PATH variable is needed in just one place in the entire application, or that you want to use the same code over and over to do the same thing. This is a perfect use for sandboxing the Environment code-putting it in a separate assembly that receives the elevated privileges while leaving the main application’s permissions alone.

Create a Custom Trust Level

You have to follow three basic steps to create a custom trust level and apply it to the sample application:

In the first step you create the policy level file. It is usually easiest to start with one of the standard policy files, and since the Medium trust level is close to what you need, you can copy that file to your application directory and rename it. So copy web_mediumtrust.config from the .NET Framework CONFIG directory to the application directory and name it web_customtrust.config.

Next you need to change the policy file to provide the FileIOPermission needed. You don’t need to change the SecurityClasses section because FileIOPermission is already listed. And you don’t need to change the CodeGroup section because the application will still meet the membership condition for the code group associated with the ASP.Net permission set. So you only need to change the permission set. Using the FileIOPermission (which is already part of the set) you can modify that element by adding the eula.txt file to the Read attribute of the permission, delimiting it from other entries with a semicolon:

&lt;IPermission
   class="FileIOPermission"
   version="1"
   Read="$AppDir$;C:\WINDOWS\system32\eula.txt"
   Write="$AppDir$"
   Append="$AppDir$"
   PathDiscovery="$AppDir$"
/&gt;

If FileIOPermission were not already in the policy file, we’d have to add it. How do you know what XML to use? The SecurityClass and IPermission elements in the policy file are derived from the serialized form of the permission object. So you can write code like this to generate the elements and attributes you need in a console application:

EnvironmentPermission envPerm = 
   new EnvironmentPermission(
      EnvironmentPermissionAccess.Read, 
      @"C:\WINDOWS\system32\eula.txt");

Console.WriteLine(envPerm.ToXml().ToString());

This code generates the following output, from which you can extract the information you need. (I’ve formatted it a bit to fit here.)

&lt;IPermission class=" EnvironmentPermission, 
      mscorlib,
      Version=2.0.0.0, Culture=neutral, 
      PublicKeyToken=b77a5c561934e089"
   version="1"
   Read="C:\WINDOWS\system32\eula.txt"/&gt;

In the final step you’ll change the application Web.config file to use the custom trust level. This requires both a securityPolicy element to define the trust level and associate the policy file with the custom level, and a trust element to actually set the trust level:

&lt;securityPolicy&gt;
   &lt;trustLevel name="CustomTrust" 
      policyFile="web_customtrust.config"/&gt;
&lt;/securityPolicy&gt;
&lt;trust level="CustomTrust" originUrl=""/&gt;

That’s all that is necessary. Run the application again and see that it can read the eula.txt file without problem. Try entering ....\boot.ini however, and it raises an exception saying that the FileIOPermission needed is not available. That’s because the CLR will only allow the operations defined in the IPermission element above, none of which involve boot.ini. Now, the application can only read eula.txt; any other file will throw the exception. The sample application is already more secure by using a modification of the Medium trust level rather than Full trust.

Sandbox Dangerous Code

Sandboxing code is a bit more involved, but the limitation it provides against security vulnerabilities makes it very worthwhile. The idea is that you have some code that requires elevated privileges-more permissions-that would be dangerous to give to the entire application. So you put the dangerous code in its own assembly, give just that assembly the higher privileges, and call into that assembly from the rest of your Web application. In the case of the sample application, you’ll sandbox the code that reads and updates the PATH environment variable.

Sandboxing code in an ASP.NET application requires several steps:

In the first step you’ll create the assembly that will receive the elevated privileges. Make this a regular class library project called PathAccess and implement the PathAccess.Path class. The class will have two methods: GetPath, which returns a string, and SetPath, which takes a string parameter and returns nothing. Listing 2 contains the code for the assembly at this point. Note that I made the methods static so that the calling code doesn’t have to instantiate an instance of the object to use the methods.

In step two you want to change the code in the Web page to use the PathAccess.Path class instead of the Environment object. After adding a reference to the PathAccess project in Visual Studio, you can change the code for the Get Path button to this:

string path = PathAccess.Path.GetPath();
ResultsLabel.Text = path;

Similarly, the code behind the Add Path button required changing just the two lines highlighted in Listing 1 to these:

string path = PathAccess.Path.GetPath();

PathAccess.Path.SetPath(path);

The changes to the code behind the Remove Path button are identical to those for the Add Path button.

At this point in development of the partial trust application, it is easiest to test under Full trust to make it easier to make sure that the code is running correctly before changing the security settings. This requires commenting out the <trust> element in the application Web.config file and running the application to make sure it works correctly and there are no bugs in the code.

Now that the code is correct, it’s time to modify the assembly so that the partially trusted Web application can call it. This requires two things: decorate the assembly to allow partially trusted callers, and short circuit the stack walk.

Decorating the assembly for partial trust callers is simple. Add the following line of code somewhere in the assembly, outside of any class or namespace definitions. By convention it should go in an Assembly Information File (AssemblyInfo.cs or the Visual Basic equivalent), which you can add as a new item in Visual Studio. But a whole new code file for a single statement seems like overkill, so I added it to the PathAccess.cs file.

[assembly: AllowPartiallyTrustedCallers]
[assembly: AllowPartiallyTrustedCallers]

Next you have to short-circuit the stack walk. Normally when code accesses a protected resource, code access security in the CLR walks the current call stack to make sure that all code in the stack has the required permission. This prevents certain types of luring attacks where bad code causes good code to execute. But you know that the caller-the Web application-doesn’t have the proper form of EnvironmentPermission that can read PATH. So you have to tell the CLR to not walk the stack. You can choose from a variety of stack modifier methods, but here you’ll use an Assert, which tells the CLR to not bother with the stack walk for this one, specific permission. This requires that the PathAccess assembly both have the asserted permission-an assembly can’t assert a permission it doesn’t have-and permission to assert.

Any time you assert a permission, you should revert the assert, that is, undo it once the permission is no longer required. The best way to make sure any code executes is to wrap the protected code in a try block and put a call to Revert or RevertAll in a finally block. You can use something similar to the modified GetPath method in the PathAccess.Path class:

public static string GetPath()
{
   new EnvironmentPermission(
      EnvironmentPermissionAccess.Read, 
      "PATH").Assert();

   try
   {
      return Environment
         .GetEnvironmentVariable("PATH");
   }
   finally
   {
      EnvironmentPermission.RevertAll();
   }
}

Notice that you have to have an instance object of the permission class to call Assert, but you don’t have to create an object variable for the permission in order to call RevertAll. Revert and RevertAll are static methods on the permission class.

Here’s where things get a bit funky. You might expect that the modification to the SetPath code would be similar to GetPath and that you’d add this line of code near the start of the method:

new EnvironmentPermission(
   EnvironmentPermissionAccess.Write, 
   "PATH").Assert();

After all, the EnvironmentPermissionAccess does have a Write member. Unfortunately this will not work and you’ll get an exception at runtime that the request for EnvironmentPermission failed. If you dig into the documentation for this permission, you’ll find that to write to a variable you need to have the full, unrestricted form of the EnvironmentPermission. This seems to be a sloppy decision by Microsoft, because it means that the PathAccess assembly will have far greater privileges than it really needs. But oh well: the framework designers have spoken. So use this statement instead:

new EnvironmentPermission(
   PermissionState.Unrestricted).Assert();

The moral here is that not all permissions act as you’d expect. See the Misbehaving Permissions sidebar for a particularly noxious example.

The rest of the code is wrapped in a try block and RevertAll is called in the finally block, just like the GetPath method. That finishes up the work on the PathAccess assembly. You can see the complete code for the class in Listing 3.

Finally it’s time to elevate privileges for the PathAccess assembly. You can do this in one of two ways: add the assembly to the Global Assembly Cache (GAC) or modify the policy file to elevate privileges. Because this assembly is specific only to this one Web application, you won’t put it in the GAC. So how do you elevate privileges? You could choose from various ways, but here I will create a custom permission set and code group in the custom policy file.

Below is the custom permission set, named PathAccessSet. It requires two permissions. First it needs the unrestricted EnvironmentPermission, which allows both reading and writing to an environment variable. Second it requires the SecurityPermission, which has various flags that let the assembly assert permissions, execute, and a couple of control flags that let the assembly use certain threading operations and manipulate principals. You should place this permission set at the same level in the XML hierarchy as the FullTrust, Nothing, and ASP.Net permission sets.

&lt;PermissionSet
   class="NamedPermissionSet"
   version="1"
   Name="PathAccessSet"&gt;
   &lt;IPermission
      class="EnvironmentPermission"
      version="1"
      Unrestricted="true"/&gt;
   &lt;IPermission
      class="SecurityPermission"
      version="1"
      Flags="Assertion, Execution, ControlThread,
         ControlPrincipal"/&gt;
&lt;/PermissionSet&gt;

You must make another change to the policy file: add a new code group to match the assembly to the PathAccessSet permission set. As is typical for security code, there are several membership conditions that will work, but here I use a strong name on the PathAccess assembly. The name of the code group is PathAccessGroup and it associates the PathAccessSet permission set to the assembly. It also includes a PublicKeyBlob with the public key of the strongly named assembly, which I’ve abbreviated to save space.

&lt;CodeGroup
   class="UnionCodeGroup"
   version="1"
   PermissionSetName="PathAccessSet"
   Name="PathAccessGroup"&gt;
   &lt;IMembershipCondition
      class="StrongNameMembershipCondition"
      version="1"
      PublicKeyBlob="00240000...D7C2B1"
   /&gt;
&lt;/CodeGroup&gt;

You can extract the public key from the PathAccess assembly with the secutil.exe tool in the .NET Framework, using this command line below in the assembly’s bin directory. Copy and paste the resulting value into the PublicKeyBlob value, excluding the leading “0x”.

secutil -hex -s pathaccess.dll

The location of this code group element in the policy file is important because that will determine the actual permissions that the PathAccess assembly will receive. For the sample application, I put it as the first child of the parent code group since it has no children and stands alone.

You’re done! Remember to change the application trust level back to the custom level if you set it to Full when testing the PathAccess assembly. Run the application and make sure that it can read the eula.txt file, and both read and update the PATH variable.

What Else Is There?

Lots! I’ve just scratched the surface of how flexible partial trust ASP.NET applications can be. The good news is that for most applications, what I’ve covered in this article will take care of most of your security needs. You’ll need to add some exception handling, of course, to degrade gracefully (read: don’t crash) when your code doesn’t have the permissions it needs. But by thinking through everything during design and development, you can usually figure out where unexpected security problems can arise. Be particularly careful when user input can affect how the application executes.

You should also remove unnecessary permissions from your custom policy file. For example, the sample application’s policy file has permissions for using isolated storage, SMTP, and a SQL Server database. Since this application uses none of those resources, you should remove those permissions in order to lock down the application even further.

Sandboxing code and using the Assert method can be dangerous if you’re not careful. Because Assert circumvents the CLR stack walk that protects against various attacks, you have to make sure that malicious code can’t call the assembly and abuse it. One of the best ways to protect code like this is to digitally sign callers and require the appropriate signature before running any of the sandboxed code.

Building partial trust ASP.NET applications is one of the single best security features you can build into a Web application. It is no more complicated than other technologies you use in most applications, such as ADO.NET and Web services. Take a little time to learn partial trust and you’ll easily eliminate many security vulnerabilities from your applications.

Listing 1: Reading and updating PATH environment variable

protected void AddPathButton_Click(object sender, EventArgs e)
{
   string path = Environment.GetEnvironmentVariable("PATH").Trim();

   // add ; to end if not there
   if (!path.EndsWith(";")) path += ";";

   string newPath = AddPathTextBox.Text.Trim();

   if (path.Contains(newPath))
   {
      ResultsLabel.Text = "PATH variable already contains " + 
         newPath + ". Existing PATH value is:"
         + "&lt;br&gt;&lt;br&gt;" + path;
      return;
   }
   else
   {
      path += newPath;
   }

   Environment.SetEnvironmentVariable("PATH", path);

   ResultsLabel.Text = newPath + " added to the PATH.";
}

protected void RemovePathButton_Click(object sender, EventArgs e)
{
   string path = Environment.GetEnvironmentVariable("PATH").Trim();

   // add ; to end if not there
   if (!path.EndsWith(";")) path += ";";

   string removePath = AddPathTextBox.Text.Trim();
   if (!removePath.EndsWith(";")) removePath += ";";

   // Blind removal. If it's not there, nothing done.
   path = path.Replace(removePath, "");

   Environment.SetEnvironmentVariable("PATH", path);

   ResultsLabel.Text = removePath + " removed from PATH.";
}

Listing 2: Basic class to sandbox dangerous environment code

namespace PathAccess
{
   public class Path
   {
      public static string GetPath()
      {
         return Environment.GetEnvironmentVariable("PATH");
      }

      public static void SetPath(string path)
      {
          Environment.SetEnvironmentVariable("PATH", path);
      }
   }
}

Listing 3: Final security-enabled code for PathAccess assembly

namespace PathAccess
{
   public class Path
   {
      public static string GetPath()
      {
         new EnvironmentPermission(
            EnvironmentPermissionAccess.Read, "PATH").Assert();

         try
         {
            return Environment.GetEnvironmentVariable("PATH");
         }
         finally
         {
            EnvironmentPermission.RevertAll();
         }
      }

      public static void SetPath(string path)
      {
         new EnvironmentPermission(
            PermissionState.Unrestricted).Assert();

         try
         {
            Environment.SetEnvironmentVariable("PATH", path);
         }
         finally
         {
            EnvironmentPermission.RevertAll();
         }
      }
   }
}