Understanding the Basics of Code Access Security

Code access security (CAS) is an extremely effective way to help protect your application at the code level, but implementing CAS properly often can be a daunting task. Although an in-depth analysis of CAS is beyond the scope of this chapter, we do provide a basic description to enable you to understand the security vulnerabilities that are common in applications using managed code. If you are already familiar with CAS, you might want to skim through this section or skip to the next section.

User Security vs. Code Security

Other than being able to develop a powerful application quickly, one of the benefits of using managed code is that the .NET Framework has the ability to protect resources by using CAS. Chapter 13, Finding Weak Permissions, discusses granting users permissions to certain resources; however, CAS is able to remove privileges an application has to system resources. For instance, imagine an application that needs only to read and write to a single file. At the system level, permissions can be set on the file to grant only certain users access to the file. If the application is written in managed code, additional security measures could be used to grant the application Read and Write permissions to that single file. If there happens to be a canonicalization bug (covered in Chapter 12, Canonicalization Issues ), the .NET Framework will prevent the code from accessing any other file than what is allowed.

Figure 15-1 illustrates the basic user security model using unmanaged (native) code. The operating system knows whether the user has permission to the binary executable file that is trying to be accessed to launch the application. When both the user and object permissions match, access is granted. If the user has permission, the unmanaged binary executable can be extremely powerful and potentially can run malicious code.

image from book
Figure 15-1: User security for an object in unmanaged code

Using managed code, CAS can isolate a user from the effects of running potentially malicious code, even if the user is an administrator. Figure 15-2 shows that a user can have permissions to an object and the application s CAS permissions can restrict access to that object. For the application to access other objects, such as a network resource, separate permissions for those resources must also be granted.

image from book
Figure 15-2: User security and code security interacting in managed code

Overview of CAS

Once an assembly is compiled using the CLR and deployed to a location on the target machine, the next process is to execute the assembly. When the application is first launched, CAS is used to determine the permissions. Figure 15-3 gives a high-level overview of the interactions between an executing managed assembly and code access security.

image from book
Figure 15-3: Overview of how CAS policy works

The assembly in Figure 15-3 is loaded into the CLR when it is executed. CAS then determines all of the information about the assembly to construct evidence. The evidence and the policy the application is running under, which is set by the system administrator, are then used to figure out the code groups for the assembly. The CLR code group security authorization process (which is discussed in more depth in the section titled Code Groups later in the chapter) uses this information to finally determine which permissions are granted for the application. During the execution of the application, any resource or access that requires permission is verified by using CAS. Also, it is possible for an application to use CAS to further restrict or grant permissions for any code it then executes; however, it can grant only the permissions it has and no more.

Now let s look at some of the individual components of the .NET Framework and CAS that are used to help secure an application.

Assemblies

Managed code is compiled into an assembly. In short, an assembly contains the assembly manifest, type metadata, Microsoft Intermediate Language (MSIL), and a set of resources. From a security perspective, permissions are granted and requested at an assembly level. You can read more about .NET assemblies at http://msdn.microsoft.com/library/en-us/cpguide/html/cpconcontentsofassembly.asp .

Microsoft Intermediate Language

One of the great benefits of the .NET Framework is that a CPU-independent application can be written in any of the .NET languages, such as C#, VB.NET, and J#, because the application s managed source code is compiled into MSIL. MSIL is similar to assembly language in that it has instructions for processes such as managing objects, calling methods , accessing memory, and performing arithmetic and logical operations. Before the application can start, the MSIL is converted to code that is specific to the CPU architecture ”native code. This compiling is usually done by the just-in-time (JIT) compiler, which is available on multiple platforms.

Strong Names

An assembly can also be signed with a strong name key, which ensures the assembly is uniquely identifiable. The strong name consists of a name, version number, culture information if provided, and a public key. In the .NET Framework 2.0, the strong name also includes the processor architecture: MSIL, x86, amd64, or IA64. For example, the strong name for the System.Security.dll that is installed as part of the .NET Framework 1.1 is as follows :

 System.Security, Version=1.0.5000.0, Culture=neutral,     PublicKeyToken=b03f5f7f11d50a3a, Custom=null 

