.NET Security

I l @ ve RuBoard

.NET security applies at a number of levels. The first level is the user's ability to run a piece of code that might perform a privileged operation or access sensitive data. A .NET component or ASP.NET Web application can be configured to use role-based security, which can check the identity of the user who is running a method or instantiating a class, and then grant or deny access based on that identity.

The second aspect of security concerns the code itself. The dynamic nature of the common language runtime and assemblies allows an application to download code from a remote site, load it, and run it. Using strong names and certificates to sign an assembly provides confidence in the authenticity of the code and ensures that it is not some spoof put in place by a malicious individual. We described this earlier.

The third and final aspect concerns what the code can do and what it has access to. For example, you might want to restrict the ability of assemblies downloaded from the Internet to write to your hard disk or access some other valuable resource. An administrator can set the security policy that governs what an assembly can and cannot do.

Application Domains

In a conventional operating system, each application is executed in a separate process. The purpose of this separation is to isolate applications from each other. The operating system typically ensures that one process cannot, inadvertently or otherwise , access the address space of another process, corrupt another process, or generally cause mayhem to other processes by going awry. Most modern operating systems also apply security at the process level ”the application often inherits the identity of the user running the process, and the operating system can use this information to determine whether the application should be allowed to perform certain sensitive operations.

This scheme provides a high degree of protection between processes, but at a cost. A process consumes resources ( mainly memory), and the more processes there are, the more resources you need to have available to run them. Also, an application that requests the services of another application (as is the case in a distributed system) will have to make cross-process calls. This involves marshaling and unmarshaling of data between process boundaries, which can be expensive. You cannot avoid this for processes running on different computers, but if both processes are executing on the same machine, this overhead can become a burden .

.NET uses a different model: It uses application domains ” environments created by a host process for running a .NET application. A single process can host several application domains. The managed nature of .NET applications and the common language runtime guarantees that different applications running in different application domains in the same process cannot interfere maliciously with each other because assemblies are verified when they are loaded (as we explained earlier in this chapter).

An application domain is actually an instance of the System.AppDomain class. It implements methods that allow the runtime to load and execute assemblies, enumerate the assemblies and threads being used by the application domain, and even terminate the domain. If several application domains being hosted by the same process load the same assembly, the host process can share a single instance of the assembly (but not the data) between application domains.

A major benefit of application domains is realized with server-side applications (Web server applications and .NET Remoting servers). A single server process can create, destroy, or even recycle application domains as client requests arrive . This makes it possible for a single process to handle many simultaneous requests in a safe and secure manner, and it greatly improves scalability. For example, a Web application being accessed by dozens of clients can run in a single process and share the same assemblies in memory across all clients . If code in one application domain needs to communicate with code in another application domain in the same process, there is no need for costly cross-process marshaling ”the application host effectively performs local procedure calls!

Application hosts are transparent as far as the user running .NET applications is concerned . This is because much of the work in creating application domains is carried out silently behind the scenes. Internet Explorer 6.0 and later acts as a host, loading the runtime and creating application domains when it is called on to execute managed browser-based controls. (One application domain is created for each Web site by default.) Similarly, when you launch a .NET executable from the shell or the command line, a host application is created automatically and the .NET executable runs in a domain created by that host.

You can create custom runtime hosts using the methods documented in the runtime Hosting Interfaces specification. A host contains an unmanaged stub, which is responsible for performing initialization and loading the runtime. The common language runtime creates an initial default application domain for running managed code. The purpose of this domain is to run the managed part of the host process rather than user code. The host application obtains a reference to the new application domain from the common language runtime and loads the managed portion of its code into this domain. The managed portion of the host process is responsible for creating additional application domains and loading assemblies as needed by the runtime. At this point, the unmanaged stub of the host application is no longer required and can be discarded.

The host application itself determines the policy for creating application domains based on application configurations, security, and the need to manage resources (unloading code that is no longer required). Individual assemblies cannot be unloaded; instead, the entire application domain must be terminated . You can see that the policies adopted for creating application domains can have a major impact on the footprint of a host process. Creating custom host applications is a specialized art!

You should note one interesting fact about threads and application domains. Threads belong to a host process rather than a single application domain, so there is not a 1:1 correspondence between threads and application domains. Several threads can execute in the same application domain at the same time, and a single thread can cross from one application domain to another (when making calls from one application domain to another, for example). The common language runtime keeps track of which threads are executing in which application domain.

Figure 2-15 shows how the unmanaged stub loads the common language runtime, which in turn creates the default application domain. Further application domains can be created (and destroyed ) by code running in the default application domain. These application domains load and use assemblies, which are then executed by the common language runtime.

Figure 2-15. Host processes, application domains, and assemblies

