Enforce access control at the code level to protect your systems
Microsoft has embedded code-level access control logic in the Microsoft .NET Framework. Known as Code Access Security (CAS), this technology lets administrators and developers enforce access control at the code level. Whereas the classic Windows OS access control model enforces the security policy based on the identity of a user, CAS uses the identity of a piece of code. Limiting a piece of code's capabilities by identifying the code and enforcing access control is currently the best way to protect against malicious mobile code execution. Because access control settings are linked to pieces of code, the engine that executes the code can enforce the settings independently of the user or service requesting access to the code. This approach also separates the definition of access control settings at development time from code execution and the application of security settings at runtime.
This introduction to CAS features the technology's administrative aspects rather than its programmatic nuts and bolts. I explain the key CAS elements and how they work together to define what a piece of code is or isn't permitted to do with system resources. To get started, let's review some fundamental .NET Framework concepts.
.NET Framework Key Concepts
The .NET term for a logical unit of code is an assembly. A .NET Framework—based application can call one or more assemblies. Developers compile assemblies into platform-independent Intermediate Language (IL). At runtime, the engine that compiles and executes the assemblies—the .NET runtime or the Common Language Runtime (CLR)—uses a just-in-time (JIT) compilation process to convert IL into platform-dependent native code. The CLR also includes the subsystem that enforces CAS-based access control.
Code that takes full advantage of the CLR's features (e.g., CAS, JIT compilation, .NET class libraries) is called managed code. Older Win32 COM+ code that uses the legacy COM runtime environment is called unmanaged code. Although administrators can't use the .NET Framework's CAS mechanism to control unmanaged code, they can use the Windows .NET Server (Win.NET Server) 2003 SAFER feature to protect against the execution of legacy malicious mobile code. SAFER lets a system identify legacy COM+ code and, depending on its attributes, either allow or disallow the code to execute on a machine. You use Group Policy Object (GPO) settings to manage the SAFER tool. For more information about SAFER, read the eBook Windows Security: A Common-Sense Guide, Jan De Clercq and Douglas Toombs, at http://www.winnetmag.com/seminars/images/securitysummitebook.pdf.
Keep in mind that an application can't take advantage of CAS unless the developer built the application using the .NET Framework building blocks and features that I've mentioned. Also, the platform executing the application code must have the .NET Framework installed. Microsoft bundles the .NET Framework (including the CLR and CAS support) with Win.NET Server and also makes it available as an add-on component for XP, Windows 2000 (requires Service Pack 2—SP2), Windows NT 4.0 (requires SP6a), Windows Me, and Windows 98. You can download the .NET Framework add-on component from the Microsoft Developer Network (MSDN) Web site at http://msdn.microsoft.com.
Evidence-based Access Control
CAS is structured around the concept of evidence-based access control, which means that a piece of code must provide evidence (i.e., identity information) to the engine that will execute it before the engine lets the code perform certain actions that involve access to specific system resources. The three key CAS elements are policies, evidence, and permissions. Policies link evidence to permissions by defining exactly what a piece of code can do and which system resources it can use. The following sections detail these CAS elements.
Policies. Security policies are probably the most important element of CAS. Note that these security policies aren't related in any way to .NET and Win2K GPOs. The .NET Framework's CAS mechanism defines policies on four levels—enterprise, machine, user, and application domain. The levels are differentiated by 1) the persons or processes that can administer the policies, 2) the assemblies to which the policies apply, and 3) the order in which the evaluation process applies the policies. Table 1 lists these four policy levels and their characteristics. In this article, I focus on the enterprise, machine, and user policies because typically, the .NET application developer, not the systems or domain administrator, defines and configures application domain policies programmatically. The CLR's CAS subsystem evaluates the four policy levels hierarchically, with enterprise policies first, then machine policies, user policies, and finally, application domain policies, as Figure 1 shows. Note that one level might contain multiple policies. Policies are linked to a code group (a collection of assemblies that use identical evidence), with every policy level having a code group hierarchy. I'll come back to code groups later in this article.
During the policy-evaluation process, permissions granted at a lower policy level can never override the permissions denied at a higher policy level or increase the permission set of a higher policy level. Thus, users have little influence on the CAS security policies that are applied to managed code executing on their machines: Enterprise-level and machine-level security policies always take precedence.
A higher level of security and ease of administration rarely go hand in hand, and CAS security policy configuration is no exception. To configure the different policy levels, you can use the Microsoft Management Console (MMC) .NET Framework Configuration snap-in (mscorcfg.msc), which lets you configure many .NET Framework—related settings, or you can use the CAS Policy tool (caspol.exe), a command-line utility that lets you configure enterprise-, machine-, and user-level security policies. (An administrator can't configure the application domain-level policies using either of the tools mentioned; the application developer must configure these policies programmatically. However, administrators can override specific application domain-level permissions by explicitly denying them at a higher policy level). The.NET Framework Configuration snap-in is available on any .NET machine; the CAS policy tool comes with the .NET Framework software development kit (SDK).
If you use the .NET Framework Configuration snap-in, you'll set security policies from the Runtime Security Policy container, as Figure 2 shows. Each Runtime Security Policy container has three subcontainers: Code Groups, Permission Sets, and Policy Assemblies. The Policy Assemblies subcontainer can contain custom code that the CLR's CAS subsystem uses to evaluate security policy. I explain Code Groups and Permission Sets subcontainers in the sections that follow.
Administrators can distribute new CAS security policies to the enterprise by generating a Windows Installer (.msi) file for a given security policy level, or they can distribute the policies by using caspol.exe. If administrators want to use caspol.exe to change multiple policy settings, they can bundle different calls to the tool in a script or batch file.
A .NET Framework—enabled machine stores the CAS security policy configuration in XML documents and stores the enterprise policy configuration files (enterprisesec.config) and machine policy configuration files (security.config) in the .NET runtime installation directory. The user policy file (also named security.config) is kept in the user's profile directory.
Evidence and Code Groups. The CLR's CAS subsystem uses an assembly's evidence and security policy to determine the permissions the assembly receives. By default, CAS supports the evidence types that Table 2 lists. Because the CAS access control model is extensible, you can also add application-specific evidence types.
A code group is a collection of assemblies that use identical evidence. Thus, assemblies within the same code group receive identical permissions. If an assembly's evidence matches the evidence of multiple code groups within the same policy level, the assembly will be a member of multiple code groups. As Figure 1 shows, every policy level has a code-group hierarchy, and a parent code group can have different child code groups. Child code groups automatically inherit the parent code group's evidence and permissions. If, for example, you build a Web application for your intranet users and the application has specific CAS security requirements, you probably want to create a new child code group underneath the LocalIntranet_Zone code group, as Figure 3 shows. This new code group will have the LocalIntranet_Zone's permissions, plus a set of additional permissions that your Web application needs.
To view a code group's description, open the .NET Framework Configuration snap-in, right-click the code group you want to view, and open the Properties dialog box. Click the General tab to see a description of the code group, as Figure 3 shows. You'll also see two check boxes that let you change the default policy-evaluation process that Figure 1 outlines.
- The first option, This policy level will only have the permissions from the permission set associated with this code group, makes membership in a particular code group an assembly's primary code group membership. If the assembly is a member of other code groups located at the same policy level, the CLR's CAS mechanism ignores these code groups. This option is known as the Exclusive code group attribute. At a given policy level, an assembly can be a member of only one code group that has the Exclusive attribute set. Figure 4 shows the effect of the Exclusive attribute.
- The second option, Policy levels below this level will not be evaluated, limits assembly code-group—membership evaluation to the security policy level of a given code group. If you select this option, known as the LevelFinal code group attribute, the CLR's CAS mechanism doesn't evaluate membership of code groups lower in the security policy hierarchy. Figure 5 shows the effect of the LevelFinal attribute.
Permissions. Permissions determine how an assembly can use protected system resources or what protected operations the assembly can perform (e.g., writing to a particular registry key or accessing certain performance counters on the machine that executes the code). As you can see in Table 3, the CLR's CAS mechanism lets you define very granular assembly permissions. Table 3 lists the protected system resources and operations for which you can define permissions. As with evidence, you can extend the CAS permissions to cover application-specific permissions.
One special permission is the Isolated Storage File permission, which refers to a special file storage system that you can define on top of the regular file system. The storage file's main characteristic is isolation: It lets an assembly store data without affecting other assembly or system data.
To facilitate permissions management, permissions are grouped into permission sets, which you can link to code groups. As with code groups, a permission set container exists for every security policy level. Table 4 lists the preconfigured permission sets that come with the .NET Framework.
Figure 6 shows the LocalIntranet permission set, and Figure 7 shows how the LocalIntranet permission set is linked to the LocalIntranet_Zone code group in the code group's properties. If an assembly is a member of different code groups located at the same policy level, the effective permissions of the assembly will be the accumulated permissions of these code groups (unless the code group has the Exclusive attribute set, as explained earlier). Remember that when you set the LevelFinal attribute, the CLR's CAS evaluation process ignores all permissions linked to code groups located at lower policy levels.
Developers can add permission requests to their code to influence how their code reacts to the permissions that the CAS policy grants. If a developer doesn't specify a permission request type for a particular permission, the assembly will receive the permissions that result from the CAS policy evaluation process. The .NET Framework defines three types of permission requests: Minimal, Optional, and Refuse. Minimal specifies the minimum set of permissions an assembly must have to run. If the CAS policy can't grant all the permissions marked as Minimal, the assembly will fail to load. For example, if a developer has marked the permission to Read and Write system environment variables as Minimal and the CAS policy doesn't grant this permission, the assembly won't load. Optional specifies permissions the developer wants the assembly to have in addition to the minimal ones. Refuse specifies permissions that should never be granted to the assembly. In this case, the assembly will fail to load if the CAS policy grants a permission that the developer has marked with a Refuse attribute. The permission requests capability is one way that Microsoft honors the concept of least privilege in the .NET Framework; that is, code receives only the permissions it needs to perform its task—no more and no less.
Don't Miss the CAS Express
CAS is a fundamental component of the .NET Framework that will let application developers write more secure code. However, I hope I've shown that application developers aren't the only ones who need to understand this important technology: CAS will affect the tasks of any security-aware Windows architect, consultant, or administrator because configuring and fine-tuning CAS security policies is generally beyond an application developer's responsibilities. Configuring CAS security policies is complex, but it's a process that you'll want to become familiar with. If you want to secure your .NET environment, don't miss the CAS Express.