The previous installment in this series described the principles of .NET Framework. This information will serve as a starting point for a discussion of code access security, which is based on functionality supplied by Common Language Runtime (CLR). Therefore, if you are not familiar with .NET Framework, we recommend reading through the previous installment.
Due to its reliance on CLR, code access security is available only for managed applications (i.e., applications written according to .NET specifications). While standard Windows applications run in the same security context as the user who launched them, the level of privileges granted to .NET applications is configurable. Computer resources can be protected not only from malicious attacks, but also from potential errors or vulnerabilities in their code. The code access security architecture is based conceptually on two main building blocks:
- Permission Set: Collections of individual permissions on various computer resources that determine what a code has access to (e.g., granting read-only access to a specific registry key)
- Code Group: Lists of criteria (called membership conditions) evaluated against the code to be executed and a permission set assigned to the code that matches these criteria
When a user starts a program (or a program calls a function stored in a dynamic link library file, referred to in .NET world as an assembly) CLR first checks its code for buffer overflows (their presence constitutes a vulnerability and a potential for an exploit). In addition, CLR goes through a verification stage during which it analyzes the digital signature the creator has assigned to the code to detect whether the application has been altered since its release.
If both checks produce satisfactory results, CLR checks other application properties, such as its origin (zone, site, or file system path) and code characteristics (hash, publisher, or strong name). This stage is often described as "evidence gathering."
Next, the code is evaluated against the code groups configured for the local computer. Permission sets associated with that group determine what types of actions the application is allowed to perform.
Permission sets and code groups are configurable via the .NET Framework Configuration tool, available from the Administrative Tools menu in Windows Server 2003 (and other Windows platforms once they have .NET framework installed). It is implemented as part of the Microsoft Management Console snap-in and contains the following nodes in the tree (left) window pane.
- Assembly cache offers the option of viewing the content of Global Assembly Cache (GAC) and adding new assemblies to it. GAC resides in the Windows\Assembly folder and stores shared assemblies, which are pieces of code that can be used by multiple .NET applications. (Assembly cache serves a function equivalent to Windows\System32 folder, where shared Win32 Dynamic Link Library files are stored. Assemblies in the cache are implemented as either DLL or EXE files, although their format differs from their legacy counterparts.)
- Configure assemblies allow the assignment of two types of policies to any assembly (including assemblies not residing on the local computer).
- Binding Policy specifies the version of the assembly that should be used by mapping the version a .NET application request to the one CLR provides. This ensures that only approved versions are used (e.g., the MyAssembly of version 220.127.116.11 is redirected to MyAssembly of version 18.104.22.168).
- Codebases Policy specifies the location of the assembly that should be used. To do this, the version requested by a .NET application is mapped to the location in the URI (uniform resource identifier) format (e.g., the request for MyAssembly of version 22.214.171.124 is redirected to the http://www.myassemblies.com Web site or file:///c:\MyAssemblies folder the local drive).
- Remoting Services provide the capability to modify properties of channels used for communication between local applications and remote services.
- Runtime Security Policies contain settings to control code access security, our primary focus. The node is divided into three subnodes, representing policy levels.
- Enterprise Level is intended for enterprise-level policies and is stored by default in the enterprisesec.config residing in the config subfolder of the Framework installation directory (typically WINDOWS\Microsoft.NET\Framework). Obviously, if you intend to apply this policy to your entire enterprise, you must ensure the file is consistent across all systems. This can be accomplished easily by creating the .MSI package with Deployment Package Wizard (part of .NET Framework Configuration tool, which is accessible from the Create Deployment Package task of the Runtime Security Policy node) and deploying it to all target systems using Active Directory Group Policy or another deployment method. Note that the Package Deployment Wizard can also be used to deploy machine- and user-level policies.
- Machine Level applies to the local computer. It is stored in the security.config file residing in the same config subfolder as the enterprisesec.config file.
- User Level applies to the currently logged-on user. It is stored in the security.config file residing in the Applications Data\Microsoft\CLR Security Config folder within the user's profile.
Security policy is evaluated based on code access policies on each level, starting with the Enterprise and ending with the User. The settings are evaluated independently and intersected such that the most restrictive ones of the three are applied. It is, however, possible to configure higher-level policies (on the individual Code Group level) to prevent lower-level policies from being applied (using options available from the General tab of a code group's Properties dialog box).
.NET Framework also includes an Application Domain policy level, which is configurable only via code (and it is also not available from .NET Framework Configuration tool).
Each policy level, in turn, contains three subnodes that determine the configuration settings:
- Code Groups each consist of the name, membership condition, and permission set assigned to it. A number of different types of membership conditions, cover both code origin (application directory, site, URL, or zone) and code properties (hash, publisher, or strong name). It is also possible to define custom membership conditions (importable from XML formatted files). The membership conditions are compared with the values collected during the evidence-gathering stage after a managed application is launched. If an application's properties match the criteria defined for a specific group, the applications is assigned the permission set associated with the code group.
Note that the .NET Framework Configuration tool contains the following pre-defined permission sets:
- FullTrust grants unrestricted access to all resources (including the ability to skip verification).
- SkipVerification skips the initial verification process (during which digital signatures are analyzed).
- Execution permits code execution.
- Nothing Denies all privileges, including execution.
- LocalIntranet contains default rights granted typically to local intranet applications.
- Internet contains default rights granted typically to Internet applications.
- Everything grants unrestricted access to all resources (but does not allow skipping of verification).
You can also define your own permission sets with extensive granularity, which includes (but is not limited to) browsing specific areas of directory services, preventing DNS access, allowing the auditing of event logs, granting read or write access to environment variables, controlling which files or folders are writable, determining which dialog boxes (e.g., Open or Save) are usable, and specifying which registry keys are readable.
- Policy Assemblies list assembly files used to implement various security policies. It is unnecessary to make changes in this node unless you are implementing custom permissions or custom membership conditions.
- Applications allow you to configure managed applications installed on the local system. You can, for example, change the garbage collection properties or set the search path for additional assemblies, view assembly dependencies, modify binding policy and codebase for shared assemblies used by the application, or alter its remoting services settings.
As you can see, new options introduced with .NET framework offer enormous flexibility in securing managed applications. As usual, there is a tradeoff, which in this case is increased administrative overhead and level of complexity.