Host applications have intimate knowledge about the assemblies being run by the various application domains they have created, so they play an important part in the code access security model implemented by .NET.

Role-Based Security

Role-based security is used to specify who can execute your code. A .NET component can tag classes and methods declaratively , stating the identity and/or roles that can execute the code. If the user running the code does not meet these requirements, the result is a SecurityException . You should be prepared to trap and handle this exception in your code and report a meaningful error to the user ”and possibly record the security exception in the Windows Event log as well.

If you prefer, you can use the features of the .NET Framework System.Security.Permissions and System.Security.Principal namespaces to perform imperative security checking in your own code.

Principals

Role-based security uses principals to represent the identity and role of a user attempting to run a piece of code. If you're operating within a Windows domain, you can use Windows users and groups to implement an integrated security scheme that encompasses both Windows and .NET. Windows users can be mapped to .NET users, and Windows groups can be mapped to .NET roles. Using this approach means you can use the built-in Windows tools to administer users and roles, which are known as Windows Principals. Windows Principals also support impersonation ”the ability of a component configured to run using one identity to switch to the identity used by a calling component and perform operations as this new identity.

If this is not convenient ”for example, if you cannot guarantee that your users will be members of a particular domain ”the .NET security scheme also allows you to use Generic Principals, which are independent of Windows users and groups.

Finally, you can define your own custom classes that implement the IPrincipal interface found in the System.Security.Principal namespace. This interface defines an Identity property and a method called IsInRole . The Identity property identifies the principal (user) in some application-defined manner. The IsInRole method is used to determine whether the principal belongs to a specified role. You write your own code to implement this property and method.

Note

The common language runtime security mechanisms are independent of any operating system security. Using Windows Principals allows you to map Windows security information to the runtime, but it is a manual rather than automatic operation ”you must explicitly configure the application domain to use Windows security. If you're running on an operating system that has little or no inherent security (Windows 98 or Windows ME, for example), the common language runtime security mechanisms will still apply.


Declarative Security

