.NET Security Workshop
Home About Workshops Articles Writing Talks Books Contact

5. Code Access Security

If I were to choose one reason for people to use .NET I would say Code Access Security. I mentioned on an earlier page that Win32 security works by giving permissions to users through an access control list. If your account is given access through an ACL (and not simultaneously denied that permission, which is possible) then any code you run will have that permission. Consider this situation. You are an administrator on the machine and so you can format the hard disks you have installed. You start Internet Explorer and this process gets your access token. You browse to a web site which has an ActiveX control on it. IE downloads the control which shows you a nice tree view image of the site, however at the same time it iterates through all the hard disks on your machine and calls format on each. The problem is that an ActiveX control is a DLL, ActiveX (or more accurately, COM) provides a mechanism to load the DLL dynamically and load its classes. Code in a DLL will get the access token that is used by the current thread, the thread that loaded the DLL. This means that the ActiveX control will get your access token through IE and it is this token that is passed to the Win32 API that access secured objects.

ActiveX controls are a good example of a situation when Microsoft marketing has got out of control and influence a decision that has no technological merit. Even the name is meaningless - ActiveX controls are COM (Component Object Model) controls and should have been named as such. At the time when ActiveX controls were released Microsoft had a serious competitor in Java. Java had Applets which were sandboxed, embedded code that were usually run as part of a web page. Microsoft Marketing was desperate for a Microsoft equivalent and OLE Controls (the replacement for Visual Basic VBX controls) were available. Someone must have made a snap decision to rename OLE Controls to ActiveX Controls and then, without considering the danger, they decided to allow IE to download such a control from any site on the internet and execute it under the current user's access token.

Microsoft fought a lengthy battle to convince the public that ActiveX control could be made 'safe' and released AuthentiCode, a code signing mechanism. However, the big weakness of code signing is that it relies on users checking the signed code's credentials, which few users do. Eventually Microsoft gave in with XPSP2 when it decided to block ActiveX controls as part of its security push.

Code access security solves this issue because it gives permissions based on evidence. There are various types of evidence but the evidence most often used is the source of the code. This means that code downloaded from a site on the internet will have different permissions than code downloaded from a site on the intranet or code that is installed on the local machine. The permissions specify the type of actions that the code can perform. Note that these permissions are applied to evidence obtained from the code and not from the user.

5.1 Jargon

First, some jargon. When an assembly is loaded the runtime gathers evidence about the assembly. Evidence is just information that describes some aspect of the assembly: where it comes from, who published it, etc. This evidence is then passed to policy objects. There are several policies and each determines the permissions that the assembly will have for that policy. Policies are just collections of expressions: if the assembly has this evidence then it should have these permissions. An action that code in an assembly can perform is called a permission. Each policy will provide a collection of permissions. The actual set of permissions that an assembly will get will be the intersection (not the union) of the permissions from each of the policies. To generate the permissions for an assembly a policy object uses code groups. A code group maps evidence to a permission set. A permission set contains zero or more permissions.

When your assembly calls another assembly the called code may demand a permission. A demand is a request to find out whether the calling assembly has the specified permission, in most cases this also involves a check that every assembly further up in the call stack also have the same permission. To do this, the runtime will do a stack walk. If an assembly in the stack does not have the permission then a security exception is thrown.

There are four policies: Enterprise, Machine, User and Application Domain. The first three of these are administered through a configuration file, whereas the last policy is administered through code. The policies that can be configured are done so by editing a configuration file and to do this the runtime provides two tools: a command line tool called caspol and an MMC snap-in called the .NET Configuration Tool (mscorcfg.msc).

5.2 Evidence

Let's investigate evidence and how .NET uses it. Create a strong named library (lib.cs) with the following code:

using System;
using System.Reflection;
using System.Text;

[assembly:AssemblyKeyFile("key.snk")]

public class LibraryClass
{
   public string GetInfo()
   {
      Assembly assem = Assembly.GetExecutingAssembly();
      StringBuilder sb = new StringBuilder();
      foreach (object part in assem.Evidence)
      {
         sb.Append(part.ToString());
      }
      return sb.ToString();
   }
}

This will obtain the aggregated evidence object from the current assembly and iterate over all of the individual evidence items. Create a process to use this (app.cs):

using System;
using System.Reflection;

class App
{
   static void Main()
   {
      LibraryClass lc = new LibraryClass();
      Console.WriteLine(lc.GetInfo());
   }
}

Compile all of this code and run the process, you'll find lots of output will be printed on the console, but closer inspection shows that most of this data is the hash for the assembly. Change the GetInfo method to ignore the hash evidence:

