Chapter 8 – Code Access Security in Practice
Retired Content |
---|
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. |
Improving Web Application Security: Threats and Countermeasures
J.D. Meier, Alex Mackman, Michael Dunner, Srinath Vasireddy, Ray Escamilla and Anandha Murukan
Microsoft Corporation
Published: June 2003
Last Revised: January 2006
Applies to:
- .NET Framework version 1.1
See the "patterns & practices Security Guidance for Applications Index" for links to additional security resources.
See the Landing Page for the starting point and a complete overview of Improving Web Application Security: Threats and Countermeasures.
Summary: This chapter shows you with practical examples, how to use .NET Framework code access security to improve the security of your assemblies. It presents an overview of the code access security programming model and then shows you how to use code access security to improve the security of your managed code.
Contents
In This Chapter
Overview
How to Use This Chapter
Code Access Security Explained
APTCA
Privileged Code
Requesting Permissions
Authorizing Code
Link Demands
Assert and RevertAssert
Constraining Code
File I/O
Event Log
Registry
Data Access
Directory Services
Environment Variables
Web Services
Sockets and DNS
Unmanaged Code
Delegates
Serialization
Summary
Additional Resources
In This Chapter
- Code access security explained
- Using APTCA
- Requesting permissions
- Sandboxing privileged code
- Authorizing code with identity demands
- Serialization, delegates, and threading
- Calling unmanaged code
Overview
Code access security is a resource constraint model designed to restrict the types of system resource that code can access and the types of privileged operation that the code can perform. These restrictions are independent of the user who calls the code or the user account under which the code runs.
Code access security delivers three main benefits. By using code access security, you can:
Restrict what your code can do
For example, if you develop an assembly that performs file I/O you can use code access security to restrict your code's access to specific files or directories. This reduces the opportunities for an attacker to coerce your code to access arbitrary files.
Restrict which code can call your code
For example, you may only want your assembly to be called by other code developed by your organization. One way to do this is to use the public key component of an assembly's strong name to apply this kind of restriction. This helps prevent malicious code from calling your code.
Identify code
To successfully administer code access security policy and restrict what code can do, the code must be identifiable. Code access security uses evidence such as an assembly's strong name or its URL, or its computed hash to identify code (assemblies.)
How to Use This Chapter
This chapter takes up where Chapter 7, "Building Secure Assemblies," left off. It shows how you can use code access security to further improve the security of your managed code. To get the most out of this chapter:
- Read Chapter 6, ".NET Security Fundamentals" for an overview and comparison of user (role)-based security versus code access security. Chapter 6 helps set the scene for the current chapter.
- Read Chapter 7, "Building Secure Assemblies." Read Chapter 7 before this chapter if you have not already done so.
- Read Chapter 9, "Using Code Access Security with ASP.NET." After you read this chapter, read Chapter 9 if you are interested specifically in ASP.NET code access security policy and ASP.NET trust levels.
Code Access Security Explained
To use code access security effectively, you need to know the basics such as the terminology and how policy is evaluated. For further background information about code access security, see the "Additional Resources" section at the end of this chapter. If you are already familiar with code access security, you may want to skip this section and go to the "APTCA" (AllowPartiallyTrustedCallersAttribute) section later in this chapter.
Code access security consists of the following elements:
- Code
- Evidence
- Permissions
- Policy
- Codegroups
Code
All managed code is subject to code access security. When an assembly is loaded, it is granted a set of code access permissions that determines what resource types it can access and what types of privileged operations it can perform. The Microsoft .NET Framework security system uses evidence to authenticate (identify) code to grant permissions.
Note An assembly is the unit of configuration and trust for code access security. All code in the same assembly receives the same permission grant and is therefore equally trusted.
Evidence
Evidence is used by the .NET Framework security system to identify assemblies. Code access security policy uses evidence to help grant the right permissions to the right assembly. Location-related evidence includes:
- URL. The URL that the assembly was obtained from. This is the codebase URL in its raw form, for example, http://webserver/vdir/bin/assembly.dll or file://C:/directory1/directory2/assembly.dll.
- Site. The site the assembly was obtained from, for example, http://webserver. The site is derived from the codebase URL.
- Applicationdirectory. The base directory for the running application.
- Zone. The zone the assembly was obtained from, for example, LocalIntranet or Internet. The zone is also derived from the codebase URL.
Author-related evidence includes:
Strong name. This applies to assemblies with a strong name. Strong names are one way to digitally sign an assembly using a private key.
Publisher. The Authenticode signature; based on the X.509 certificate used to sign code, representing the development organization.
Important Publisher evidence (the Authenticode signature) is ignored by the ASP.NET host and therefore cannot be used to configure code access security policy for server-side Web applications. This evidence is primarily used by the Internet Explorer host.
Hash. The assembly hash is based on the overall content of the assembly and allows you to detect a particular compilation of a piece of code, independent of version number. This is useful for detecting when third party assemblies change (without an updated version number) and you have not tested and authorized their use for your build.
Permissions
Permissions represent the rights for code to access a secured resource or perform a privileged operation. The .NET Framework provides codeaccesspermissions and codeidentitypermissions. Code access permissions encapsulate the ability to access a particular resource or perform a privileged operation. Code identity permissions are used to restrict access to code, based on an aspect of the calling code's identity such as its strong name.
Your code is granted permissions by code access security policy that is configured by the administrator. An assembly can also affect the set of permissions that it is ultimately granted by using permission requests. Together, code access security policy and permission requests determine what your code can do. For example, code must be granted the FileIOPermission to access the file system, and code must be granted the RegistryPermission to access the registry. For more information about permission requests, see the "Requesting Permissions" section later in this chapter.
Note Permission sets are used to group permissions together to ease administration.
Restricted and Unrestricted Permissions
Permissions can be restricted or unrestricted. For example, in its unrestricted state, the FileIOPermission allows code to read or write to any part of the file system. In a restricted state, it might allow code to read files only from a specific directory.
Demands
If you use a class from the .NET Framework class library to access a resource or perform another privileged operation, the class issues a permission demand to ensure that your code, and any code that calls your code, is authorized to access the resource. A permission demand causes the runtime to walk back up through the call stack (stack frame by stack frame), examining the permissions of each caller in the stack. If any caller is found not to have the required permission, a SecurityException is thrown.
Link Demands
A link demand does not perform a full stack walk and only checks the immediate caller, one stack frame further back in the call stack. As a result, there are additional security risks associated with using link demands. You need to be particularly sensitive to luring attacks.
Note With a luring attack, malicious code accesses the resources and operations that are exposed by your assembly, by calling your code through a trusted intermediary assembly.
For more information about how to use link demands correctly, see the "Link Demands" section later in this chapter.
Assert, Deny, and PermitOnly Methods
Code access permission classes support the Assert, Deny, and PermitOnly methods. You can use these methods to alter the behavior of a permission demand stack walk. They are referred to as stack walk modifiers.
A call to the Assert method causes the stack walk for a matching permission to stop at the site of the Assert call. This is most often used to sandbox privileged code. For more information, see the "Assert and RevertAssert" section later in this chapter.
A call to the Deny method fails any stack walk that reaches it with a matching permission. If you call some non-trusted code, you can use the Deny method to constrain the capabilities of the code that you call.
A call to the PermitOnly method fails any unmatching stack walk. Like the Deny method, it tends to be used infrequently but it can be used to constrain the actions of some non-trusted code that you may call.
Policy
Code access security policy is configured by administrators and it determines the permissions granted to assemblies. Policy can be established at four levels:
Enterprise. Used to apply Enterprise-wide policy.
Machine. Used to apply machine-level policy.
User. Used to apply per user policy.
ApplicationDomain. Used to configure the application domain into which an assembly is loaded.
ASP.NET implements application domain policy to allow you to configure code access security policy for Web applications and Web services. For more information about ASP.NET application domain policy, see Chapter 9, "Using Code Access Security with ASP.NET."
Policy settings are maintained in XML configuration files. The first three levels of policy (Enterprise, Machine, and User) can be configured by using the .NET Framework Configuration tool, which is located in the Administrative Tools program group or the Caspol.exe command line utility. ASP.NET application domain level policy must currently be edited with a text or XML-based editor.
For more information about policy files and locations, see Chapter 19, "Securing Your ASP.NET Application and Web Services."
Code Groups
Each policy file contains a hierarchical collection of code groups. Code groups are used to assign permissions to assemblies. A code group consists of two elements:
- A membership condition. This is based on evidence, for example, an assembly's zone or its strong name.
- A permission set. The permissions contained in the permission set are granted to assemblies whose evidence matches the membership condition.
How Does It Work?
Figure 8.1 shows a simplified overview of code access security.
Figure 8.1
Code access security*—*a simplified view
The steps shown in Figure 8.1 are summarized below.
An assembly is loaded.
This operation is performed by an application domain host. On a Web server loading a Web application assembly, this is the ASP.NET host.
Evidence is gathered from the assembly and presented by the host.
Evidence is evaluated against the defined security policy.
The output from security policy evaluation is one or more named permission sets that define the permission grant for the assembly.
Note An assembly can include permission requests, which can further reduce the permission grant.
Code within the assembly demands an appropriate permission prior to accessing a restricted resource or performing a privileged operation.
All of the .NET Framework base classes that access resources or perform privileged operations contain the appropriate permission demands. For example, the FileStream class demands the FileIOPermission, the Registry class demands the RegistryPermission, and so on.
If the assembly (and its callers) have been granted the demanded permission, the operation is allowed to proceed. Otherwise, a security exception is generated.
How Is Policy Evaluated?
When evidence is run through the policy engine, the output is a permission set that defines the set of permissions granted to an assembly. The policy grant is calculated at each level in the policy hierarchy: Enterprise, Machine, User, and Application Domain. The policy grant resulting from each level is then combined using an intersection operation to yield the final policy grant. An intersection is used to ensure that policy lower down in the hierarchy cannot add permissions that were not granted by a higher level. This prevents an individual user or application domain from granting additional permissions that are not granted by the Enterprise administrator.
Figure 8.2 shows how the intersection operation means that the resulting permission grant is determined by all levels of policy in the policy hierarchy.
Figure 8.2
Policy intersection across policy levels
In Figure 8.2, you can see that the intersection operation ensures that only those permissions granted by each level form part of the final permission grant.
How Do Permission Requests Affect the Policy Grant?
You can add security attributes to your assembly to specify its permission requirements. You can specify the minimal set of permissions that your assembly must be granted in order to run. These do not affect the permission grant. You can also specify the optional permissions your assembly could make use of but does not absolutely require, and what permissions you want to refuse. Refused permissions are those permissions you want to ensure your assembly never has, even if they are granted by security policy.
If you request optional permissions, the combined optional and minimal permissions are intersected with the policy grant, to further reduce it. Then, any specifically refused permissions are taken away from the policy grant. This is summarized by the following formula where PG is the policy grant from administrator defined security policy and Pmin , Popt , and Prefused are permission requests added to the assembly by the developer.
Resulting Permission Grant = (PG ∩ (Pmin∪ Popt)) – Prefused
For more information about how to use permission requests, their implications, and when to use them, see the "Requesting Permissions" section later in this chapter.
Policy Evaluation at a Policy Level
An individual policy file at each specific level consists of a hierarchical arrangement of code groups. These code groups include membership conditions that are used to determine which assemblies they apply to, and permission sets that are used to determine the permissions that should be granted to matching assemblies. A hierarchical structure enables multiple permission sets to be assigned to an assembly, and it allows security policy to support simple AND and OR logic. For example, consider the sample security policy shown in Figure 8.3.
Figure 8.3
Hierarchical code groups at a single policy level
Note The AllCode code group is a special code group that matches all assemblies. It forms the root of security policy and in itself grants no permissions, because it is associated with the permission set named Nothing.
Consider the granted permissions based on the security policy shown in Figure 8.3.
- Any assembly originating from the My_Computer_Zone (any locally installed assembly), is granted the permissions defined by the FullTrust permission set. This is a built-in permission set defined when the .NET Framework is installed and represents the unrestricted set of all permissions.
- Assemblies authored by Company1 and originating from the intranet zone are granted the permissions defined by the built-in LocalIntranet_Zone permission set and the custom Comp1PSet permission set.
- Assemblies authored by Company2 are granted permissions defined by the custom Comp2PSet permission set.
- Any assembly downloaded from a.b.c.com is granted permissions defined by the custom ABCPSet permission set.
Note If the membership condition for a particular code group is not satisfied, none of its children are evaluated.
Exclusive and Level Final Code Groups
Policy hierarchy processing and traversal can be fine-tuned using a couple of attributes specified at the code group level, both of which can be set through the .NET Framework Configuration Tool. These are:
Exclusive
This indicates that no other sibling code groups should be combined with this code group. You mark a code group as exclusive by selecting This policy level will only have the permissions from the permission set associated with this code group in the .NET Framework Configuration Tool.
LevelFinal
This indicates that any lower level policies should be ignored. You mark a code group as Level Final by selecting Policy levels below this level will not be evaluated in the .NET Framework Configuration Tool. For example, if a matching code group in the machine policy is marked LevelFinal, policy settings from the user policy file is ignored.
Note The application domain level policy, for example, ASP.NET policy for server-side Web applications, is always evaluated regardless of the level final setting.
APTCA
An assembly that has a strong name cannot be called by a partial trust assembly (an assembly that is not granted full trust), unless the strong named assembly contains AllowPartiallyTrustedCallersAttribute (APTCA) as follows:
[assembly: AllowPartiallyTrustedCallersAttribute()]
This is a risk mitigation strategy designed to ensure your code cannot inadvertently be exposed to partial trust (potentially malicious) code. The common language runtime silently adds a link demand for the FullTrust permission set to all publicly accessible members on types in a strong named assembly. If you include APTCA, you suppress this link demand.
Avoid Using APTCA
If you use APTCA, your code is immediately more vulnerable to attack and, as a result, it is particularly important to review your code for security vulnerabilities. Use APTCA only where it is strictly necessary.
In the context of server-side Web applications, use APTCA whenever your assembly needs to support partial trust callers. This situation can occur in the following circumstances:
- Your assembly is to be called by a partial trust Web application. These are applications for which the <trust> level is set to something other than Full. For more information about partial trust Web applications and using APTCA in this situation, see Chapter 9, "Using Code Access Security with ASP.NET."
- Your assembly is to be called by another assembly that has been granted limited permissions by the code access security administrator.
- Your assembly is to be called by another assembly that refuses specific permissions by using SecurityAction.RequestRefuse or SecurityAction.RequestOptional. These make the calling assembly a partial trust assembly.
- Your assembly is to be called by another assembly that uses a stack walk modifier (such as Deny or PermitOnly) to constrain downstream code.
Diagnosing APTCA Issues
If you attempt to call a strong named assembly that is not marked with APTCA from partial trust code such as a partial trust Web application, you see an exception similar to the one shown in Figure 8.4. Notice that the exception details provide no permission details and simply indicate that the required permissions (in this case, FullTrust) cannot be acquired from the calling assembly. In this case, the somewhat confusing description text means that the error occurred because the application's <trust> level was set to something other than Full.
Figure 8.4
The result of partial trust code calling a strong named assembly
To overcome this exception, either the calling code must be granted FullTrust or the assembly being called must be annotated with APTCA. Note that individual types within an assembly marked with APTCA might still require full trust callers, because they include an explicit link demand or regular demand for full trust, as shown in the following examples.
[PermissionSet(SecurityAction.LinkDemand, Name="FullTrust")]
[PermissionSet(SecurityAction.Demand, Unrestricted=true)]
Privileged Code
When you design and build secure assemblies, you must be able to identify privileged code. This has important implications for code access security. Privileged code is managed code that accesses secured resources or performs other security-sensitive operations, such as calling unmanaged code, using serialization, or using reflection. Privileged code is privileged because code access security must grant it specific permissions before it can function.
Privileged Resources
Privileged resources for which your code requires specific code access security permissions are shown in the Table 8.1.
Table 8.1 Secure Resources and Associated Permissions
Secure Resource | Requires Permission |
---|---|
Data access | SqlClientPermission
OleDbPermission OraclePermission Note The ADO.NET OLE DB and Oracle-managed providers currently require full trust. |
Directory services | DirectoryServicesPermission |
DNS databases | DnsPermission |
Event log | EventLogPermission |
Environment variables | EnvironmentPermission |
File system | FileIOPermission |
Isolated storage | IsolatedStoragePermission |
Message queues | MessageQueuePermission |
Performance counters | PerformanceCounterPermission |
Printers | PrinterPermission |
Registry | RegistryPermission |
Sockets | SocketPermission |
Web services (and other HTTP Internet resources) | WebPermission |
Privileged Operations
Privileged operations are shown in Table 8.2, together with the associated permissions that calling code requires.
Table 8.2 Privileged Operations and Associated Permissions
Operation | Requires Permission |
---|---|
Creating and controlling application domains | SecurityPermission with SecurityPermissionFlag.ControlAppDomain |
Specifying policy application domains | SecurityPermission with SecurityPermissionFlag.ControlDomainPolicy |
Asserting security permissions | SecurityPermission with SecurityPermissionFlag.Assertion |
Creating and manipulating evidence | SecurityPermission with SecurityPermissionFlag.ControlEvidence |
Creating and manipulating principal objects | SecurityPermission with SecurityPermissionFlag.ControlPrincipal |
Configuring types and channels remoting | SecurityPermission with SecurityPermissionFlag.RemotingConfiguration |
Manipulating security policy | SecurityPermission with SecurityPermissionFlag.ControlPolicy |
Serialization | SecurityPermission with SecurityPermissionFlag.SerializationFormatter |
Threading operations | SecurityPermission with SecurityPermissionFlag.ControlThread |
Reflection | ReflectionPermission |
Calling unmanaged code | SecurityPermission with SecurityPermissionFlag.UnmanagedCode |
Requesting Permissions
When you design and develop your assemblies, create a list of all the resources that your code accesses, and all the privileged operations that your code performs. At deployment time, the administrator may need this information to appropriately configure code access security policy and to diagnose security related problems.
The best way to communicate the permission requirements of your code is to use assembly level declarative security attributes to specify minimum permission requirements. These are normally placed in Assemblyinfo.cs or Assemblyinfo.vb. This allows the administrator or the consumer of your assembly to check which permissions it requires by using the Permview.exe tool.
RequestMinimum
You can use SecurityAction.RequestMinimum method along with declarative permission attributes to specify the minimum set of permissions your assembly needs to run. For example, if your assembly needs to access the registry, but only needs to retrieve configuration data from a specific key, use an attribute similar to the following:
[assembly: RegistryPermissionAttribute(
SecurityAction.RequestMinimum,
Read=@"HKEY_LOCAL_MACHINE\SOFTWARE\YourApp")]
If you know up front that your code will run in a full trust environment and will be granted the full set of unrestricted permissions, using RequestMinimum is less important. However, it is good practice to specify your assembly's permission requirements.
Note Permission attributes accept a comma-delimited list of properties and property values after the mandatory constructor arguments. These are used to initialize the underlying permission object. A quick way to find out what property names are supported is to use Ildasm.exe on the assembly that contains the permission attribute type.
RequestOptional
If you use SecurityAction.RequestOptional method, no other permissions except those specified with SecurityAction.RequestMinimum and SecurityAction.RequestOptional will be granted to your assembly, even if your assembly would otherwise have been granted additional permissions by code access security policy.
RequestRefused
SecurityAction.RequestRefuse allows you to make sure that your assembly cannot be granted permissions by code access security policy that it does not require. For example, if your assembly does not call unmanaged code, you could use the following attribute to ensure code access security policy does not grant your assembly the unmanaged code permission.
[assembly: SecurityPermissionAttribute(SecurityAction.RequestRefuse,
UnmanagedCode=true)]
Implications of Using RequestOptional or RequestRefuse
If you use RequestOptional, the set of permissions that are specified with RequestOptional and RequestMinimum are intersected with the permission grant given to your assembly by policy. This means that all other permissions outside of the RequestOptional and RequestMinimum sets are removed from your assembly's permission grant. Additionally, if you use RequestRefuse, the refused permissions are also removed from your assembly's permission grant.
So if you use RequestOptional or RequestRefuse, your assembly becomes a partial trust assembly, which has implications when you call other assemblies. Use the following considerations to help you decide whether you should use SecurityAction.RequestOptional or SecurityAction.RequestRefuse:
Do not use them if you need to directly call a strong named assembly without AllowPartiallyTrustedCallersAttribute (APTCA) because this prevents you from being able to call it.
Many strong named .NET Framework assemblies contain types that do not support partial trust callers and do not include APTCA. For more information, and a list of assemblies that support partial trust callers, see "Developing Partial Trust Web Applications," in Chapter 9, "Using Code Access Security with ASP.NET."
If you must call strong named assemblies without APTCA, let the administrators who install your code know that your code must be granted full trust by code access security policy to work properly.
If you do not need to access any APTCA assemblies, then add permission requests to refuse those permissions that you know your assembly does not need. Test your code early to make sure you really do not require those permissions.
If downstream code needs the permission you have refused, a method between you and the downstream code needs to assert the permission. Otherwise, a SecurityException will be generated when the stack walk reaches your code.
Authorizing Code
Code access security allows you to authorize the code that calls your assembly. This reduces the risk of malicious code successfully calling your code. For example, you can use identity permissions to restrict calling code based on identity evidence, such as the public key component of its strong name. You can also use explicit code access permission demands to ensure that the code that calls your assembly has the necessary permissions to access the resource or perform the privileged operation that your assembly exposes.
Usually, you do not explicitly demand code access permissions. The .NET Framework classes do this for you, and a duplicate demand is unnecessary. However, there are occasions when you need to issue explicit demands, for example, if your code exposes a custom resource by using unmanaged code or if your code accesses cached data. You can authorize code in the following ways:
- Restrict which code can call your code.
- Restrict inheritance.
- Consider protecting cached data.
- Protect custom resources with custom permissions.
Restrict Which Code Can Call Your Code
A method marked as public can be called by any code outside of the current assembly. To further restrict which other code can call your methods, you can use a code access security identity permission demand as shown in the following example.
public sealed class Utility
{
// Although SomeOperation() is a public method, the following
// permission demand means that it can only be called by assemblies
// with the specified public key.
[StrongNameIdentityPermission(SecurityAction.LinkDemand,
PublicKey="00240000048...97e85d098615")]
public static void SomeOperation() {}
}
The above code shows a link demand. This results in the authorization of the immediate caller. Therefore, your code is potentially open to luring attacks, where a malicious assembly could potentially access the protected resources or operations provided by your assembly through a trusted intermediary assembly with the specified strong name.
Depending on the nature of the functionality provided by your class, you may need to demand another permission to authorize the calling code in addition to using the identity-based link demand. Alternatively, you can consider using a full demand in conjunction with the StrongNameIdentityPermission, although this assumes that all code in the call stack is strong name signed using the same private key.
Note Issuing a full stack walk demand for the StrongNameIdentityPermission does not work if your assembly is called by a Web application or Web service. This is because it is not possible to strong name the dynamically compiled classes associated with ASP.NET Web applications or Web services.
Note In .NET 2.0, the ASP.NET Web applications can be precompiled by using the Visual Studio 2005 or aspnet_compiler.exe command line utility. Therefore, it is now possible to strong name the ASP.NET applications. For more information, see "How to: Sign Assemblies for Precompiled Web Sites."
In .NET 2.0, StrongNameIdentityPermission only works for partial trust callers. Any demand, including a link demand, will always succeed for full trust callers regardless of the strong name of the calling code.
To extract a public key from an assembly
Run the following command to obtain a hex representation of a public key from an assembly:
secutil -hex -strongname yourassembly.dll
To extract the public key from a key pair file
Generate the key pair file with the following command:
sn -k keypairfile
Extract the public key from the key pair file:
sn -p keypairfile publickeyfile
Obtain a hex representation of the public key:
sn -tp publickeyfile > publickeyhex.dat
Restrict Inheritance
If your class is designed as base class, you can restrict which other code is allowed to derive from your class by using an inheritance demand coupled with a StrongNameIdentityPermission as shown in the following example. This prevents inheritance of your class from any assembly that is not signed with the private key corresponding to the public key in the demand.
// The following inheritance demand ensures that only code within the
// assembly with the specified public key (part of the assembly's strong
// name can sub class SomeRestrictedClass
[StrongNameIdentityPermission(SecurityAction.InheritanceDemand,
PublicKey="00240000048...97e85d098615")]
public class SomeRestrictedClass
{
}
Note In .NET 2.0, StrongNameIdentityPermission only works for partial trust callers. Any demand, including a link demand, will always succeed for full trust callers regardless of the strong name of the calling code.
Consider Protecting Cached Data
If you access a resource by using one of the .NET Framework classes, a permission demand appropriate for the resource type in question is issued by the class. If you subsequently cache data for performance reasons, you should consider issuing an explicit code access permission demand prior to accessing the cached data. This ensures the calling code is authorized to access the specific type of resource. For example, if you read data from a file and then cache it, and you want to ensure that calling code is authorized, issue a FileIOPermission demand as shown in the following example.
// The following demand assumes the cached data was originally retrieved from
// C:\SomeDir\SomeFile.dat
new FileIOPermission(FileIOPermissionAccess.Read,
@"C:\SomeDir\SomeFile.dat").Demand();
// Now access the cache and return the data to the caller
Protect Custom Resources with Custom Permissions
If you expose a resource or operation by using unmanaged code, you should sandbox your wrapper code and consider demanding a custom permission to authorize the calling code.
Full trust callers are granted the custom permission automatically as long as the permission type implements the IUnrestrictedPermission interface. Partial trust callers will not have the permission unless it has been specifically granted by code access security policy. This ensures that non-trusted code cannot call your assembly to access the custom resources that it exposes. Sandboxing also means that you are not forced to grant the powerful UnmanagedCodePermission to any code that needs to call your code.
For more information about calling unmanaged code, see the "Unmanaged Code" section later in this chapter. For an example implementation of a custom permission, see "How To: Create a Custom Encryption Permission" in the "How To" section of this guide.
Link Demands
A link demand differs from a regular permission demand in that the run-time demands permissions only from the immediate caller and does not perform a full stack walk. Link demands are performed at JIT compilation time and can only be specified declaratively.
Carefully consider before using a link demand because it is easy to introduce security vulnerabilities if you use them. If you do use link demands, consider the following issues:
- Luring attacks
- Performance and link demands
- Calling methods with link demands
- Mixing class and method level link demands
- Interfaces and link demands
- Structures and link demands
- Virtual methods and link demands
Luring Attacks
If you protect code with a link demand, it is vulnerable to luring attacks, where malicious code gains access to the resource or operation exposed by your code through a trusted intermediary as shown in Figure 8.5.
Figure 8.5
An example of a luring attack with link demands
In figure 8.5, methods in assembly X, which access a secure resource, are protected with a link demand for a specific public key (using a StrongNameIdentityPermission). Assemblies A, B, and C are signed with the private key that corresponds to the public key that assembly X trusts, and so these assemblies can call assembly X. Assemblies A, B, and C are subject to a luring attack if they do not check their callers for specific evidence before making calls to assembly X. For example, assembly D that is not signed with the same private key cannot call assembly X directly. It could, however, access assembly X through the trusted assembly A, if A does not check its callers, either with another link demand or through a full demand.
Only use link demands in an assembly when you trust the assembly's callers not to expose its functionality further (for example, when the caller is an application, not a library) or when you know it is safe just to verify the immediate caller's identity with an identity permission demand.
Note In .NET 2.0, StrongNameIdentityPermission only works for partial trust callers. Any demand, including a link demand, will always succeed for full trust callers regardless of the strong name of the calling code.
Performance and Link Demands
Compared to other Web application performance issues such as network latency and database access, the cost of a stack walk is small. Do not use link demands purely for performance reasons. Full demands provide a much greater degree of security.
Calling Methods with Link Demands
If you call a link demand protected method, only your code will be checked by the link demand. In this situation, you should make sure your code takes adequate measures to authorize its callers, for example, by demanding a permission.
Mixing Class and Method Level Link Demands
Method level link demands override class level link demands. For example, in the following code fragment, the link demand for FileIOPermission must be repeated on the method declaration or the EnvironmentPermission link demand replaces the class level FileIOPermission demand.
[FileIOPermission(SecurityAction.LinkDemand, Unrestricted=true)]
public sealed class SomeClass
{
// The unrestricted FileIOPermission link demand must be restated at the
// method level, if the method is decorated with another link demand.
// Failure to do so means that (in this example) that the
// EnvironmentPermission link demand would override the class level
// FileIOPermission link demand
[FileIOPermission(SecurityAction.LinkDemand, Unrestricted=true)]
[EnvironmentPermission(SecurityAction.LinkDemand, Read="PATH")]
public void SomeMethod()
{
}
}
Interfaces and Link Demands
If your class implements an interface and one of the method implementations has a link demand, make sure that the method declaration on the interface definition has the same link demand. Otherwise, the caller simply has to call your method through the interface to bypass the link demand. An example is shown below.
public interface IMyInterface
{
// The link demand shown on the method implementation below
// should be repeated here
void Method1();
}
public class MyImplementation : IMyInterface
{
// The method implementation has a link demand but the interface does not
[SecurityPermission(SecurityAction.LinkDemand,
Flags=SecurityPermissionFlag.ControlPrincipal)]
public void Method1()
{
}
}
With the following code, the caller is subject to the link demand:
MyImplementation t = new MyImplementation();
t.Method1();
With the following code, the caller is not subject to the link demand:
IMyInterface i = new MyImplementation();
i.Method1();
Structures and Link Demands
Link demands do not prevent the construction of structures by untrusted callers. This is because default constructors are not automatically generated for structures. Therefore, the structure level link demand only applies if you use an explicit constructor.
For example:
[SecurityPermission(SecurityAction.LinkDemand,
Flags=SecurityPermissionFlag.ControlPrincipal)]
public struct SomeStruct
{
// This explicit constructor is protected by the link demand
public SomeStruct(int i)
{
field = i;
}
private int field;
}
The following two lines of code both result in a new structure with the field initialized to zero. However, only the first line that uses the explicit constructor is subject to a link demand.
SomeStruct s = new SomeStruct(0);
SomeStruct s = new SomeStruct();
The second line is not subject to a link demand because a default constructor is not generated. If this were a class instead of a structure, the compiler would generate a default constructor annotated with the specified link demand.
Virtual Methods and Link Demands
If you use link demand to protect a method override in a derived class, make sure you also put it on the corresponding virtual base class method. Otherwise, if the JIT compiler sees a reference to the base class type where no link demand is present, no link demand is performed.
Assert and RevertAssert
You can call the CodeAccessPermission.Assert method to prevent a demand propagating beyond the current stack frame. By using Assert, you vouch for the trustworthiness of your code's callers. Because of the potential for luring attacks, Assert needs to be used with caution.
Asserts are most often used to sandbox privileged code. If you develop code that calls Assert, you need to ensure that there are alternate security measures in place to authorize the calling code. The following recommendations help you to minimize the risks.
- Use the demand / assert pattern
- Reduce the Assert duration
Use the Demand / Assert Pattern
Demanding a specific permission before calling Assert is an effective way to authorize upstream code. Sometimes you might be able to demand a built-in permission type to authorize calling code.
Often, if your assembly is exposing functionality that is not provided by the .NET Framework class library, such as calling the Data Protection API (DPAPI), you need to develop a custom permission and demand the custom permission to authorize callers. For example, you might develop a custom Encryption permission to authorize callers to a managed DPAPI wrapper assembly. Demanding this permission and then asserting the unmanaged code permission is an effective way to authorize calling code.
For more information about this approach and about developing custom permissions, see "How To: Create a Custom Encryption Permission" in the "How To" section of this guide.
Reduce the Assert Duration
If you only need to call Assert to satisfy the demands of a single downstream method that your code calls, then place the Assert immediately prior to the downstream method call. Then immediately call RevertAssert to keep the assertion window as small as possible, and to ensure that any subsequent code your method calls does not inadvertently succeed because the Assert is still in effect.
A common practice is to place the call to RevertAssert in a finally block to ensure that it always gets called even in the event of an exception.
Constraining Code
Constraining code and building least privileged code is analogous to using the principle of least privilege when you configure user or service accounts. By restricting the code access security permissions available to your code, you minimize scope for the malicious use of your code.
There are two ways to constrain code to restrict which resources it can access and restrict which other privileged operations it can perform:
- Using policy permission grants
- Using stack walk modifiers
Using Policy Permission Grants
You can configure code access security policy to grant a restricted permission set to a specific assembly. This constrains its ability to access resources or perform other privileged operations. For more information, see "How To: Configure Code Access Security Policy to Constrain an Assembly" in the "How To" section of this guide.
Using Stack Walk Modifiers
You can use stack walk modifiers to ensure that only specific permissions are available to the code that you call. For example, you can use SecurityAction.PermitOnly to ensure that your method and any methods that are called only have a restricted permission set. The following example applies a very restrictive permission set. The code only has the permission to execute. It cannot access resources or perform other privileged operations.
[SecurityPermissionAttribute(SecurityAction.PermitOnly,
Flags=SecurityPermissionFlag.Execution)]
public void SomeMethod()
{
// The current method and downstream can only execute. They cannot access
// resources or perform other privileged operations.
SomeOtherMethod();
}
The following sections show you how to use code access security to constrain various types of resource access including file I/O, event log, registry, data access, directory services, environment variables, Web services, and sockets.
File I/O
To be able to perform file I/O, your assembly must be granted the FileIOPermission by code access security policy. If your code is granted the unrestricted FileIOPermission, it can access files anywhere on the file system, subject to Windows security. A restricted FileIOPermission can be used to constrain an assembly's ability to perform file I/O, for example, by specifying allowed access rights (read, read/write, and so on.)
Constraining File I/O within your Application's Context
A common requirement is to be able to restrict file I/O to specific directory locations such as your application's directory hierarchy.
Note If your Web application is configured for Medium trust, file access is automatically restricted to the application's virtual directory hierarchy. For more information, see Chapter 9, "Using Code Access Security with ASP.NET."
Configuring your application for Medium trust is one way to constrain file I/O, although this also constrains your application's ability to access other resource types. There are two other ways you can restrict your code's file I/O capabilities:
- Using PermitOnly to restrict File I/O
- Configuring code access security policy to restrict File I/O
Using PermitOnly to Restrict File I/O
You can use declarative attributes together with SecurityAction.PermitOnly as shown in the following example to constrain file I/O.
// Allow the code only to read files from c:\YourAppDir
[FileIOPermission(SecurityAction.PermitOnly, Read=@"c:\YourAppDir\")]
[FileIOPermission(SecurityAction.PermitOnly, PathDiscovery=@"c:\YourAppDir\")]
public static string ReadFile(string filename)
{
// Use Path.GetFilePath() to canonicalize the file name
// Use FileStream.OpenRead to open the file
// Use FileStream.Read to access and return the data
}
Note The second attribute that specifies PathDicovery access is required by the Path.GetFilePath function that is used to canonicalize the input file name.
To avoid hard coding your application's directory hierarchy, you can use imperative security syntax, and use the HttpContext.Current.Request.MapPath(".") to retrieve your Web application's directory at runtime. You must reference the System.Web assembly and add the corresponding using statement as shown in the following example.
using System.Web;
public static string ReadFile(string filename)
{
string appDir = HttpContext.Current.Request.MapPath(".");
FileIOPermission f = new FileIOPermission(PermissionState.None);
f.SetPathList(FileIOPermissionAccess.Read, appDir);
f.SetPathList(FileIOPermissionAccess.PathDiscovery, appDir);
f.PermitOnly();
// Use Path.GetFilePath() to canonicalize the file name
// Use FileStream.OpenRead to open the file
// Use FileStream.Read to access and return the data
}
Note For a Windows application you can replace the call to MapPath with a call to Directory.GetCurrentDirectory to obtain the application's current directory.
Configuring Code Access Security Policy to Restrict File I/O
An administrator can also configure code access security policy to restrict your code's ability to perform file I/O beyond your application's virtual directory hierarchy.
For example, the administrator can configure Enterprise or Machine level code access security policy to grant a restricted FileIOPermission to your assembly. This is most easily done if your assembly contains a strong name, because the administrator can use this cryptographically strong evidence when configuring policy. For assemblies that are not strong named, an alternative form of evidence needs to be used. For more information about how to configure code access security to restrict the file I/O capability of an assembly, see "How To: Configure Code Access Security Policy to Constrain an Assembly, " in the "How To" section of this guide.
If your assembly is called by a Web application, a better approach is to configure ASP.NET (application domain-level) code access security policy because you can use $AppDirUrl$ which represents the application's virtual directory root. For more information about restricting File I/O using ASP.NET code access security policy, see Chapter 9, "Using Code Access Security with ASP.NET."
Requesting FileIOPermission
To help the administrator, if you know your assembly's precise file I/O requirements at build time (for example, you know directory names), declare your assembly's FileIOPermission requirements by using a declarative permission request as shown in the following example.
[assembly: FileIOPermission(SecurityAction.RequestMinimum, Read=@"C:\YourAppDir")]
The administration can see this attribute by using permview.exe. The additional advantage of using SecurityAction.RequestMinimum is that the assembly fails to load if it is not granted sufficient permissions. This is preferable to a runtime security exception.
Event Log
To be able to access the event log, your assembly must be granted the EventLogPermission by code access security policy. If it is not, for example, because it is running within the context of a medium trust Web application, you need to sandbox your event logging code. For more information about sandboxing access to the event log, see Chapter 9, "Using Code Access Security with ASP.NET."
Constraining Event Logging Code
If you want to constrain the actions of event log wrapper code — perhaps code written by another developer or development organization — you can use declarative attributes together with SecurityAction.PermitOnly as shown in the following example.
The following attribute ensures that the WriteToLog method and any methods it calls can only access the local computer's event log and cannot delete event logs or event sources. These operations are not permitted by EventLogPermissionAccess.Instrument.
[EventLogPermission(SecurityAction.PermitOnly,
MachineName=".",
PermissionAccess=EventLogPermissionAccess.Instrument)]
public static void WriteToLog( string message )
To enforce read-only access to existing logs, use EventLogPermissionAccess.Browse.
Requesting EventLogPermission
To document the permission requirements of your code, and to ensure that your assembly cannot load if it is granted insufficient event log access by code access security policy, add an assembly level EventLogPermissionAttribute with SecurityAction.RequestMinimum as shown in the following example.
// This attribute indicates that your code requires the ability to access the
// event logs on the local machine only (".") and needs instrumentation access
// which means it can read or write to existing logs and create new event sources
// and event logs
[assembly: EventLogPermissionAttribute(SecurityAction.RequestMinimum,
MachineName=".",
PermissionAccess=
EventLogPermissionAccess.Instrument)]
Registry
Code that accesses the registry by using the Microsoft.Win32.Registry class must be granted the RegistryPermission by code access security policy. This permission type can be used to constrain registry access to specific keys and sub keys, and can also control code's ability to read, write, or create registry keys and named values.
Constraining Registry Access
To constrain code to reading data from specific registry keys, you can use the RegistryPermissionAttribute together with SecurityAction.PermitOnly. The following attribute ensures that the code can only read from the YourApp key (and subkeys) beneath HKEY_LOCAL_MACHINE\SOFTWARE.
[RegistryPermissionAttribute(SecurityAction.PermitOnly,
Read=@"HKEY_LOCAL_MACHINE\SOFTWARE\YourApp")]
public static string GetConfigurationData( string key, string namedValue )
{
return (string)Registry.
LocalMachine.
OpenSubKey(key).
GetValue(namedValue);
}
Requesting RegistryPermission
To document the permission requirements of your code, and to ensure your assembly cannot load if it is granted insufficient registry access from code access security policy, add an assembly level RegistryPermissionAttribute with SecurityAction.RequestMinimum as shown in the following example.
[assembly: RegistryPermissionAttribute(SecurityAction.RequestMinimum,
Read=@"HKEY_LOCAL_MACHINE\SOFTWARE\YourApp")]
Data Access
The ADO.NET SQL Server data provider supports partial trust callers. The other data providers including the OLE DB, Oracle, and ODBC providers currently require full trust callers.
Note In ADO.NET 2.0, the OLE DB, Oracle and ODBC .NET data providers no longer require full trust. To use these providers from a partial trust Web application, see How To: Use Medium Trust in ASP.NET 2.0.
If you connect to SQL Server using the SQL Server data provider, your data access code requires the SqlClientPermission. You can use SqlClientPermission to restrict the allowable range of name/value pairs that can be used on a connection string passed to the SqlConnection object. In the following code, the CheckProductStockLevel method has been enhanced with an additional security check to ensure that blank passwords cannot be used in the connection string. If the code retrieves a connection string with a blank password, a SecurityException is thrown.
[SqlClientPermissionAttribute(SecurityAction.PermitOnly,
AllowBlankPassword=false)]
public static int CheckProductStockLevel(string productCode)
{
// Retrieve the connection string from the registry
string connectionString = GetConnectionString();
. . .
}
For more information about how to sandbox data access code to allow the OLE DB and other data providers to be used from partial trust Web applications, see Chapter 9, "Using Code Access Security with ASP.NET."
Directory Services
Currently, code that uses classes from the System.DirectoryServices namespace to access directory services such as Active Directory must be granted full trust. However, you can use the DirectoryServicesPermission to constrain the type of access and the particular directory services to which code can connect.
Constraining Directory Service Access
To constrain code, you can use the DirectoryServicesPermissionAttribute together with SecurityAction.PermitOnly. The following attribute ensures that the code can only connect to a specific LDAP path and can only browse the directory.
[DirectoryServicesPermissionAttribute(SecurityAction.PermitOnly,
Path="LDAP://rootDSE",
PermissionAccess=DirectoryServicesPermissionAccess.Browse)]
public static string GetNamingContext(string ldapPath)
{
DirectorySearcher dsSearcher = new DirectorySearcher(ldapPath);
dsSearcher.PropertiesToLoad.Add("defaultNamingContext");
dsSearcher.Filter = "";
SearchResult result = dsSearcher.FindOne();
return (string)result.Properties["adsPath"][0];
}
Requesting DirectoryServicesPermission
To document the permission requirements of your code, and to ensure your assembly cannot load if it is granted insufficient directory services access from code access security policy, add an assembly level DirectoryServicesPermissionAttribute with SecurityAction.RequestMinimum as shown in the following example.
[assembly: DirectoryServicesPermissionAttribute(SecurityAction.RequestMinimum,
Path="LDAP://rootDSE",
PermissionAccess=DirectoryServicesPermissionAccess.Browse)]
Environment Variables
Code that needs to read or write environment variables using the System.Environment class must be granted the EnvironmentPermission by code access security policy. This permission type can be used to constrain access to specific named environment variables.
Constraining Environment Variable Access
To constrain code so that it can only read specific environment variables, you can use the EnvironmentPermissionAttribute together with SecurityAction.PermitOnly. The following attributes ensure that the code can only read from the username, userdomain, and temp variables.
[EnvironmentPermissionAttribute(SecurityAction.PermitOnly, Read="username")]
[EnvironmentPermissionAttribute(SecurityAction.PermitOnly, Read="userdomain")]
[EnvironmentPermissionAttribute(SecurityAction.PermitOnly, Read="temp")]
public static string GetVariable(string name)
{
return Environment.GetEnvironmentVariable(name);
}
Requesting EnvironmentPermission
To document the permission requirements of your code, and to ensure your assembly cannot load if it is granted insufficient environment variable access from code access security policy, add an assembly level EnvironmentPermissionAttribute with SecurityAction.RequestMinimum as shown in the following code.
[assembly: EnvironmentPermissionAttribute(SecurityAction.RequestMinimum,
Read="username"),
EnvironmentPermissionAttribute(SecurityAction.RequestMinimum,
Read="userdomain"),
EnvironmentPermissionAttribute(SecurityAction.RequestMinimum,
Read="temp")]
Web Services
Code that calls Web services must be granted the WebPermission by code access security policy. The WebPermission actually constrains access to any HTTP Internet-based resources.
Constraining Web Service Connections
To restrict the Web services to which your code can access, use the WebPermissionAttribute together with SecurityAction.PermitOnly. For example, the following code ensures that the PlaceOrder method and any methods it calls can only invoke Web services on the http://somehost site.
[WebPermissionAttribute(SecurityAction.PermitOnly,
ConnectPattern=@"http://somehost/.*")]
[EnvironmentPermissionAttribute(SecurityAction.PermitOnly, Read="USERNAME")]
public static void PlaceOrder(XmlDocument order)
{
PurchaseService.Order svc = new PurchaseService.Order();
// Web service uses Windows authentication
svc.Credentials = System.Net.CredentialCache.DefaultCredentials;
svc.PlaceOrder(order);
}
In the prior example, the ConnectPattern property of the WebPermissionAttribute class is used. This allows you to supply a regular expression that matches the range of addresses to which a connection can be established. The EnvironmentPermissionAttribute shown previously is required because the code uses Windows authentication and default credentials.
The following example shows how to use the Connect attribute to restrict connections to a specific Web service.
[WebPermissionAttribute(SecurityAction.PermitOnly,
Connect=@"http://somehost/order.asmx")]
Sockets and DNS
Code that uses sockets directly by using the System.Net.Sockets.Socket class must be granted the SocketPermission by code access security policy. In addition, if your code uses DNS to map host names to IP addresses, it requires the DnsPermission.
You can use SocketPermission to constrain access to specific ports on specific hosts. You can also restrict whether the socket can be used to accept connections or initiate outbound connections, and you can restrict the transport protocol, for example, Transmission Control Protocol (TCP) or User Datagram Protocol (UDP).
Constraining Socket Access
To constrain code so that it can only use sockets in a restricted way, you can use the SocketPermissionAttribute together with SecurityAction.PermitOnly. The following attributes ensure that the code can connect only to a specific port on a specific host using the TCP protocol. Because the code also calls Dns.Resolve to resolve a host name, the code also requires the DnsPermission.
[SocketPermissionAttribute(SecurityAction.PermitOnly,
Access="Connect",
Host="hostname",
Port="80",
Transport="Tcp")]
[DnsPermissionAttribute(SecurityAction.PermitOnly, Unrestricted=true)]
public string MakeRequest(string hostname, string message)
{
Socket socket = null;
IPAddress serverAddress = null;
IPEndPoint serverEndPoint = null;
byte[] sendBytes = null, bytesReceived = null;
int bytesReceivedSize = -1, readSize = 4096;
serverAddress = Dns.Resolve(hostname).AddressList[0];
serverEndPoint = new IPEndPoint(serverAddress, 80);
socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
bytesReceived = new byte[readSize];
sendBytes = Encoding.ASCII.GetBytes(message);
socket.Connect(serverEndPoint);
socket.Send(sendBytes);
bytesReceivedSize = socket.Receive(bytesReceived, readSize, 0);
socket.Close();
if(-1 != bytesReceivedSize)
{
return Encoding.ASCII.GetString(bytesReceived, 0, bytesReceivedSize);
}
return "";
}
Requesting SocketPermission and DnsPermission
To document the permission requirements of your code, and to ensure your assembly cannot load if it is granted insufficient socket or DNS access from code access security policy, add an assembly level SocketPermissionAttribute and a DnsPermissionAttribute with SecurityAction.RequestMinimum as shown in the following example.
[assembly: SocketPermissionAttribute(SecurityAction.RequestMinimum,
Access="Connect",
Host="hostname",
Port="80",
Transport="Tcp")
DnsPermissionAttribute(SecurityAction.PermitOnly, Unrestricted=true)]
Unmanaged Code
Code that calls unmanaged Win32 APIs or COM components requires the unmanaged code permission. This should only be granted to highly trusted code. It is defined by the SecurityPermission type with its Flags property set to SecurityPermissionFlag.UnmanagedCode.
The following guidelines for calling unmanaged code build upon those introduced in Chapter 7, "Building Secure Assemblies."
- Use naming conventions to indicate risk.
- Request the unmanaged code permission.
- Sandbox unmanaged API calls.
- Use SupressUnmanagedCodeSecurityAttribute with caution.
Use Naming Conventions to Indicate Risk
Categorize your unmanaged code and prefix the types used to encapsulate the unmanaged APIs by using the following naming convention.
Safe. This identifies code that poses no possible security threat. It is harmless for any code, malicious or otherwise, to call. An example is code that returns the current processor tick count. Safe classes can be annotated with the SuppressUnmanagedCode attribute which turns off the code access security permission demand for full trust.
[SuppressUnmanagedCode] class SafeNativeMethods { [DllImport("user32")] internal static extern void MessageBox(string text); }
Native. This is potentially dangerous unmanaged code, but code that is protected with a full stack walking demand for the unmanaged code permission. These are implicitly made by the interop layer unless they have been suppressed with the SupressUnmanagedCode attribute.
class NativeMethods { [DllImport("user32")] internal static extern void FormatDrive(string driveLetter); }
Unsafe. This is potentially dangerous unmanaged code that has the security demand for the unmanaged code permission declaratively suppressed. These methods are potentially dangerous. Any caller of these methods must do a full security review to ensure that the usage is safe and protected because no stack walk is performed.
[SuppressUnmanagedCodeSecurity] class UnsafeNativeMethods { [DllImport("user32")] internal static extern void CreateFile(string fileName); }
Request the Unmanaged Code Permission
Strong-named
[assembly: SecurityPermission(SecurityAction.RequestMinimum,
UnmanagedCode=true)]
Sandbox Unmanaged API Calls
Isolate calls to unmanaged code in specific assemblies and keep the number of assemblies that call unmanaged code to a minimum. Then, use the sandboxing pattern to ensure that the unmanaged code permission is only granted to selected assemblies.
To sandbox your managed code that calls unmanaged code
Place your code that calls unmanaged code in a separate (wrapper) assembly.
Add a strong name to the assembly.
This allows custom code access security policy to be easily applied to the assembly. For more information, see the "Strong Names" section in Chapter 7, "Building Secure Assemblies."
Request the unmanaged code permission (as described in the preceding section.)
Authorize calling code with a full permission demand.
You typically need to use a custom permission that represents the unmanaged resource being exposed by your assembly. For example:
(new EncryptionPermission(EncryptionPermissionFlag.Encrypt, storePermissionFlag.Machine)).Demand();
Assert the unmanaged code permission in your wrapper class:
(new SecurityPermission(SecurityPermissionFlag.UnmanagedCode)).Assert();
For a full example implementation that shows you how to call the unmanaged Win32 DPAPI functionality, see "How To: Create a Custom Encryption Permission," in the "How To" section of this guide.
Use SuppressUnmanagedCodeSecurity with Caution
If your assembly makes many calls to unmanaged code, the performance overhead associated with multiple unmanaged code permission demands can become an issue.
In this case, you can use the SupressUnmanagedCodeSecurity attribute on the P/Invoke method declaration. This causes the full demand for the unmanaged permission to be replaced with a link demand which only occurs once at JIT compilation time.
In common with the use of link demands, your code is now vulnerable to luring attacks. To mitigate the risk, you should only suppress the unmanaged code permission demand if your assembly takes adequate precautions to ensure it cannot be coerced by malicious code to perform unwanted operations. An example of a suitable countermeasure is if your assembly demands a custom permission that more closely reflects the operation being performed by the unmanaged code
Using SuppressUnmanagedCodeSecurity with P/Invoke
The following code shows how to apply the SuppressUnmanagedCodeSecurity attribute to a Platform Invocation Services (P/Invoke) method declaration.
public NativeMethods
{
// The use of SuppressUnmanagedCodeSecurity here applies only to FormatMessage
[DllImport("kernel32.dll"), SuppressUnmanagedCodeSecurity]
private unsafe static extern int FormatMessage(
int dwFlags,
ref IntPtr lpSource,
int dwMessageId,
int dwLanguageId,
ref String lpBuffer, int nSize,
IntPtr *Arguments);
}
Using SuppressUnmanagedCodeSecurity with COM Interop
For COM interop calls, the attribute must be used at the interface level, as shown in the following example.
[SuppressUnmanagedCodeSecurity]
public interface IComInterface
{
}
Delegates
There is no way of knowing in advance what a delegate method is going to do when you invoke it. If your assembly supports partial trust callers, you need to take extra precautions when you invoke a delegate. You can use code access security to further improve security.
- Consider restricting permissions for the delegate.
- Do not assert a permission before calling a delegate.
Consider Restricting Permissions for the Delegate
The permissions granted to the code that calls the delegate determine the capabilities of the delegate. If your code has more permissions than the code that gives you the delegate, this provides a way for the caller to execute code using elevated permissions. To address this issue, you can either authorize the external code at the point it passes you the delegate with an appropriate permission demand, or you can restrict the permissions of the delegate just prior to calling it by using a deny or permit only stack modifier. For example, the following code only grants the delegate code execution permission to constrain its capabilities.
// Delegate definition
public delegate void SomeDelegate();
. . .
// Permit only execution, prior to calling the delegate. This prevents the
// delegate code accessing resources or performing other privileged
// operations
new SecurityPermission(SecurityPermissionFlag.Execution).PermitOnly();
// Now call the "constrained" delegate
SomeDelegate();
// Revert the permit only stack modifier
CodeAccessPermission.RevertPermitOnly();
Do Not Assert a Permission Before Calling a Delegate
Asserting a permission before calling a delegate is dangerous to do because you have no knowledge about the nature or trust level of the code that will be executed when you invoke the delegate. The code that passes you the delegate is on the call stack and can therefore be checked with an appropriate security demand. However, there is no way of knowing the trust level or permissions granted to the delegate code itself.
For more guidelines about using delegates securely, see the "Delegates" section in Chapter 7, "Building Secure Assemblies."
Serialization
Code that supports serialization must be granted a SecurityPermission with its Flag attribute set to SerializationFormatter. If you develop classes that support serialization and your code supports partial trust callers, you should consider using additional permission demands to place restrictions on which code can serialize your object's state.
Restricting Serialization
If you create a class that implements the ISerializable interface, which allows your object to be serialized, you can add a permission demand to your ISerializable.GetObjectData implementation to authorize the code that is attempting to serialize your object. This is particularly important if your code supports partial trust callers.
For example, the following code fragment uses a StrongNameIdentityPermission demand to ensure that only code signed with a particular private key corresponding to the public key in the demand can serialize your object's state.
[StrongNameIdentityPermission(SecurityAction.Demand,
PublicKey="00240000048...97e85d098615")]
public override void GetObjectData(SerializationInfo info,
StreamingContext context)
For more guidelines about using serialization securely, see the "Serialization" section in Chapter 7, "Building Secure Assemblies."
Note In .NET 2.0, StrongNameIdentityPermission only works for partial trust callers. Any demand, including a link demand, will always succeed for full trust callers regardless of the strong name of the calling code.
Summary
Code access security allows you to restrict what your code can do, restrict which code can call your code, and identify code. In full trust environments where your code and the code that calls you have the unrestricted set of all permissions, code access security is of less significance.
If your code supports partial trust callers, the security risks are that much greater. In partial trust scenarios, code access security enables you to mitigate some of the additional risks and allows you to constrain privileged code.
Additional Resources
For more information, see the following resources:
- "Security in .NET: The Security Infrastructure of the CLR Provides Evidence, Policy, Permissions, and Enforcement Services" by Don Box, MSDN Magazine, September 2002, at https://msdn.microsoft.com/en-us/magazine/default.aspx.
- "Security in .NET: Enforce Code Access Rights with the Common Language Runtime" by Keith Brown, MSDN Magazine, February 2001, at https://msdn.microsoft.com/en-us/magazine/default.aspx.
- .NET Framework Security by LaMacchia, Lange, Lyons, Martin and Price, published by Addison Wesley.
Retired Content |
---|
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. |