A Programmer's Eye-View of .NET Security Administration

 

Applying .NET Code Access Security is confusing, even if you understand the technology. This article is the first of several articles that attempt to demystify things by using Code Access Security (CAS) to solve concrete problems in software development. The first topic: How do you as a programmer indicate to an administrator in some other computer environment what permissions your application needs. If the administrator cannot set up the permissions properly your software will not run.  All you have to do is annotate the assembly itself to provide this information; you do not need to rely on separate documentation or instructions.

 

The Fundamental Problem

 

How do you actually use Code Access Security in .NET? Understanding the technology itself is not easy. It often seems that you have to understand everything about security before you can do anything. Further, most programmers today do not live in a world where security affects them directly, so it is difficult for them to think about security issues. Yet the elegance and flexibility of .NET makes this ignorance dangerous. You need not do anything to a .NET assembly to make it a component. Unlike the days of COM, or for those of you who go back to prehistoric times, Windows 3.0 (WEP problems, global vs. local allocations, DS != SS, anyone?), there is no involved infrastructure to set up. So it becomes straightforward to take any .NET assembly and use it through .NET remoting or expose it as a Web service. You cannot assume that you are developing software that will work on a single machine. You have to assume that your technology will be exposed to the outside world, and that means exposing it to hackers.

 

Code Access Security (CAS) helps deal with this problem by allowing you to place limitations on the code itself irrespective of the user id under which the code runs. A previous series of articles on .NET security[1] explained the fundamentals. What I would like to do is explain how to use CAS to solve concrete problems. Of course in the process of doing this we will go beyond what has already been explained. Keep two caveats in mind as we do it. First, .NET Security resides on top of Windows Security so that you still have to pay attention to issues such as ACLs and restricted tokens.[2] Second, in order to make the exposition clear we avoid certain complications that I will discuss in subsequent articles.

 

The first problem I want to address is: How does a developer ensure that an administrator, whom they will never meet, understands what privileges their software needs?

 

Principle of Least Privilege

 

One simple way to solve this problem is to make sure that your software has all the privileges that could be granted on a machine. This is dangerous. Software should only be granted the minimal set of rights that it needs to run - no less, no more. One never can be completely sure that software is immune from security flaws. Your code, or software that uses your code could be hacked. Restricting your code's rights minimizes the chances it could be exploited to do things you did not intend.

 

For example, IRS examiners have the right to look at tax returns so they can do their audits. They can, however, only look at the returns they are investigating. They are not allowed to look at any tax return they want. This idea is called the Principle of Least Privilege and has been known to the security community since at least 1975.[3]

 

Code Access Security gives administrators the ability to prevent software that is not trusted from doing certain actions (such as opening a file or connecting to the Internet). Hence, developers must determine what CAS security permissions the software absolutely needs to run with, and what permissions your software would like to have, but could live without. If you do not you, might find out that your software is unable to run. You should also figure out what permissions you do not want, even if they were given to you. This will help prevent your software from being used as part of a security attack.

 

So when you know what permissions your software needs, how do you communicate this information to the administrator?

 

Declarative Security For Assemblies

 

You can request security permissions in one of two ways: imperatively or declaratively. An imperative request is done in code using the Demand method on the appropriate security permission class. The SimpleDemand example that accompanies this article illustrates this with the request to have the rights to display any User Interface window.[4]

 

...

UIPermission perm;

perm=new UIPermission(UIPermissionWindow.AllWindows);

perm.Demand();

...

 

If the call fails a SecurityException is thrown. If you call the Deny method on the permission class instead, the attempt to put up a message box will fail[5]. The advantage to declarative security is that you control when the request is made and where the possible failure point is. However, an administrator has no way of knowing what permission requests are being made.

 

Using declarative security you can place your permission requests in the metadata of the assembly by adding attributes to classes, methods, or the assembly itself. The metadata can be read so that the administrator can find out what permission requests are being made. The .NET Framework ships with a tool permview.exe that does exactly that. The SecurityAttribute example uses an attribute to make the same request as the previous example. When using declarative security, the requests are referred to as security actions.

 

UIPermission(SecurityAction.Demand, Window =

                   UIPermissionWindow.AllWindows)]

Public static void Main()

...

 

Again, if the Demand action is changed to a Deny action, the attempt to put up a message box will fail. Running the check.bat file (in the Debug directory) will demonstrate the permview utility's ability to display the declarative security requests. In general, the security attribute classes are functionally equivalent to the imperative demand. For most cases a declarative security demand on a method is performed just before the method is executed.[6] A security attribute on a class generally applies to every method in the class.[7]

Using Attributes in Declarative Security

 