foreach(object part in assem.Evidence)
{
   if (part is System.Security.Policy.Hash)
      sb.Append("<System.Security.Policy.Hash version=\"1\"/>\r\n");
   else
      sb.Append(part.ToString());
}

The evidence for the library is now given as:

<System.Security.Policy.Zone version="1">
   <Zone>MyComputer</Zone>
</System.Security.Policy.Zone>
<System.Security.Policy.Url version="1">
   <Url>file://C:/TestFolder/lib.DLL</Url>
</System.Security.Policy.Url>
<StrongName version="1"
   Key="00240000048000009400000006020000002400005253413100040000010
        00100754DFDD8549F6966F4497DEC2BDE97D07D6296608882A2D0010191
        386375DE71919926E2F3875B58A74D13E4094D1E387C1E1ECE5E0DE5BF6
        F8EA73E243DF5C30CAB1F80AA13EB9890F1E84693F0B1D9518F74CC9988
        3A3BDC0957C025CD2FB8676BD23001B4038F185B93A5FD2A79103B7EB87
        266B7DBB5FB51A071539ED5CF"
   Name="lib"
   Version="0.0.0.0"/>
<System.Security.Policy.Hash version="1"/>

This illustrates four of the seven types of host evidence: Zone, Url, StrongName and Hash. The other three are Site, ApplicationDirectory and Publisher.

.NET Version 3.0
Version 3.0/2.0 of the framework adds the GacInstalled host evidence. This evidence is given to assemblies that have been installed in the GAC.

Zone refers to the IE security zones, Url is the codebase for the library. Site is the host name of a ftp: or http: protocol URL. ApplicationDirectory is provided by the application and indicates the base directory for the application. StrongName evidence is just that: the strong name of the assembly. However, although the public key portion of the strong name will be unique for each publisher it does not conclusively identify the publisher, this is the reason for the Publisher evidence which contains an X.509 certificate. (Note also that in v1.0 and v1.1 of the runtime there is a bug that allows a cracker to disable strong name validation, so you should not put much trust in the StrongName evidence.) The Hash is a cryptographic hash over the assembly and is better way to identify an assembly than using the StrongName evidence because, as explained earlier, strong names do not guarantee uniqueness.

5.3 Creating a Permission Set

To show that evidence is changed according to the source of the assembly let's deploy the assembly to another site. In fact, if you deploy it to IIS on the local machine that will be sufficient for the runtime to treat the library as being obtained from a site on the intranet. Create a new folder under the IIS wwwroot folder called bin and copy the library there:

C:\TestFolder>md \Inetpub\wwwroot\bin
C:\TestFolder>copy lib.dll \Inetpub\wwwroot\bin

Next create a configuration file (app.exe.config) to indicate that the library should be loaded from the web site (you can either type this by hand, or you can use the .NET configuration tool):

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="lib"
          publicKeyToken="c7611f8614380ed3"/>
        <codeBase version="0.0.0.0"
          href="http://localhost/bin/lib.dll"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

The publicKeyToken is created from your public key, to view this, type the following at the command line and copy the public key token that is printed on the console:

sn -T lib.dll

Now run the process and you'll find that an exception is thrown (this has been edited to show the relevant items):

Unhandled Exception: System.Security.SecurityException: Request for the permission of type System.Security.Permissions.SecurityPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 failed.
   at [snip...]

The state of the failed permission was:

<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="ControlEvidence"/>

The important point here is that because the assembly is deemed to have come from the intranet it does not have enough permission to view its own evidence! To verify this, start the .NET Configuration Tool from the Administrative Tools folder (mscorcfg.msc):

.NET Version 3.0
The screenshot, above, is for version 1.1 of the configuration tool, version 3.0/2.0 of the tool has an extra level, the root is called .NET Framework 2.0 Configuration with My Computer as its only child. To show the Runtime Security Policy you need to open the My Computer node.

Open the Runtime Security Policy, then open Machine to get the policy defined for this machine. Next open Permission Sets to view the default permission sets and select LocalIntranet. Click on the View Permissions link to get a view control with the permissions for the permission set. Double click on the Security entry to list details of that permission. This shows that Allow Evidence Control is set to No. Note that the dialog indicates that the permission is read-only, this is because the default permission sets cannot be changed.

So that we can allow our assembly to show its evidence we must make a new permission set and apply this to the appropriate policy. Before you change the policy you may want to save the current policy. You have two options. If the policy of your machine is the default you do not need to do anything, because after you have made changes you can simply tell the configuration tool to return back to the default policy. If an administrator has already changed the policy on your machine, you should save this policy so that after these tests you can revert back to this policy. Details of how to do this will be given in a later section.