The strong name is generated using the assembly and a private key. Unless the private key is compromised, the strong name of an assembly cannot be duplicated and the .NET Framework will perform security checks to guarantee the file has not been modified since it was built. Because of this integrity check, your application can use strong names as evidence to ensure they aren t loading potentially Trojan assemblies that an attacker loaded onto the system.

Evidence

After the assembly is loaded into the runtime, certain information, known as evidence, is extracted and presented as input to the system to determine the permissions the assembly is granted. Evidence is the characteristics that identify an assembly, much like a fingerprint identifies a person. The information that an assembly can provide as evidence include these:

  • Zone     Similar to the concept of zones used in Microsoft Internet Explorer (refer to Chapter 10 for more details on Internet Explorer zones)

  • URL     A specific URL or file location from which the assembly was downloaded, such as http://www.microsoft.com/downloads or file://C:\Programs

  • Site     Site from which the assembly was downloaded, for example, www.microsoft.com

  • Strong name     An assembly s strong identity

  • Hash     Hash value of an assembly using a hash algorithm such as MD5 or SHA1

  • Application directory     The directory from which the assembly was loaded

  • Authenticode signature     Digital signature with the publisher s certificate

An assembly also can have custom evidence, such as a simple checksum of the assembly or the date the assembly was created. The custom evidence can be supplied by the host application or in any user code that loads the assembly. When the assembly is loaded, the evidence is computed in real time, meaning the evidence can change even if the assembly doesn t. For instance, if you execute an assembly from a particular directory, it might result in different permissions than if you ran it from another directory.

Permissions

The .NET Framework defines numerous permissions that are used to protect a resource or action. Here are several examples of common permissions that can be found in the .NET Framework:

  • EnvironmentPermission     Controls access to environment variables

  • FileIOPermission     Controls access to files and folders

  • PrintingPermission     Controls access to printers

  • ReflectionPermission     Controls access to an assembly s metadata through reflection

  • RegistryPermission     Controls access to the registry

  • SqlClientPermission     Controls access to SQL data source

  • WebPermission     Controls access to HTTP resources

Most permissions can be further defined by using parameters. For example, instead of granting an application FileIOPermission, the application can also specify the type of access that is granted: Read, Write, Append, and PathDiscovery. In addition, the FileIOPermission allows the path of the file or folder to be specified, so you can restrict an application to being able to read only a single file, as the following C# example shows:

 // Allow method to read only the file C:\test.txt. [FileIOPermission(SecurityAction.PermitOnly, Read = "C:\test.txt")] 

The example specifies the PermitOnly security action, which is discussed later in the chapter, and restricts the code to being able to read only the file located at C:\test.txt . If the code attempts to read any other file, a SecurityException would be thrown. Also, the example uses the declarative style , which is expressed at compile time and is scoped to an entire method, class, or namespace. On the other hand, an imperative style can also be used to allow more flexibility because it is calculated at run time. For example, the path might need to be determined programmatically, such as by using the following C# code:

 // Restrict the caller to reading only the temp file, which was // determined at run time. FileIOPermission filePerm =     new FileIOPermission(FileIOPermissionAccess.Read, tempFile); filePerm.PermitOnly(); ReadFileData(tempFile);                 // Do some read operation on the file. FileIOPermission.RevertPermitOnly();    // Remove the PermitOnly permission. 
Note  

It is generally recommended that you use the declarative style if at all possible since it is expressed in the assembly s metadata. Doing so makes it easier for tools to check the permissions in an assembly. Of course, if the permissions need to be calculated at run time or scoped to just a portion of the code, imperative style should be used.

Policies

