SECCIONES
AÑADIR ARTÍCULO
CLUB TÉCNICO
CURSOS
WEB AMIGAS
| Fecha de publicación: 2008.04.09 | |
| Grado de Dificultad: 3 | |
While developing an IT system, or just an application for the .NET Framework platform, one is likely to face the task of implementing mechanisms of security, permissions, authentication and authorisation. This article describes some aspects of such usage. While developing an IT system, or just an application for the .NET Framework platform, one is likely to face the task of implementing mechanisms of security, permissions, authentication and authorisation. At that point there are at least three possible approaches to choose from:
For obvious reason, the first approach is hardly a good one. The second one might be acceptable, then again it will require large amounts of time and effort. Last but not least, the third one usually turns out to be the best one… why? An integrated security system The .NET Framework platform contains an integrated security system providing administrators, programmers and end users with a consistent, uniform and transparent security model, all the necessary administrative tools and documentation. One might say - why bother with all that? After all our company is already protected by access passwords, firewalls, anti-virus scanners etc. However, one should bear in mind that in the great majority of cases the weakest link in the security chain is humans themselves. Quite frequently users use resources coming from many unknown sources - e.g. documents, e-mail attachments, Web sites or FTP. Such resources can contain malicious code or flaws which make them useful in attacks against, destruction of or theft of data. The security system in .NET Framework allows setting restrictions on application permissions depending on the trustworthiness of their source of origin. By default, every application coming from the local computer possesses a full set of permissions (Full Trust) which grants them access to all the resources of the computer. On the other hand, applications coming e.g. from the LAN or from the Internet have their permissions restricted from the beginning. Of course .NET Framework provides the means of flexible configuration of which sources of origin are trusted and to what degree, as well as to grant permissions to applications coming from particular sources. Implementation of mechanisms of authorisation and authentication that enforce presentation of user credentials in an application (e.g. by providing a login and a password) and restrict access to various features depending on the roles assigned to the given user, is important too. Here assistance is given by the platform itself, through the ability to control permissions depending on roles (Role-Based Security), together with mechanisms of identification and identity. Security PoliciesA security policy is the basis and the foundation of all the security mechanisms built into .NET Framework. Security policies are configurable and extensible definitions of conditions, basing on which the CLR grants appropriate permissions to every loaded assembly. Security policies can be set and modified by the administrator through the .NET Framework configuration tool - mscorcfg.msc (a snap-in for the management console MMC) - and the tool called caspol.exe (available from the command line). A security policy consists of four main elements:
In order to understand exactly the purpose and significance of these elements, as well as the dependencies between them, let us begin with the last one: Permissions - that is, definitions of permissions which the CLR can grant loaded assemblies which match conditions defined by the security policy. Permissions can be segregated into three groups:
Code access permissions - they allow protection of resources and access to restricted operations against unauthorised use by untrusted assemblies. .NET Framework provides a series of built-in access permissions to files, the registry, the user interface, calls of non-managed code etc., thus making it possible for the administrator to restrict any assembly's access to various resources of the computer. The programmer on the other hand is able to implement explicit dropping of permissions the application doesn't require but which have been granted to it. Such approach allows one to limit permissions possessed by the application to the minimum that is necessary for its proper operations - which in turn decreases the risk of the application being exploited by malicious code (a virus, a trojan etc.) through flaws in its code in order to attack the machine the code has been run on. Additionally, the programmer is capable to simulate lack of permissions to access certain resources on his own computer, thus verifying behaviour of the application under such circumstances. In other words, the programmer is capable of testing the application in conditions more closely resembling its deployment environment. Identity permissions - they are granted on the basis of Evidence, which may consist of various information about the origin of the given assembly, e.g. about the website the assembly has come from, who published it, does it possess a so-called Strong Name or which zone it comes from (Internet, Intranet, Local computer etc.). We can distinguish two types of evidence: Host Evidence and Assembly Evidence. Host evidence is created and provided when the given assembly is loaded by the so-called Host, that is e.g. the shell of Windows (Shell Host), a Web browser (Browser Host) or a Web server (Server Host). Assembly evidence is created and issued by the assembly itself (assuming appropriate functionality has been implemented and included) and by default is ignored - still, it can be used by programmers extending features of security policies provided by the platform. Role-based security permissions - this permission (there is only one) is granted depending on current identity of the user of the application and the roles (s)he is assigned. Therefore, it makes it possible to restrict, at several levels (the whole assembly, methods, properties etc.), access to selected parts of code depending on the name of currently-authenticated user or the roles this user has been assigned. Programmers can also create their own, custom permissions in either of the three aforementioned groups Named Permission SetsNamed permission sets are exactly what their name says - named sets of code access permissions described a moment ago. In addition to the possibility for the administrators to define their own permission sets, the platform provides seven predefined, ready-to-use sets: FullTrust (unrestricted access to all the resources), SkipVerification (permissions to skip verification of code), Execution (execution permissions), Nothing (no permissions), LocalInternet (default permissions granted to applications on a local intranet), Internet (default permissions granted to Internet applications), Everything (unrestricted access to all the resources protected by built-in permissions Code GroupsCode groups are tree-like structures containing membership conditions, having met which for a certain group results in grant of a permission set assigned to this group. Let us now attempt to resolve this enigmatic definition. As mentioned earlier on, every loaded assembly gets created so-called Evidence, containing information about origin of that assembly. Afterwards, the CLR initiates the process of evaluating that evidence and, basing on this, granting the assembly appropriate permissions. Code groups are exactly what enables configuration and control of the evidence evaluation process. A single code group contains two very important properties: a membership condition and a permission set. The first property - the membership condition - can be e.g. the condition that an assembly must originate from a certain URL (e.g. "http://www.t3.com.pl") or possess a so-called Strong Name, in further section of this article we will describe other predefined kinds of condition provided together with .NET Framework. A test of meeting a condition takes place by comparing arguments of the condition with elements of the given assembly's evidence. The second property is a permission set - that is, permissions which will be granted if the membership condition is met. Therefore, now we know what a code group is - a definition of a condition of belonging to it and permissions granted when that condition is met. What is then the point of merging such code groups into a tree-like structure? The reason for that is that such hierarchical system of conditions and permissions allows the administrator to perform extremely flexible modelling of the permission-granting process. The root of such a tree is a code group (called "All_Code") whose membership condition is always met by all assemblies. All child code groups can be composed into arbitrary tree-like structures, in which the CLR will verify whether the condition is met for a certain code group assuming the conditions it its parent groups have been met as well. In order to understand better how code groups work, let us have a look at Figure 1.
Figure 1. Security policies - an example code group tree The code group model presented in the figure tells the CLR that:
The final permission set is a sum of all the permissions the CLR obtained while traversing the tree. Therefore, if we launched an imaginary application originating from the Web site "http://t3.com.pl" and possessing a Strong Name the public key of which equals the value passed as an argument of the condition, such an application would be granted permissions defined by the permission set "LocalIntranet" along with permission contained in the permission set "Execution". .NET Framework provides eight predefined membership conditions we can use:
Additionally, programmers are able to define their own, custom membership conditions - the same applies to permission sets and code groups. Furthermore, their definitions can be written to or read from XML files. Security Policy LevelsLet us now move on to the most general element of a security policy, namely - to security policy levels. We distinguish four policy levels:
Each of these elements (except application domain policy) has its own tree of code groups, list of permission sets, definitions of permissions themselves and of membership conditions. This structure is illustrated by Figure 2. Figure 2. Three levels of a security policy: Enterprise, Machine, User Security policy levels make it possible for the administrator to apply different policies of evidence evaluation and permission granting at the level of whole enterprises, individual computers and logged-in users. The process of determining the so-called effective (final) permission for the loaded assembly is therefore slightly more complicated than we described it in the section discussing code groups. Namely - having loaded the assembly the system determines its permissions by processing code groups at the highest (enterprise) policy level, after which the process is repeated at the Machine Policy level and finally at the User Policy level. The final, not always applicable level is the Application Domain, defined by the host which is loading the assembly (to discuss application domains would be beyond the scope of this article - therefore, we'd like to refer the readers to documentation of .NET Framework SDK). The CLR sums permissions granted at each level - staying faithful to the rule that permissions from a higher level can never be elevated at a lower level but can be reduced. This means that if administrators e.g. deny certain applications access to the system registry at the level of the whole enterprise, no assembly will be granted access to that registry even if the permission has been granted at the machine level. Code Access Security (CAS)CAS is a mechanism which controls and enforces access permissions to resources and operations which have been granted to executed code. When executed code attempts to perform an operation on a protected resource, CAS checks whether it possesses necessary permissions, then traverses the call stack upwards checking, whether the code of all previous calls possessed those permissions as well. Such a mechanism, even though it somewhat decreases performance, makes it possible to prevent scenarios where code which doesn't possess certain permissions (e.g. an application downloaded from the Internet) executes code which possesses wider permissions (e.g. from a trusted DLL present on the local machine) in order to gain unauthorised access to protected resources.
Figure 3. Examining the call stack in order to verify permissions possessed by the code Figure 3. this very scenario. Let us look into what happens in subsequent steps:
Implementation SyntaxA programmer implementing CAS-based security mechanisms has at his disposal two methods of expressing them: imperative and declarative one. Certain operations can be performed using either of the methods, while others can be performed only declaratively. Every standard built-in permission in .NET Framework has been defined as a class inheriting from the class CodeAccessSecurity in case of imperative syntax, as well as as a class inheriting from CodeAccessSecurityAttribute in case of declarative syntax. To illustrate this, the file access permission is represented by classes: FileIOPermission and FileIOPermissionAttribute, present in the name space System.Security.Permissions. Declarative syntax is expressed through attributes (classes inheriting from System.Attribute), which extend metadata present in code with information about permissions. Attributes representing permissions inherit from, as mentioned above, the class CodeAccessSecurityAttribute and can refer to an assembly, a structure, a class, a constructor or a method of a class. Each of these attributes possesses the property SecurityAction which allows us to specify the kind of action we want to perform. Here is an example of using declarative syntax in order to verify file access permissions:
In the example we have defined a class called MyClass which possesses a method MyMethod, calls to which succeed only if the code possesses full file access permissions. Imperative syntax involves creation of instances of classes which represent permissions and calling methods which manipulate permissions associated with code. Unlike declarative syntax, imperative syntax allows one to perform a certain operation simultaneously on a group of different permissions. This is possible thanks to classes PermissionSet and NamedPermissionSet, which represent groups of permissions. Using imperative syntax it is impossible, unlike when using declarative syntax, perform the operation of Requesting Permissions, which we will describe later on. Here is an example of using imperative syntax:
In the example we have defined MyClass as a class whose method MyMethod performs some operations, then verifies whether the code possesses full file access permissions and if the condition is met, executes further parts of the method. Operations Performed on PermissionsPermission classes make available three groups of operations on permissions:
Security demands let the programmer define, using declarative syntax, required levels of permissions on a per-assembly basis. Permission requests are stored in the manifest of an assembly as metadata. Having loaded the assembly, created its evidence and, basing on that evidence, granted the assembly appropriate permissions, the CLR verifies whether granted permissions are not lower than the requests stored in the manifest. If the granted permissions are greater than or equal to the requested ones the code gets executed, otherwise it is not executed and a SecurityException is thrown. Such an approach makes it possible to resolve problems related with granted permissions as soon as at the stage of loading the assembly. Imagine a scenario in which our application allows its users to write documents to disc - this means it must possess FileIOPermisssion. If this permission hasn't been granted, the moment an attempt is made by the user to save his document to disc, a FileIOPermission will be thrown as a result of insufficient permissions and our application will be forced to inform the user that his document cannot be saved due to too low permissions. Such behaviour in an application can be tiring and annoying for an user - it is therefore better to specify the minimal set of necessary permissions up front, using permission requests. If the requested permissions aren't granted, the user will receive a message about insufficient permissions at application loading time, thus avoiding frustrating situations while attempting to save a freshly-created document. Defined permission requests of an assembly are also highly valuable for administrators who, using the Permission View Tool (permview.exe), can check minimal permissions for the given assembly and potentially take them into account while preparing a security policy. Furthermore, there is another advantage in requesting permissions - it enables the programmer to drop all granted permissions other than the ones which are necessary for the code to run properly; this approach greatly reduces the risk of exploiting potential flaws in our code in order to gain access to protected resources. We distinguish three types of permissions requests; those three types can be placed simultaneously in a single assembly:
Permission requests are declared through assembly-level permission attributes, with the property SecurityAction set to RequestMinimum, RequestOptional or RequestRefuse. Several examples can be found below. An example of using a Request Minimum which states that the minimal, necessary permissions are full access to the system registry and the ability to read/write files in the folder "c:\temp":
An example of using a Request Optional which states that the only permission that can (but doesn't have to) be granted to the applications is access to the GUI; all other permissions will be dropped!:
An example of using Request Refuse which states that the file access permission will be dropped even if it has been granted by the CLR:
Within requests, the programmer can also refer to built-in permission sets. An example of requesting the built-in permission level "FullTrust":
It is also possible to make references to permission sets defined in XML files:
Overriding permissions - in the beginning of the section devoted to CAS we have described the mechanism of traversing the call stack used by the CLR in order to verify that the code possesses appropriate permissions at the moment of the attempt to access a protected resource. Operations of overriding permissions allow the programmer to "trick" the CLR in an arbitrary place, influencing the mechanism of traversing the call stack and verifying code permissions by setting one of the three special override tags in the stack frame: Assert, Deny or PermitOnly. We cannot override permissions in constructors of classes in which the state of the stack can be ambiguous. Permission-overriding tags work as follows: Assert - presence of the Assert tag for a certain permission - assuming the code using it possesses that permission - enables access to the protected resource even if earlier calls on the stack did not have this permission. Let us return to Figure 2, in which access to the resource in "Call 4" was denied because the CLR, traversing the stack towards its top, reached "Call 2" which lacked the necessary permission U1. Setting the Assert tag for U1 in "Method 3" would cause the CLR to stop traversing the stack at that point - as a result, access to the protected file would be granted even though earlier call on the stack ("Method 2", "Method 1") didn't have the permission U1. Here are a declarative and an imperative example of using
the Assert tag in order to grant full access to a hypothetical log file:
Deny - presence of the Deny tag for a certain permission implies that from the point where the tag has been inserted, access to resources protected by this permission will not be granted even if the code does have appropriate permissions. Here are a declarative and an imperative example of using the Deny tag in order to revoke access to a hypothetical log file:
PermitOnly - an opposite to Deny - implies that from the call the tag has been inserted for, access will be granted only to the resource protected by the given permission. The programmer can clear previously-set overrides by calling one of the four static methods of the class CodeAccessPermission: CodeAccessPermission.RevertAll - clears all defined overrides CodeAccessPermission.RevertAssert - clears all overrides of the type Assert CodeAccessPermission.RevertDeny - clears all overrides of the type Deny CodeAccessPermission.RevertPermitOnly - clears all overrides of the type PermitOnly Security demands - let the programmer declaratively (on a per-assembly, per-class, per-method etc. basis) or imperatively protect certain fragments of code by requirements of possession of certain permissions. If the code does possess these permissions, no action is taken; if it doesn't, a SecurityException is thrown. Security demands can be implemented with the aid of three methods: - Demand - checks with a full scan of the stack - LinkDemand - checks with verification of the last call only - InheritanceDemand - checks of permissions of subclasses An imperative example of protecting a class method:
If at the moment of the call to the method Demand the code doesn't possess full permissions to perform operations on files in the path "d:\", a SecurityException will be thrown. Similarly, a method can be protected in a declarative way:
With LinkDemand, the only difference is that the CLR will not traverse the whole stack in order to verify the permission in question, checking the last call only instead. The method InheritanceDemand allows one to limit inheritance / shadowing of of code, for classes or individual methods, only to classes possessing certain permissions. ConclusionThe security model contained in .NET Framework is a rather complex system, providing an extensive and modular API. The structure of the security systems gives programmers almost unlimited possibilities of extending and modifying its functionality. This article discusses only a small fragment of the issue, a full description of which would be a topic for at least one whole book. A definitely invaluable source of knowledge and information here is the original documentation provided by Microsoft, available on the Web at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconsecuringyourapplication.asp GNU General Public License |
|
| Autor: Tomasz Leszczyński | Nota: |
| Volver | |