If your account is a member of the Administrators group then you will be able to edit the Machine and the Enterprise policies. If your account is not an administrator then you'll only be able to edit the Users policy. However, XP provides the RunAs context menu for Start Menu shortcuts that allows you to run a process under a different account than the interactive user.

The easiest way to create a new permission set is to copy an existing permission set and make modifications to the copy. So close down the Permission Viewer dialog and right click on LocalIntranet and select Duplicate. Now right click on the new permission set (Copy of LocalIntranet) and select Rename; call this MyIntranet. Right click again and select Change Permissions. This will show a dialog with all the permissions in the permission set.

.NET Version 3.0
Version 3.0/2.0 of the configuration tool shows slightly different permissions for the intranet. This permission set does not contain an Event Log permission.

Now double click on Security in the Assigned Permissions list box to get the settings for the security permission. You'll see that two flags are selected, and you should check the one that we are interested in: Allow evidence control. Click on OK, and then Finish.

Now you have to ensure that this permission set is used when the evidence indicates that the assembly came from the intranet. To do this you need to create a code group for the intranet that will give the permission set you have created. When the runtime creates evidence it will traverse through the policy matching the evidence of the assembly to code groups in the policy; it will then create a union of all the permissions for all of the code groups obtained.

Open Code Groups, then All_Code (the membership of this group is true for all code and gives no permissions) and then select LocalIntranet_Zone. This is the code group for code that originates from the intranet. Right click on this and select Properties. Peruse the tabs, in particular note that the Membership Condition tab shows that this code group occurs when the Zone evidence is the Local Intranet. The Permission Set tab shows that the permission set is currently set to LocalIntranet, so scroll down the pull down list box, select MyIntranet and click on OK. You have now indicated that all assemblies that come from the intranet, regardless of where they come from, or what assembly it is, will get the ControlEvidence permission. Run the process from the command line, you'll see that the exception is not thrown and the library assembly now has the permission to view its evidence.

<System.Security.Policy.Zone version="1">
  <Zone>Intranet</Zone>
</System.Security.Policy.Zone>
<System.Security.Policy.Url version="1">
  <Url>http://localhost/bin/lib.dll</Url>
</System.Security.Policy.Url>
<System.Security.Policy.Site version="1">
  <Name>localhost</Name>
</System.Security.Policy.Site>
<StrongName version="1"
  Key="00240000048000009400000006020000002400005253413100040000010
       00100754DFDD8549F6966F4497DEC2BDE97D07D6296608882A2D0010191
       386375DE71919926E2F3875B58A74D13E4094D1E387C1E1ECE5E0DE5BF6
       F8EA73E243DF5C30CAB1F80AA13EB9890F1E84693F0B1D9518F74CC9988
       3A3BDC0957C025CD2FB8676BD23001B4038F185B93A5FD2A79103B7EB87
       266B7DBB5FB51A071539ED5CF"
  Name="lib"
  Version="0.0.0.0"/>
<System.Security.Policy.Hash version="1"/>

The evidence now has a Site item, and this, and the Url item, reflect where the library was downloaded from. In addition the Zone indicates that the code comes from the Intranet.

5.4 Creating a Code Group

You have indicated that all assemblies downloaded from the intranet should have the ControlEvidence permission. This permission is powerful and it should only be given to assemblies that you trust. The one rule that you must always follow with security is the principle of least privilege:

Code should be run under the least privileges that it needs to do its work, and no more.

To do this we must use the evidence in a more targeted way. For example, I trust myself (mostly <g>) and since I know that I am the only person using my key pair I can check for that evidence.

Again, the changes should be made to the Machine policy. The first thing to do is change the LocalIntranet_Zone back to use the default LocalIntranet permission set. Now create a new permission set called MyCodePermissions, this time you need to create a blank permission set so select Permission Sets and through the context menu, select New. Type in the name, and then click Next. Now double click on the Security item, check the Allow evidence control item and click OK. Click on Finish and you have a new permission set.

Now you need to create a new code group, so select LocalIntranet_Zone under the All_Code group and from the context menu select New. Type in the name My_Code and click on Next. In the next dialog you need to determine the evidence that will be checked. I want to make sure that assemblies downloaded from the bin virtual folder on my site will be given the permission to check its evidence. Select URL from the code group drop down list box and extra controls will appear pertinent to the evidence type. In the URL text box type http://localhost/bin/*. Now click on the Next button. On the next dialog select your MyCodePermissions permission set from the Use existing permission set dropdown list box. Click on Next and then Finish.