A policy is actually an XML file that describes the permissions that are granted to assemblies. There are four policy levels, of which two can be configured by an administrator and used to determine the permissions that are granted to an assembly. Listed in order from highest to lowest they are as follows:

  • Enterprise     Used to set policy at the entire enterprise level

  • Machine     Used to set policy for code that runs on the machine

  • User     Used to set policy for the current user logged on to the machine

  • Application Domain     Used to set policy of an assembly loaded by an application

Generally, an administrator can only configure the Enterprise and Machine policies, but normally can t control the User or Application Domain policy. When the policy is evaluated, it starts with the permissions granted at the Enterprise level, then intersects those permissions with the ones at the Machine level, then intersects the resulting permissions with the User level, and finally intersects the policy on the Application Domain (if used). An Application Domain (AppDomain) can be used by an application to control the permissions that are granted to any assemblies it might load. By combining the permissions at each level from high to low using an intersection, the resulting permissions will never be higher than that granted by the previous level.

Figure 15-4 shows an example of how permissions from each level are intersected to determine the resulting permissions. Each policy consists of code groups that are used to determine the resulting permissions granted to an assembly.

image from book
Figure 15-4: Intersection of permissions from the four policy levels

Code Groups

A code group is a building block for policy trees that consist of two parts :

  • Membership condition     A membership condition is the checks done on an assembly based on the evidence that was collected. For example, an ASP.NET Web application might have UrlMemberShipCondition that is based on the URL of the application. For additional membership conditions defined by the .NET Framework, refer to http://msdn.microsoft.com/library/en-us/cpguide/html/cpconCodeGroups.asp .

  • Permission set     The permission set is the collection of permissions that are granted to an assembly when its evidence matches the membership condition.

Because the code groups in a policy act like a filter, permissions are granted once the condition is met. However, what happens if more than one condition can be met? A policy is evaluated in hierarchal order as the code groups appear. If a policy has multiple code groups, the first one in the policy XML is evaluated before the last one is. If an earlier code group matches the condition first, the permission set for that code group will be granted. During the evaluation, the code group can also determine how the permission set can be granted to the existing permission set using the following code group classes:

  • FileCodeGroup     Returns a permission set that grants file access to the application s directory

  • FirstMatchCodeGroup     Returns a permission set that is the union of the permissions of the root code group and the first child group that is also matched

  • NetCodeGroup     Returns a permission set that grants Web Connect access to the site from which the application is executed

  • UnionCodeGroup     Returns a permission set that is a union of all the pervious matching permission sets

Policy Example     To understand policy and how code groups are used to determine the permissions granted to an assembly, look at the following policy file:

 <configuration>   <mscorlib>     <security>       <policy>         <PolicyLevel version="1">           <SecurityClasses>             <SecurityClass Name="NamedPermissionSet" Description="System.Security.NamedPermissionSet"/>           </SecurityClasses>           <NamedPermissionSets>             <PermissionSet                 class="NamedPermissionSet"                 version="1"                 Unrestricted="true"                 Name="FullTrust"             />             <PermissionSet                 class="NamedPermissionSet"                 version="1"                 Name="Nothing"                 Description="Denies all resources, including the right to execute"              />           </NamedPermissionSets>           <CodeGroup               class="FirstMatchCodeGroup"               version="1"               PermissionSetName="Nothing">             <IMembershipCondition                 class="AllMembershipCondition"                  version="1"             />             <CodeGroup                 class="UnionCodeGroup"                 version="1"                 PermissionSetName="FullTrust">                    <IMembershipCondition                        class="UrlMembershipCondition"                        version="1"                        Url="$AppDirUrl$/bin/*"                    />             </CodeGroup>           </CodeGroup>         </PolicyLevel>       </policy>     </security>   </mscorlib> </configuration> 

