Browse DevX
Sign up for e-mail newsletters from DevX


Never Write an Insecure ASP.NET Application Ever Again : Page 5

Learn to take advantage of the inner workings of ASP.NET's security model to help eliminate security vulnerabilities from your web applications.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

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 a few steps:

  1. Create an assembly that contains the dangerous code and nothing else.
  2. Add a reference to the assembly and rewrite the code in the main application to use the class rather than the Environment object directly.
  3. Modify the new assembly so that you can call it from partially trusted code.
  4. Elevate the privileges for the new assembly.
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. Here's 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:

namespace PathAccess { public class Path { public static string GetPath() { return Environment.GetEnvironmentVariable("PATH"); } public static void SetPath(string path) { Environment.SetEnvironmentVariable("PATH", path); } } }

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]

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. Here's the complete code for the class:

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(); } } } }

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.

<PermissionSet class="NamedPermissionSet" version="1" Name="PathAccessSet"> <IPermission class="EnvironmentPermission" version="1" Unrestricted="true"/> <IPermission class="SecurityPermission" version="1" Flags="Assertion, Execution, ControlThread, ControlPrincipal"/> </PermissionSet>

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.

<CodeGroup class="UnionCodeGroup" version="1" PermissionSetName="PathAccessSet" Name="PathAccessGroup"> <IMembershipCondition class="StrongNameMembershipCondition" version="1" PublicKeyBlob="00240000...D7C2B1" /> </CodeGroup>

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 CodeGroup 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.

Don Kiely, MVP, MSCD, is senior technology consultant specializing in developing secure desktop and Web applications that integrate databases, Microsoft Office, and related technologies, using tools including SQL Server, Visual Basic, ASP.NET and XML. When he isn't writing software, he's writing and about technology, speaking about it at conferences, and training others.
Thanks for your registration, follow us on our social networks to keep up-to-date