Now review what you have done: you have reverted to the previous intranet code group which will deny your code access to the assembly evidence. Next, you have created a code group under the LocalIntranet_Zone group that allowed any assembly downloaded from the bin folder of your web site to have permission to access its evidence. Now run the application and confirm that your assembly does have the required permissions.

Of course, you only know that you have those permissions because the code works, let's see if we can view those permissions. The configuration file indicates that the library should be downloaded from the intranet, and it will be stored in the download cache. The first thing to do is clear the download cache so that you can change the library:

gacutil /cdl

Next, change the library to add the following lines after the evidence has been added to the StringBuilder object:

sb.Append("Permissions:\r\n");
PermissionSet perms = SecurityManager.ResolvePolicy(assem.Evidence);
sb.Append(perms.ToString());
return sb.ToString();

You will also need to add a using statement for System.Security to the source code. Build the library and the process and copy the library to the appropriate IIS folder. Run the application. This will dump a load of XML to the console, but notice the format: this is in the same form as the permission set that was put into the non-verifiable assembly in the last section. It looks like this:

<PermissionSet class="System.Security.PermissionSet" version="1">
  <!-- other permissions omitted -->
  <IPermission
    class="System.Security.Permissions.SecurityPermission,
           mscorlib, Version=1.0.5000.0, Culture=neutral,
           PublicKeyToken=b77a5c561934e089"
    version="1"
    Flags="Assertion, Execution, ControlEvidence, BindingRedirects"/>
</PermissionSet>

All permissions implement IPermission which is why this is the name of the permission element, the type of the permission is given in the class attribute. The Flags attribute corresponds to the Flags property of the SecurityPermission class. This property is an enumeration of type SecurityPermissionFlag and we have been given four:

Assertion The code can assert that all the code's callers have this permission.
Execution The code has the permission to run.
ControlEvidence Permission to access and provide evidence.
BindingRedirects Permission to perform binding redirection through the configuration file.

5.5 Evaluating an Assembly

When you are developing an assembly it is useful to determine the permissions that it will be granted. The configuration tool will do this through the Evaluate Assembly Wizard. To do this, select the Runtime Security Policy and from the context menu select Evaluate Assembly.

The dialog allows you to specify the location of the assembly. Note that the form of the path you give affects the zone that will be used. If you give a local path then the assembly will come from My Computer and hence it will be fully trusted. If the path is a URI and has a computer name (or localhost) then the zone will be Intranet. If the path is a URI that uses a dotted format (an IP address, eg 127.0.0.1, or a dotted name, eg www.microsoft.com) then the zone will be Internet. In the figure I have simulated loading the assembly from the intranet by supplying a URI with a machine name.

You can use this tool to obtain a list of the permission sets and to see the actual permissions it will be granted. In addition you can get a list of the code groups that have been checked, this is important if you use child groups as explained later. You can also select the policy level, so you can check the permission sets obtained from each policy.

For example, select the options to evaluate just the Machine policy and view the code groups and you can verify that the code groups used for this assembly will include the My_Code code group. To see the permissions that will be granted you can use the Back button and then select the option to view the permissions.

Test this dialog to see the permissions that you get for the assembly located from different zones. Use the following table as a guide:

File Name Zone Example
Local Path MyComputer C:\TestFolder\lib.dll
UNC Intranet \\MYHOST\C$\TestFolder\lib.dll
URI with site name Intranet http://localhost/bin/lib.dll
URI with dotted name Internet http://127.0.0.1/bin/lib.dll

In this table MYHOST is the name of your machine.

You can do a similar thing with the /resolveperm and /resolvegroup switches of caspol. For example:

C:\TestFolder>caspol /m /resolvegroup http://localhost/bin/lib.dll
Microsoft (R) .NET Framework CasPol 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

Level = Machine

Code Groups:

1. All code: Nothing
1.2. Zone - Intranet: LocalIntranet
1.2.1. All code: Same site Web.
1.2.2. All code: Same directory FileIO - Read, PathDiscovery
1.2.3. Url - http://localhost/bin/*: MyCodePermissions

Success

This indicates the code groups and the permission sets that have been granted the assembly when only the machine policy (/m) is applied.

Note that although the UNC format and the URI with the machine name and with localhost are all treated as if the library comes from the intranet, only the latter (localhost) resolves to the My_Code code group because the code group has a URL with localhost. Similarly, if you change the URL in the code group to be http://MYHOST/bin/lib.dll (where MYHOST is the name of your machine) then you'll find that your permission set will only be used if the library is loaded with that exact URL. Thus, when you use URL evidence you must ensure that you use the same format of the URL as will be used in your application configurations.