You can use System.Security.Permissions.PrincipalPermissionAttribute to indicate the identities and roles of users that can run your code. The following example shows the CakeInfo class with the FeedsHowMany method restricted to members of the Bakers group in the Wonderland domain and the user JSharp . If you apply PrincipalPermissionAttribute several times (as shown in the example), identities that match any of the attributes shown will be allowed to execute the method. If you specify a Name and a Role in the same attribute, you will restrict access to identities that satisfy both criteria ”that is, the identity must match the specified name and the role.

 packageCakeUtils; importSystem.Reflection.*; importSystem.Security.Permissions.*; publicclassCakeInfo { /**@attributePrincipalPermissionAttribute(SecurityAction.Demand, Role="WONDERLAND\Bakers")*/ /**@attributePrincipalPermissionAttribute(SecurityAction.Demand, Name="WONDERLAND\JSharp")*/ //Workouthowmanypeopleacakeofagivensize,shape, //andfillingwillfeed publicstaticshortFeedsHowMany(shortdiameter,shortshape, shortfilling) { } } 

You can tag a class with PrincipalPermissionAttribute ” the restrictions will be applied to every method in the class. However, if you also tag a method, the restrictions of the method will override those of the class (even if they're less restrictive ).

A client application that calls this method must make sure that the application domain is using the appropriate authentication provider. Authentication will fail otherwise. The following example executes the SetPrincipalPolicy method of the current application domain (which is retrieved using the AppDomain.get_CurrentDomain method). The authentication provider is set to PrincipalPolicy.WindowsPrincipal , which indicates that Windows Principals should be used. This authentication provider then validates the user when the Feeds ­HowMany method is called.

 packageSizeCake; importCakeUtils.*; importSystem.*; importSystem.Security.Principal.*; publicclassSizeTest { publicstaticvoidmain(String[]args) { shortnumEaters; //ConfiguretheapplicationdomaintousetheWindowsPrincipal //authenticationprovider AppDomain.get_CurrentDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); //Howmanypeoplewilla10" hexagonalspongecakefeed? numEaters=CakeInfo.FeedsHowMany((short)10,CakeShape.Hexagonal, CakeFilling.Sponge); Console.WriteLine("Thiswillfeed " +numEaters+ " people"); } } 

Security Demands

The value SecurityAction.Demand , which is invoked by Principal ­PermissionAttribute , forces a security check not only on the direct calling method (in the client, in this case) but also on the method that called the calling method, and so on all the way up the call stack. In this case, the call to FeedsHowMany will fail the security check if the identity of the caller was changed at any point in the call stack. This type of thorough security checking is vital if you cannot guarantee the trustworthiness of the calling code!

A less powerful option is SecurityAction.LinkDemand . This value causes the identity of the immediate caller to be checked but does not proceed any further up the call stack. Use this option only for trusted code.

Imperative Security

Instead of using attributes, you can implement what's known as imperative security by writing your own code to check the identity of a user executing your methods. Imperative security is a little more involved than declarative security, but it gives you a lot more flexibility if you need it. Declarative security is fixed when your assembly is compiled, but imperative code allows you to adapt to changing circumstances dynamically.

The following code shows the FeedsHowMany method again, but this time using imperative rather than declarative security:

 packageCakeUtils; importSystem.Reflection.*; importSystem.Security.Permissions.*; publicclassCakeInfo { //Workouthowmanypeopleacakeofagivensize,shape,andfilling //willfeed publicstaticshortFeedsHowMany(shortdiameter,shortshape, shortfilling) { //Imperativesecuritycheck-makesurecallerisin //WONDERLAND\Bakersrole //orhastheWONDERLAND\JSharpidentity PrincipalPermissionrolePermission= newPrincipalPermission(null, "WONDERLAND\Bakers"); PrincipalPermissionnamePermission= newPrincipalPermission("WONDERLAND\JSharp",null); rolePermission.Union(namePermission).Demand(); } } 

The method creates two PrincipalPermission objects: rolePermission and namePermission . The rolePermission object comprises the WONDERLAND\Bakers role. The first parameter of the constructor is the identity to use. If you want to include all the members of a certain role, you can set the first parameter to null . Similarly, if you set the identity parameter to a name, you can set the role parameter to null if you do not want to restrict the identity to any particular role, as the namePermission object does. The Union method combines permissions, and the Demand method performs the security check. If the check fails, an exception will be raised; otherwise, execution will continue with the remainder of the method.

Code Access Security

Role-based security determines who can access your code, and code access security determines what your code is allowed to do when it runs. Code access security examines characteristics of an assembly, such as its source, which it refers to as evidence. The .NET security system uses configuration files that map evidence to permission sets, which specify what the code is and isn't allowed to do. The mapping between evidence and permission sets is called a security policy.

Code access security policies can be defined at three levels: enterprise, machine, and user. Policy information is held in XML format in files located in \Windows\Microsoft.NET\Framework\< version >\config . These files should be protected appropriately to prevent unauthorized editing. An administrator can make use of the .NET Framework Configuration tool to maintain this information. A command-line tool, Caspol.exe, is also available, but it is not very friendly.

Evidence

Evidence is information passed to the runtime from the host application loading an assembly about the origin of that assembly. As you'll recall, assemblies can be loaded from almost anywhere , such as a URL over the Internet, a network file share, or a local disk. What's more, the location of an assembly can change during the lifetime of an application as new versions are released and configuration files are updated with different code bases.

Evidence takes many forms, but the most common ones are

  • The installation directory (local machine, network share, and so on)

  • The assembly publisher's signature (created using Authenticode)

  • The strong name of the assembly

  • The URL the assembly was loaded from

  • The zone of origin

Runtime security uses zones in a similar way to Internet Explorer. Assemblies originating from different zones (the Internet, a local intranet, My Computer, and so on) can be granted different levels of trust. The .NET Framework Configuration tool lets you modify the degree of trust for each zone, but unless you have good reason to do so, you should probably leave them at their default settings. Zone information can be modified for a single user or for the computer.

Figure 2-16 shows the Security Adjustment Wizard, which is part of the .NET Framework Configuration tool.

Figure 2-16. The Security Adjustment Wizard

Permissions and Permission Sets

Runtime security has a set of built-in code access permissions that can be granted to assemblies, depending on their origin (as determined by the available evidence). Table 2-1 summarizes these permissions. If these are insufficient for your requirements, you can create your own custom permissions. See "Creating Your Own Code Access Permissions" in the .NET Framework Developer's Guide for details.

Table 2-1. Code Access Permissions

Permission Name

Description

DirectoryServicesPermission

Allows the assembly to programmatically access Directory Services.

DnsPermission

Grants access to the Domain Name System.

EnvironmentPermission

Allows the assembly to read and write environment variables .

EventLogPermission

Allows the assembly to use the Windows Event Logs.

FileDialogPermission

Permits access to files selected by the user in the Open File common dialog box.

FileIOPermission

Grants the ability to read, write, and append to files and directories.

IsolatedStorageFilePermission

Allows access to private virtual file systems.

IsolatedStoragePermission

Grants access to user-specific isolated storage.

MessageQueuePermission

Allows the assembly to manipulate MSMQ message queues.

OleDbPermission

Grants access to databases using OLE DB.

PerformanceCounterPermission

Allows the assembly to access Windows performance counters.

PrintingPermission

Provides access to printers.

ReflectionPermission

Allows the assembly to use reflection to discover run ­time information about types.

RegistryPermission

Grants access to the Windows Registry.

SecurityPermission

Allows code to query and manipulate runtime security information. Also permits calls to unmanaged code and allows assemblies to skip code verification.

ServiceControllerPermission

Allows an assembly to start, stop, and query Windows Services.

SocketPermission

Allows an assembly to make and accept connections using a socket.

SqlClientPermission

Grants access to the SQL Server data provider.

UIPermission

Allows the assembly to use user interface functionality.

WebPermission

Allows the assembly to make and accept connections on a Web address.

To simplify the task of assigning permissions, you can group permissions into permission sets. Figure 2-17 shows the built-in .NET permission sets. You can also define your own permission sets in code or by using the .NET Framework Configuration tool.

Figure 2-17. Permission sets in the .NET Framework Configuration tool

Table 2-2 below describes the default permissions assigned to the various permission sets.

Table 2-2. Built-In Permission Sets

Permission Set

Permission(s)

Nothing

No permissions

Execution

SecurityPermission

FullTrust

N/A

Internet

FileDialogPermission , IsolatedStorageFilePermission , SecurityPermission , UIPermission , PrintingPermission

LocalIntranet

EnvironmentPermission , FileDialogPermission , IsolatedStorageFilePermission , ReflectionPermission , SecurityPermission , UIPermission , DnsPermission , PrintingPermission , EventLogPermission

SkipVerification

SecurityPermission

Everything

All permissions

With the exception of Everything , you cannot modify these permission sets. The FullTrust permission set causes security checking to be bypassed, so it does not need to contain any permissions. Naturally, you should be very careful when using this permission set!

You can define permission sets at the user, machine, and enterprise levels.

Security Policy and Code Groups

As we mentioned earlier, the sets of permissions that are ascribed to assemblies based on evidence is referred to as a security policy. The .NET Framework Configuration tool lets you configure security policies by using code groups. .NET defines a number of built-in code groups, but you can edit them and change their associated permission sets. The Create Code Group Wizard in the .NET Framework Configuration tool also allows you to create your own custom code groups. You specify the evidence to use, plus a permission set. Code groups can be defined at the enterprise, machine, and individual user levels.

All assemblies are evaluated against all code groups when they are loaded, and they are granted the permissions associated with any corresponding evidence. A single assembly might match the evidence in more than one code group, in which case the assembly will be granted the union of all the permissions of the matching code groups. If the assembly subsequently attempts to perform an operation that is incompatible with the permissions granted, the runtime will raise a SecurityException .

Requesting and Refusing Permissions

An assembly can specify a minimum set of permissions needed in order to use it successfully, or it can filter certain permissions that it doesn't need to prevent malicious use. The assembly developer can specify this information declaratively (using attributes) or with code. For example, the SecureLibrary assembly shown below specifies that the System.Diagnostics.EventLogPermission is required to use any of the classes in the assembly, but any operations calling methods that require System.Security.Permissions.FileIOPermission will be refused , even if the calling assemblies have been granted this permission.

 packageSecureLibrary; importSystem.Diagnostics.*; importSystem.Security.Permissions.*; /**@assemblyEventLogPermissionAttribute(SecurityAction.RequestMinimum)*/ /**@assemblyFileIOPermissionAttribute(SecurityAction.RequestRefuse)*/ publicclassAuditor { } 

You can also tag individual methods and classes. The TransmitData method shown below specifies that it must have System.Net.SocketPermission . It also revokes System.Security.Permissions.UIPermission to prevent potentially malicious use:

 importSystem.Net.*; importSystem.Security.Permissions.*; publicclassAuditor { /**@attributeSocketPermissionAttribute(SecurityAction.Demand)*/ /**@attributeUIPermissionAttribute(SecurityAction.Deny)*/ publicvoidTransmitData(Stringdata) { } } 

Microsoft provides the PERMVIEW.exe tool (in the folder \Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin), which you can use to display the permissions requested , refused, demanded, and denied by the code in an assembly.

Java Security Policy

If you're familiar with security features of the Java language implementation, you're aware that, by default, applications written in the Java language that are installed and executed on the same local machine have no security manager. Essentially, local applications written in the Java language are considered fully trusted.

With .NET, the situation is subtly different. Assemblies that are loaded from the hard disk of the user's machine are members of the My Computer zone, which by default also has full trust status. However, if you change the degree of trust that the My Computer zone enjoys, you'll find that your local assemblies are subject to the corresponding security constraints and might no longer work as a result.

In other words, plan your security policies carefully .

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

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