.NET Security Part 4
- by Simon Cooper
Finally, in this series, I am going to cover some of the security issues that can trip you up when using sandboxed appdomains.
DISCLAIMER: I am not a security expert, and this is by no means an exhaustive list. If you actually are writing security-critical code, then get a proper security audit of your code by a professional. The examples below are just illustrations of the sort of things that can go wrong.
1. AppDomainSetup.ApplicationBase
The most obvious one is the issue covered in the MSDN documentation on creating a sandbox, in step 3 – the sandboxed appdomain has the same ApplicationBase as the controlling appdomain. So let’s explore what happens when they are the same, and an exception is thrown.
In the sandboxed assembly, Sandboxed.dll (IPlugin is an interface in a partially-trusted assembly, with a single MethodToDoThings on it):
public class UntrustedPlugin : MarshalByRefObject, IPlugin
{
// implements IPlugin.MethodToDoThings()
public void MethodToDoThings()
{
throw new EvilException();
}
}
[Serializable]
internal class EvilException : Exception
{
public override string ToString()
{
// show we have read access to C:\Windows
// read the first 5 directories
Console.WriteLine("Pwned! Mwuahahah!");
foreach (var d in
Directory.EnumerateDirectories(@"C:\Windows").Take(5))
{
Console.WriteLine(d.FullName);
}
return base.ToString();
}
}
And in the controlling assembly:
// what can possibly go wrong?
AppDomainSetup appDomainSetup = new AppDomainSetup {
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
}
// only grant permissions to execute
// and to read the application base, nothing else
PermissionSet restrictedPerms = new PermissionSet(PermissionState.None);
restrictedPerms.AddPermission(
new SecurityPermission(SecurityPermissionFlag.Execution));
restrictedPerms.AddPermission(
new FileIOPermission(FileIOPermissionAccess.Read,
appDomainSetup.ApplicationBase);
restrictedPerms.AddPermission(
new FileIOPermission(FileIOPermissionAccess.pathDiscovery,
appDomainSetup.ApplicationBase);
// create the sandbox
AppDomain sandbox = AppDomain.CreateDomain("Sandbox", null, appDomainSetup, restrictedPerms);
// execute UntrustedPlugin in the sandbox
// don't crash the application if the sandbox throws an exception
IPlugin o = (IPlugin)sandbox.CreateInstanceFromAndUnwrap("Sandboxed.dll", "UntrustedPlugin");
try
{
o.MethodToDoThings()
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
And the result?
Oops. We’ve allowed a class that should be sandboxed to execute code with fully-trusted permissions! How did this happen? Well, the key is the exact meaning of the ApplicationBase property:
The application base directory is where the assembly manager begins probing for assemblies.
When EvilException is thrown, it propagates from the sandboxed appdomain into the controlling assembly’s appdomain (as it’s marked as Serializable). When the exception is deserialized, the CLR finds and loads the sandboxed dll into the fully-trusted appdomain. Since the controlling appdomain’s ApplicationBase directory contains the sandboxed assembly, the CLR finds and loads the assembly into a full-trust appdomain, and the evil code is executed.
So the problem isn’t exactly that the sandboxed appdomain’s ApplicationBase is the same as the controlling appdomain’s, it’s that the sandboxed dll was in such a place that the controlling appdomain could find it as part of the standard assembly resolution mechanism. The sandbox then forced the assembly to load in the controlling appdomain by throwing a serializable exception that propagated outside the sandbox.
The easiest fix for this is to keep the sandbox ApplicationBase well away from the ApplicationBase of the controlling appdomain, and don’t allow the sandbox permissions to access the controlling appdomain’s ApplicationBase directory. If you do this, then the sandboxed assembly can’t be accidentally loaded into the fully-trusted appdomain, and the code can’t be executed. If the plugin does try to induce the controlling appdomain to load an assembly it shouldn’t, a SerializationException will be thrown when it tries to load the assembly to deserialize the exception, and no damage will be done.
2. Loading the sandboxed dll into the application appdomain
As an extension of the previous point, you shouldn’t directly reference types or methods in the sandboxed dll from your application code. That loads the assembly into the fully-trusted appdomain, and from there code in the assembly could be executed. Instead, pull out methods you want the sandboxed dll to have into an interface or class in a partially-trusted assembly you control, and execute methods via that instead (similar to the example above with the IPlugin interface).
If you need to have a look at the assembly before executing it in the sandbox, either examine the assembly using reflection from within the sandbox, or load the assembly into the Reflection-only context in the application’s appdomain. The code in assemblies in the reflection-only context can’t be executed, it can only be reflected upon, thus protecting your appdomain from malicious code.
3. Incorrectly asserting permissions
You should only assert permissions when you are absolutely sure they’re safe. For example, this method allows a caller read-access to any file they call this method with, including your documents, any network shares, the C:\Windows directory, etc:
[SecuritySafeCritical]
public static string GetFileText(string filePath)
{
new FileIOPermission(FileIOPermissionAccess.Read, filePath).Assert();
return File.ReadAllText(filePath);
}
Be careful when asserting permissions, and ensure you’re not providing a loophole sandboxed dlls can use to gain access to things they shouldn’t be able to.
Conclusion
Hopefully, that’s given you an idea of some of the ways it’s possible to get past the .NET security system. As I said before, this post is not exhaustive, and you certainly shouldn’t base any security-critical applications on the contents of this blog post. What this series should help with is understanding the possibilities of the security system, and what all the security attributes and classes mean and what they are used for, if you were to use the security system in the future.