Note that you will find that for Intranet evidence you will find that the code will also be members of the Intranet_Same_Site_Access and Intranet_Same_Directory_Access code groups and for Internet evidence you will find that the code will be a member of the Internet_Same_Site_Access code group. These are all custom code groups and the permissions are given as raw XML. This XML is given in a label control which means that if the XML is larger than can be displayed in the label no vertical scroll bar will be shown. The only way to scroll is to select the text and drag the cursor down.

5.6 Code Group Levels

As you can see, by default the permission sets from the code groups in a policy will be combined to give the permission set used by the policy. When all policies have been resolved, the intersection of the policy permission sets will be used to generate the permission set for the assembly. The properties for a code group allow you to determine how the permission set for the current code group is combined with other code groups in the policy. The configuration tool will display code groups in a hierarchy to indicate how this combination will occur.

At the bottom of the General page for a code group are two check boxes. The first check box is used to set the Level Exclusive property. If this is checked and an assembly matches the membership condition then no other code group will contribute to the assembly's permission set. Note however, that if another code group matches the evidence of the assembly and this group also has the Level Exclusive property then an exception will be thrown. The Level Exclusive property is useful if you want to set specific permissions for a particular assembly that you can identify with evidence, the exclusive property means that the assembly will not get any more permissions from other code groups. (Note that although other code groups in this policy do not contribute to the permission set, other policies are still evaluated and will also contribute to the final permission set.) In the example shown earlier this check box was deliberately left unchecked because the permission set granted to this code group only has the ControlEvidence permission, and it relies on getting addition permissions from the Local_Intranet_Zone group.

The other check box specifies the Level Final property of the code group. Usually, all the policies are applied to the assembly, the 'level' of the policy is considered to be in the order Enterprise, Machine, User, highest to lowest level. Only enterprise administers can change the Enterprise level, enterprise and machine administrators can change the Machine policy, and any user can change their own User level. A lower policy level cannot add more permissions than granted by the higher policy, but it may reduce the permissions. If you are a higher level policy administer you can prevent lower policies from being evaluated by setting the Level Final property of a code group. You would do this if you want to ensure that lower policies do not reduce the permissions you give to an assembly. If this box is checked it means that code groups below the current group will not be used.

At this point return the security policy back to its original value, as explained in the next section.

5.7 Administering Policies

All of the policy information is held as XML. Security configuration files are held in:

%windir%\Microsoft.NET\Framework\<framework version>\CONFIG

where <framework version> is the version you are interested in (for example, v1.1.4322 or v2.0.50727). This will contain enterprisesec.config for the Enterprise policy and security.config for the Machine policy. The User policy is held in a file called security.config and it is held in:

%userprofile%\Application Data\Microsoft\CLR Security Config\<framework version>

The MMC configuration tool and caspol will edit this these XML files. You can edit these files by hand, but you shouldn't. In spite of the fact that XML is text, it is not human-readable, and hence it is easy to make mistakes when editing XML by hand. Use the tools, that is what they are for.

If you want to try out a security policy but have to ability to return back to a working policy, you have two options. First, if you have not change the default policies it means that any changes that you make will be to the default policies and hence reverting means going back to the default. To do this with caspol you use the -reset switch with either -machine, -enterprise or -user for the Machine, Enterprise and User policies or -all to revert to the default for all policies. For the MMC configuration tool you can right click on the policy and select Reset to revert that paticular policy to the default, or right click on Runtime Security Policy and select Reset to reset all policies. Whenever you make a change with the caspol tool a backup is made of the policy file and so you can use the -recover switch (with an appropriate switch to indicate the configuration level) to revert back to the previous version.

The second method, and the most flexible option, is to use the MMC configuration tool to create and open a new configuration file. You do this by right clicking on Runtime Security Policy and select New to create a copy of the default settings.

The dialog allows you to indicate the policy that you want to create and specify the location where the policy file will be stored. Once you have closed this dialog, the new policy file will be used and all changes will be made to this new file. When you have finished with this policy you can revert back to the previous security policy through the Open option from right clicking on Runtime Security Policy:

Again, this allows you to select the policy level (in the top half). It also gives you the option of opening the policy file from the default location or open a specific policy file. To revert back to the system policy files you should select the first option. If you want to go back to your test policy at a later stage you can use the second option.

If you are a developer, then the policy that you develop with the configuration file is vital for the correct running of your components. In this case you should start with a default configuration and then copy this configuration to your deployment machines. To do this, right click on Runtime Security Policy and select Create Deployment Package. This will create an msi file for the policy that you specify.

I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com.

Errata

If you see an error on this page, please contact me and I will fix the problem.

Page Six

This page is (c) 2007 Richard Grimes, all rights reserved