In this sample policy file, two permission sets, called Nothing and FullTrust, are defined in the System.Security.NamedPermissionSet assembly. The first code group in the policy sets the permission to Nothing on all of the assemblies by having the membership condition AllMembershipCondition . That root code group has a child code group that unions the current permission set, which is now Nothing, with the permission set FullTrust for any applications that have a URL starting with the bin folder in the application s directory. For example, using the URL http://localhost/bin/sample.aspx , sample.aspx will be granted FullTrust. As you can see, the policy used the value $AppDirUrl$ to specify the application s directory URL and the asterisk (*) to indicate a wildcard for any applications running under the bin directory.

FullTrust

Code that is fully trusted (FullTrust) means exactly what the name implies ”you trust the code 100 percent. Managed code that is running as FullTrust has the ability to do anything on the system, such as call into unmanaged or native code, access random memory locations, use pointers, and manipulate any file. An assembly that is fully trusted can be called only by other managed code that is also fully trusted, unless the assembly is marked with the AllowPartially TrustedCallers attribute, which is discussed in the later section titled Understanding the Issues of Using APTCA. By default, applications that are installed locally are fully trusted.

Partial Trust

The CLR provides a great security system for partially trusted code, which is essentially code that is running with reduced permissions. The level of permissions that partially trusted code has is somewhere between FullTrust and no trust. The system has several policies to control the trust level for an application. For example, if you attempt to execute an application from a network share or from the Internet, by default the application will be partially trusted and probably won t execute unless it was designed to work in a partially trusted environment because it will attempt to access a resource it doesn t have permission to.

Sandboxing

The .NET Framework allows code to execute in a sandbox , meaning the assembly that is loaded is granted limited permissions. The main idea behind using a sandbox is to reduce what the code is capable of doing. After all, why allow code to access the registry if it does not need to? An administrator can use policy at run time or a developer can use an AppDomain at development time in order to reduce the granted permissions of an application. For example, applications running under the ASP.NET worker process are executed in an AppDomain. An ASP.NET Web application can run with less than full trust by using one of the predefined trust levels in a policy file. By default, ASP.NET has five security policies that grant different permissions: Full, High, Medium, Low, and Minimal. The concept of sandboxing is similar to running an application with least privileges, which is discussed in Chapter 13.

Important  

Similar to running applications with least privileges, an application that grants only the minimal permissions it uses can help mitigate security risks. PermitOnly and Deny are often used to create a virtual sandbox, but they do not achieve this effectively. If you are sandboxing potentially hostile code, use a separate AppDomain with the Internet permission set or some subset thereof instead.

Global Assembly Cache

Systems with the CLR also have the global assembly cache (GAC), which stores .NET assemblies that are designated to be shared between several applications. For example, you might have a new and efficient compression library. Applications that use the compression library are responsible for deploying the library, but sometimes it might be more desirable to install all such libraries in a common place. The GAC can be this common repository of shared libraries, but it should be used only if it is absolutely needed. Assemblies that are installed to the GAC must be strong named and also must execute as FullTrust by default ” meaning the code is highly trusted.

Note  

In the .NET Framework 2.0, the GAC will always by FullTrust, even if you changed the MyComputer zone to have no permissions.

Stack Walks

At this point, you might be wondering how the CLR ensures that the calling code has the correct permissions needed to access a resource or perform an operation. It does this by performing a stack walk and comparing the permissions of the caller and the AppDomain to the permission that is needed. This notion of checking the caller for a particular permission is known as a demand . The CLR can perform three types of demands: full demands, link demands, and inheritance demand.

Full Demands

A full demand performs an entire stack walk and checks each caller to make sure it has the permissions needed. Figure 15-5 shows how the CLR checks for Permission X in each caller. Assembly A calls a method in Assembly B that eventually calls all the way into Assembly D, thus triggering the stack walk to check for Permission X.

image from book
Figure 15-5: Example of a full stack walk caused by a demand for Permission X

Because a full demand can affect performance, the code can be optimized to perform fewer stack walks, such as by using a link demand. However, not checking all of the callers introduces a security risk that must be tested thoroughly, as discussed in the section Problems with Link Demands later in the chapter.