The arguments supplied to an attribute come from two places. The first set of arguments is from one of the attribute class' constructors. The second set of arguments represents public properties.

 

The UIPermissionAttribute class (the C# compiler allows you to omit the Attribute) , as most of the security attribute classes, has a constructor that takes a SecurityAction argument. It also has two properties, Windows and Clipboard, which can be set after the constructor arguments. You done not have to set them all, or even set any one of them.

 

 

 

What is really valuable, however, is to be able to provide metadata to help in setting up the appropriate security policy. In other words, to determine if your application can run in the environment under the administrator's control. To this end, there are three security actions that apply to the assembly: RequestMinimum, RequestOptional, and RequestRefuse.

 

RequestMinimum actions specify permissions that are necessary for the assembly to function. If the security policy for the environment is to deny these permissions, the assembly is not loaded, and a PolicyException is thrown. By default, no permissions are required. RequestOptional actions specify permissions that are not essential for the assembly to function. Some functionality might be missing, but the assembly can provide basic services. RequestRefuse actions specify permissions that the assembly does not want. Given the number of permissions needed, it might be easier to specify want is not wanted than want is needed. Also certain permissions can be explicitly refused to avoid the possibility of an assembly being used in ways not foreseen because of a security flaw.

 

The AssemblyMetadata example demonstrates the use of these attributes:

 

[assembly:SecurityPermission(SecurityAction.Request

Refuse, UnmanagedCode = true)]

[assembly:FileIOPermission(SecurityAction.Request

Optional, Unrestricted = true)]

[assembly:UIPermission(SecurityAction.RequestMinimum,

Window = UIPermissionWindow.AllWindows)]

 

Running check.bat will use permview to display the various permissions for each category.  At this point the administrator can then decide if your assembly can run under the existing security policy or change the policy to allow the assembly to run.[8] Remember that to avoid luring attacks, not only does your assembly need to have the necessary permissions, but also every assembly above you needs them same permission. Hence, you should always be prepared to handle a SecurityException. Of course you can assert a permission, but that potentially opens  up a big security hole.  I can discuss assertion of permissions in a future article, but I want to note that at the minimum there should be a security code review for every use of an assertion.

 

If you specify a RequestRefuse assembly attribute, your code will be denied those permissions. For example, if you change the Security Action of the UIPermission in the above example from RequestMinimum to RequestRefuse, the code will not run. The behavior of RequestMinimum and RequestOptional depend on the Security Policy settings for your assembly.[9] For example, if the policy that applies to the AssemblyMetadata assembly grants use of All Windows for the User Interface permission, then there is no difference between RequestMinimum and RequestOptional actions. If that permission is denied, then the RequestMinimum will cause an immediate PolicyException, but a RequestOptional will not throw a SecurityException until the untrusted action is attempted.

 

Administrator Rights, Open Security Policy, and Security Problems

 

One of the problems in applying the principle of least privilege is that programmers often run as administrator on their own machines when they develop software. You get in the habit of writing programs that assume you have all privileges and permissions. Get in the habit of using a user id that has the same set of privileges, and an environment that has the security policy that your customers will have.

 

If for some reason you have to run some program as an administrator (installation programs are often notorious for this),  you can hold down the shift key and right mouse-click to select the program. A context menu with an item labeled "Run As..." will appear. [10] Select it and login in with the appropriate id. The program will then run as that user.

 

This article was written, and the programs developed, as a user without administrator privileges. Stop programming as administrator. Friends don't let friends program as administrator.

 

Michael Stiefel is the principal of Reliable Software, Inc. where he does training and consulting in Microsoft technologies for companies ranging from startups to the Fortune 500. He is the co-author of "Application Development Using C# and .NET" published by Prentice-Hall. Go to www.reliablesoftware.com for more information about his development work and the training courses he offers.



[1] See Tarak Modi, .NET Security Explained: The Basics, and .NET Security Explained, Part 2: Code Access Security in previous issues.

[2] For more information about Windows security issues see; Michael Howard and David LeBlanc, Writing Secure Code,  Microsoft Press.

[3]See John Viega and Gary McGraw, Building Secure Software. For more information about the Principle of Least Privilege, see pages 100-102.

[4] The accompanying examples are given in both C# and VB.NET. The discussion in the text uses C#.

[5] This can be slightly confusing in as much as we are requesting a denial.

[6] The exceptions are the LinkDemand and InheritanceDemand actions.

[7] The exception is the InheritanceDemand action.

[8] The two previously mentioned articles by Modi discuss this.

[9] See Tarak Modi, .NET Security Explained, Part 2: Code Access Security for an explanation of how to setup policy.

[10] This works on Windows 2000 and XP Professional.


All Content (c) 2000 - 2014 Reliable Software, Inc. All rights reserved.