Link Demands

Unlike a full demand, link demands check the permissions only on the immediate caller. They are performed at JIT compilation, meaning you cannot imperatively do a link demand during code execution. In Figure 15-6, Assembly A is fully trusted and calls a method in Assembly B, which needs Permission X. Assembly B has a link demand for Permission X, so it checks whether the caller is granted Permission X. In this case, Assembly A also has the correct permission, so the link demand succeeds.

image from book
Figure 15-6: Example of a link demand that succeeds for a fully trusted assembly

If Assembly A is partially trusted, it might not have Permission X, so when Assembly B performs the link demand for Permission X, it will fail, as shown in Figure 15-7.

image from book
Figure 15-7: Example of a link demand that fails for a partially trusted assembly

Assembly A starts by calling a method in Assembly B. Because Assembly B has a LinkDemand on it, the demand is checked with Assembly A is JIT complied. Because Assembly A does not have Permission X, the call will not succeed.

Inheritance Demands

An inheritance demand is applied to a class that wants to make sure any derived classes have the specified permission. This demand prevents malicious code from deriving from another class. For instance, if Class A is protected by an inheritance demand, Class B can not inherit from Class A if it is not granted that permission. You can use only the declarative style for an inheritance demand.

Stack Walk Modifiers

In the previous section, you saw how CAS can cause a stack walk to check whether callers have a particular permission. You can also alter the behavior of the security checks by using stack walk modifiers, which can cause the stack walk to stop for later callers, deny a particular permission, or allow only a particular permission. From a security perspective, a developer needs to use stack walk modifiers with caution because they can cause security vulnerabilities. The following methods are used to override the security checks during a stack walk:

  • Assert

  • Deny

  • PermitOnly

Assert

Using assert essentially stops the stack walk from checking the rest of the callers if they have the permission demanded. The code that asserts declares that any of the callers should be trusted with the permission asserted. The code cannot assert for a permission it does not have, and the asserting code must be granted the permission that is being asserted and also must have the Assertion flag for SecurityPermission; otherwise , the security check will fail. Figure 15-8 shows that a partially trusted Assembly A can call a method in Assembly B.

image from book
Figure 15-8: Example of an assembly asserting for Permission X, thus allowing the call to the method to succeed

Deny

Using deny is a way to prevent code from accessing a resource or performing an action that requires a particular permission. When a deny is used, any callers downstream that cause a demand for that permission will not be granted access, even if all the callers have permissions for the resource. Figure 15-9 shows Assembly A calling a method in Assembly B, which then denies Permission X and calls a method in Assembly C. Assembly C does a demand for Permission X, so the CLR checks the callers, which fails because Assembly B denied Permission X.

image from book
Figure 15-9: Example of using deny to remove a permission for subsequent callers

However, denying permission does not block any downstream callers from asserting for that same permission. Thus, if a caller asserts for a permission that was previously denied, the security check will succeed and permission will be granted, as shown in Figure 15-10. Even though Assembly B denied Permission X, the assert in Assembly C for the permission causes the stack walk to stop there and the method call to succeed.

image from book
Figure 15-10: Example of a deny being reversed because a subsequent caller asserts for the permission
PermitOnly

PermitOnly is similar to using deny , but instead of denying a particular permission, PermitOnly grants only the permission specified and denies all other permissions. Figure 15-11 shows how the method call in Assembly A fails because Assembly B uses PermitOnly for Permission X, but Assembly B does a demand for Permission X and Permission Y. Like deny , any callers downstream that have the correct permissions can also override the PermitOnly and grant more permission than the assembly doing the PermitOnly might expect. Rather than use PermitOnly , use policy and AppDomains to guarantee that all of the callers have only the specified permissions.

image from book
Figure 15-11: Example of how PermitOnly can be used to grant only a single permission


Hunting Security Bugs
Hunting Security Bugs
ISBN: 073562187X
EAN: 2147483647
Year: 2004
Pages: 156

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net