.NET component-oriented security is based on an
elegant concept: using an administration tool, the system administrator
grants assemblies certain permissions
to perform operations with external entities such as the filesystem,
the Registry, the user interface, and so on. .NET provides the system
administrator with multiple ways to identify which assembly gets
granted what permission and what evidence the assembly needs to provide
in order to establish its identity. At runtime, whenever an assembly
tries to perform a privileged operation or access a resource, .NET
verifies that the assembly and its calling assemblies have permission
to perform that operation. Although the idea is intuitive enough, there
are a substantial number of new terms and concepts to understand before
configuring .NET security for your own applications. The rest of this
section describes the elements of the .NET security architecture. The
next sections describe how to administratively configure security and
take programmatic control over security.
1. Permissions
A permission is a grant to perform a specific operation. Permissions have both a type and a scope.
A file I/O permission is different from a user-interface permission in
type because they control access to different types of resources.
Similarly, a reflection permission is different from an unmanaged code
access permission because they control the execution of different types
of operations. In scope, a permission can be very narrow, wide, or
unrestricted. For example, a file I/O permission can allow reading from
a particular file, while writing to the same file may be represented by
a different file I/O permission (narrow scope). Alternatively, a file
I/O permission may grant access to an entire directory (or a drive), or
grant unrestricted access to the filesystem. .NET defines 25 types of
permissions that govern all operations and resources an application is
likely to use (see Table 1).
Of
particular interest is the Security permission type, which controls
both sensitive operations and security configuration. The list of
privileged operations includes execution, invocation of unmanaged code,
creating and controlling app domains, serialization, thread
manipulation, and remoting configuration. The security configuration
aspect includes permission to assert granted permissions; skip assembly
verification; control security policies, evidences, and principals; and
extend the security infrastructure. These facets are described later.
.NET permissions are subject to the underlying Windows or resource security permissions.
For example, if the filesystem is NTFS, it can still deny an
application access to a file if the identity under which the
application is running isn't granted access to that file. Other
examples are when accessing user-specific environment variables and
when accessing a SQL server that has its own security policy. |
|
Table 1. Security permission typesPermission type | Grants permission to | Example |
---|
ASP.NET Hosting | Host ASP.NET objects. Defines several levels. | Minimal level permission is required for using the ASP.NET authentication and authorization classes. | Data Protection | Use the ProtectedData class to protect or unprotect data and memory. | Encrypt a memory block using ProtectedData.Protect( ). | Directory Services | Access Active Directory. Allows browsing a path or writing to it. | Browse all content under LDAP://. | Distributed Transactions | Create a new distributed transaction. | Unrestricted use of distributed transactions. | DNS | Domain-name servers. Permission is required to resolve URLs at runtime. | Deny access to or grant unrestricted access to DNS. | Environment Variables | Read or write the value of specific environment variables. | Write the PATH environment variable. | Event Log | Write, browse, or audit an event log on a specified machine. Can also deny access to event log. | Browse the event log on localhost. | File Dialog | Display the common dialogs used to open or save files, or deny permission to display the dialogs. | Display the File Save dialog. | File I/O | Read, write, or append data to a file, or all files in a directory. Grants path-discovery permission as well. | Write to c:\temp\Myfile.txt. | Isolated Storage | Allow or disallow administration; configure isolation policy and disk quotas. | Allow administration of isolated storage by the user of the assembly and allocate a disk quota of at most 10 KB. | Key Container | Create and delete key containers; export and import keys; sign, open, or encrypt containers. | Import an existing key from a container. | Message Queue | Browse, peek, send, or receive messages from a specified message queue. Allow queue administration as well. | Grant unrestricted access to all message queues. | OLE DB | Access specified OLE DB providers. Can specify whether a blank password is permitted for all providers. | Grant access to the Microsoft OLE DB provider for SQL Server. | Performance Counter | Browse or instrument specified performance counters on designated machines. | Instrument the thread performance counter on the current machine. | Printing | Print (either in safe mode, default mode, or all modes). | Allow all printing operations to all accessible printers. | Reflection | Discover member and type information about other assemblies using reflection. Emit code at runtime. | Allow reflection of both type and member information on other assemblies but deny emission of new code at runtime. | Registry | Read, write, or create Registry keys. | Read the values stored under HKEY_LOCAL_MACHINE\SOFTWARE\. | Security | Control various security aspects. | Allow unmanaged code access. | Service Controller | Control or browse services on specified machines. | Control (start and stop) the fax service on the local machine. | Socket Access | Accept connections on or connect to specific ports on specified machines using either TCP or UDP (or both). | Allow connecting and accepting calls on port 8005 using TCP on the local machine. | SQL Client | Access SQL servers using ADO.NET, and specify whether a blank password is permitted. | Allow unrestricted access to all SQL servers available on the intranet. | Store | Create
and delete certificate stores, enumerate existing stores, enumerate
certificates in a store, add or remove a certificate from a store. | Permission to add a certificate to a store. | User Interface | Interact
with the user using all top-level windows and events, safe top-level
windows, safe subwindows, or no windows at all. Control access to the
clipboard. | Allow displaying all windows but disallow clipboard access. | Web Access | Allow connecting to or accepting requests from specified web hosts. | Allow invoking a particular web service. | Web Browser | Render
content in the web browser Windows Forms control. Can be unrestricted
or restricted to rendering only simple HTML (that is, without ActiveX,
HTML scripts, Java applets, or other potentially unsafe operations). | Allow restricted use of the web browser control. |
2. Permission Sets
Individual
permissions are just that—individual. To function properly, a given
assembly often requires a set of permissions of particular scope and
type. .NET allows system administrators to use permission sets,
or collections of individual permissions. A permission set can contain
as many individual permissions as required. Administrators can
construct custom permission sets, or they can use pre-existing,
well-known permission sets. .NET provides seven predefined permission
sets, also known as named permission sets: Nothing, Execution, Internet, LocalIntranet, Everything, FullTrust, and SkipVerification. Table 2 presents the individual permissions granted by each named permission set.
Table 2. The named permission setsPermissions | Nothing | Execution | Internet | LocalIntranet | Everything | FullTrust | SkipVerification |
---|
ASP.NET Hosting | | | | | | Unrestricted | | Data Protection | | | | | Unrestricted | Unrestricted | | Directory Services | | | | | | Unrestricted | | Distributed Transactions | | | | | | Unrestricted | | DNS | | | | Unrestricted | Unrestricted | Unrestricted | | Environment Variables | | | | Read USERNAME | Unrestricted | Unrestricted | | Event Log | | | | | Unrestricted | Unrestricted | | File Dialog | | | File Open | Unrestricted | Unrestricted | Unrestricted | | File IO | | | | | Unrestricted | Unrestricted | | Isolated Storage | | | Domain isolation by user with 10 KB disk quota | Assembly isolation by user, unrestricted disk quota | Unrestricted | Unrestricted | | Key Container | | | | | Unrestricted | Unrestricted | | Message Queue | | | | | | Unrestricted | | OLE DB | | | | | Unrestricted | Unrestricted | | Performance Counter | | | | | Unrestricted | Unrestricted | | Printing | | | Safe printing | Default | Unrestricted | Unrestricted | | Reflection | | | | Emit | Unrestricted | Unrestricted | | Registry | | | | | Unrestricted | Unrestricted | | Security | | Execution | Execution | Execution and assertion | All, except skip verification | Unrestricted | Skip code-safety verification | Service Controller | | | | | | Unrestricted | | Socket Access | | | | | Unrestricted | Unrestricted | | SQL Client | | | | | Unrestricted | Unrestricted | | Store | | | | | Unrestricted | Unrestricted | | User Interface | | | Safe top-level windows, clipboard ownership | Unrestricted | Unrestricted | Unrestricted | | Web Access | | | | | Unrestricted | Unrestricted | | Web Browser | | | Restricted | Restricted | Unrestricted | Unrestricted | |
The named permission sets offer a spectrum of trust:
The Nothing permission set Grants
nothing. Code that has only the Nothing permission set can't execute,
and .NET will refuse to load it. The Nothing permission set is used
when there is a need to prevent assemblies from running, typically
because the code origin is known to be untrustworthy and dangerous. For
example, the default .NET security policy associates any code coming
from the list of untrusted sites (maintained by Internet Explorer) with
the Nothing permission set, effectively preventing such code from
causing any harm.
The Execution permission set Allows
code to load and run, but doesn't permit interaction with any kind of
external resource and doesn't perform any privileged operations. When
an assembly is assigned the Execution permission set (but nothing
else), the assembly can perform operations such as numerical
calculations, but it can't save the results. By default, .NET does not
use the Execution permission set.
The Internet permission set Gives
code some ability to execute and display a user interface, so should be
used carefully. Generally, you shouldn't trust code coming from the
Internet unless the site of origin is explicitly trusted. Note that the
default .NET security policy grants the Internet permission set to all
code coming from the Internet. Administrators can change that and
explicitly assign the Internet permission set to only selected trusted
sites.
The LocalIntranet permission set Code
coming from the local intranet is, of course, more trustworthy than
code coming from the Internet. As a result, the LocalIntranet
permission set grants code wide permissions. .NET's default associates
the LocalIntranet permission set with code originating from the local
intranet.
The Everything permission set Grants
code most permissions except for directory services, distributed
transactions, message queue, service control, ASP.NET hosting, and
permission to skip verification. The lack of permission to skip
security verification means that the code must be verifiable. Verifiable code
is code that can be verified in a formal manner as type-safe.
CLR-compliant compilers can emit unverifiable code, such as unsafe code
in C#. You can use the Everything permission set to ensure that the
managed code invoked has all the permissions required for normal
operation and yet it doesn't use techniques such as pointer arithmetic
to access restricted memory areas or areas owned by other app domains
(more on that at the end of the chapter). By default, .NET doesn't use
the Everything permission set.
The FullTrust permission set Allows
unimpeded access to all resources. .NET trusts such code implicitly and
allows it to perform all operations. Only the most trustworthy code
should be granted this permission, because there are no safeguards. By
default, all code executing from the local machine is granted full.
The SkipVerification permission set Grants
a single permission—permission to skip code-safety verification. You
can use the SkipVerification permission set to explicitly allow
unverifiable code, without risking it touching any external resources
or performing sensitive operations. For example, imagine porting legacy
C or C++ code to C#, when the legacy code uses complex pointer
arithmetic. In that case, it may be easier to keep that pointer
arithmetic in place using unsafe code than to fully rewrite safer C#.
It's overkill to grant that code FullTrust permissions; instead, grant
it the SkipVerification permission set and any other specific
permissions it may require. Granting assemblies only the minimum
permissions they require is a good guideline, because it reduces the
chances of damage caused by a malicious party luring a benign assembly
to do dirty work on its behalf.
Component-based security obviously has a lot to do with code origin—
that is, where the code is coming from. Code origin has nothing to do
with remote calls, because the remote object executes locally on the
remote machine. Code origin is relevant only when loading an assembly
from a remote location. You can load a remote assembly in a number of
ways. First, you can have the application indicate in its code-binding
policy that it requires an assembly from another machine (by specifying
the machine name), or perhaps that the assembly is coming from a
network-mapped drive. Applications can also programmatically load an
assembly at runtime from a remote location using the static method LoadFrom( ) of the Assembly class:
public static Assembly LoadFrom(string assemblyFile);
For example:
Assembly assembly; assembly = Assembly.LoadFrom("\\SomeMachine\MyAssembly.dll");
That said, by far the most common case of loading an assembly from a remote location is using ClickOnce
deployment. As you will see later on in this chapter, a few features of
the security infrastructure and Visual Studio 2005 are dedicated for
the use of ClickOnce applications.
|
3. Security Evidence
System
administrators grant permissions to assemblies based on the assembly's
identity. The question is, what sort of evidence should an assembly
present to .NET in order to establish its identity? A security evidence
is some form of proof that an assembly can provide to substantiate its
identity. Evidences are vital for .NET security, because without them
rogue assemblies can pretend to be something they aren't and gain
unauthorized access to resources or operations. There are two types of
evidences: origin-based and content-based evidences. An origin-based evidence
simply examines where the assembly is coming from and is independent of
the actual content of the assembly. The standard origin-based evidences
are Application Directory, GAC, Site, URL, and Zone. A content-based evidence
examines the content of the assembly, looking for a specific match with
specified criteria. The standard content-based evidences are Strong
Name, Publisher, and Hash. There is no relationship between permission
sets and evidences. A single assembly can be granted multiple
permission sets and satisfy a different evidence for each permission
set, or it can satisfy the same evidence associated with multiple
permission sets. .NET also defines a wildcard—the All Code evidence. Here is a description of the available evidences and how to select an appropriate security evidence.
3.1. The All Code evidence
The All Code evidence is satisfied by all assemblies.
3.2. The Application Directory evidence
The
Application Directory evidence is satisfied by all assemblies coming
from the same directory as or a child directory of the running
application. Typically, this evidence allows an application to trust
code deployed together with it but distrust other code on the same
machine or anywhere else.
3.3. The GAC evidence
The
GAC evidence is satisfied by all assemblies originating from the GAC.
Because only an administrator can install assemblies in the GAC, the
GAC evidence is used to implicitly demand that whoever installed the
evaluated assembly was an administrator. Assemblies that satisfy the
GAC evidence are somewhat more trustworthy than assemblies installed by
non-administrators, but to what degree is a question of judgment.
3.4. The Site evidence
The Site evidence is satisfied by all assemblies coming from a specified site, such as http://www.somesite.com or ftp://www.somesite.com.
The protocol (and port number, if specified) is ignored, and only the
top-level domain portion is used. .NET also ignores any subsite
specifications, such as the /myfolder in http://www.somesite.com/myfolder, and extracts the domain name only. Sites can also point to a specific machine, as in tcp://somemachine/myfolder.
3.5. The URL evidence
The
URL evidence is satisfied by all assemblies coming from a specified
URL. The URL evidence is more specific than the Site evidence, because
.NET takes into account protocol, port number, and subfolders. For
example, the following are considered different URL evidences but
identical Site evidences:
You can use an asterisk at the end of a URL to indicate that the URL evidence applies to all code coming from a sub-URL as well:
- http://www.somesite.com/*
3.6. The Zone evidence
The Zone evidence is satisfied by all assemblies coming from the specified zone. .NET defines five zones:
The My Computer zone Identifies code coming from the local machine.
The Local Intranet zone Identifies
code coming from machines on the same LAN. The local intranet is any
location identified by a universal name convention (UNC), usually in the form of \\<machinename>\<further scope> (e.g., \\Somemachine\SomeSharedFolder).
You can also identify a location as part of the Local Intranet zone
using a URL, as long as the URL doesn't contain dots (e.g., http://Somemachine\SomeSharedFolder or tcp://Somemachine\SomeSharedFolder). Note that even if you specify your own local machine name (e.g., \\MyMachine or http://localhost),
it will be considered part of the Local Intranet zone, not the My
Computer zone. Network-mapped drives are also considered part of the
Local Intranet zone.
The Internet zone Identifies
code coming from the Internet. The Internet is considered as any
location identified by a dotted or numeric IP address, such as http://www.somesite.com or http://66.129.71.238. Note that by default, even if the URL points to a location on the LAN (including the local machine), such as http://127.0.0.1, it's still considered part of the Internet zone.
If
you wish to include local intranet sites as part of the local intranet
but refer to them using a generic Internet dotted or numeric URL, you
need to add those sites explicitly to the intranet site list. To do so,
open Internet Explorer and display the Security tab on the Internet
Options dialog. Select the Local Intranet icon, and click the Sites
button (see Figure 12-1).
In the Local Intranet dialog, click Advanced, and add web sites to this
zone. You can add even non-intranet web sites to the Local Intranet
zone. |
|
The Trusted Sites zone Identifies code coming from a list of trusted Internet sites. You can add sites to and remove sites from the Trusted Sites list
using Internet Explorer—go to the Security tab on the Internet Options
dialog, select the Trusted Sites icon, and click Sites (see Figure 1) to display the Trusted Sites dialog.
The Untrusted Sites zone Identifies
code coming from a list of untrusted Internet sites. You can add sites
to and remove sites from the Untrusted Sites list using Internet
Explorer, similarly to adding sites to and removing sites from the
Trusted Sites list.
When
you add sites to the lists of trusted and untrusted sites using
Internet Explorer, the lists are maintained per user, not per machine.
There is no easy documented way to add sites to a machine-wide list. |
|
3.7. The Strong Name evidence
The
Strong Name evidence is satisfied by all assemblies whose public keys
match a specified key value. The Strong Name evidence is an excellent
way to trust all code coming from a particular vendor, assuming the
vendor uses the same public key to sign all its assemblies. The Strong
Name evidence can optionally contain the assembly names and/or version
numbers. As a result, the system administrator can opt to trust only a
particular version of a specific assembly coming from a particular
vendor identified by a public key. That said, the name and version
typically are not supplied—doing so implies that a vendor can be
trusted with only that particular assembly or version, which is
conceptually inconsistent with the notion of a trustworthy vendor.
3.8. The Hash evidence
The
Hash evidence grants the permission set associated with it to the
single assembly whose computational hash matches a specified hash. The
assembly in question need not have a strong name. As a result, the Hash
evidence is useful only for uniquely identifying an assembly with a
friendly name and granting permissions only to that trusted assembly.
The Hash evidence is the most stringent form of evidence, but it is
also the most maintenance-intensive—you need to update it on every
revision of the assembly. The Hash evidence can be used to detect
changes to an assembly, even if the new assembly has the same strong
name and version number. System administrators can configure which
cryptographic hashing algorithm to use: either SHA1 (the default) or
MD5.
3.9. The Publisher evidence
The
Publisher evidence is satisfied by all assemblies that are digitally
signed with a specified certificate, such as AuthentiCode. To digitally
sign an assembly with a certificate, first build it, and then use the SignTool.exe command-line utility, specifying the assembly to sign and the file containing the digital certificate. SignTool.exe can optionally launch a wizard to guide you through the signing process.
3.10. Selecting a security evidence
Choosing
a security level to apply always involves a trade-off between risks and
usability. In general, you should prefer content- to origin-based
evidence, because content-based evidence is more accurate. For example,
the Strong Name evidence safely and consistently identifies an
assembly. With origin-based evidence such as the Site evidence, the
same assembly may be trusted if it comes from one site but not trusted
if it comes from a different site. In addition, origin-based evidence
is more susceptible to subversion than content-based evidence. It's
next to impossible to fake a strong name, but it's possible to fool
your machine into thinking that a certain IP address maps to a trusted
site by subverting the DNS server. Another breach of origin-based
evidence is compromising the proxy server on the local network so that
instead of returning an assembly from a trusted site, it returns a
different assembly but makes it look like it came from a trusted site.
You should do a careful threat analysis, and trust origin-based
evidences only as far as the DNS and other network facilities can be
trusted. Origin-based evidences let you interact with much wider sets
of assemblies than content-based evidences, which require individual
configuration. Origin-based evidences also let you apply a generic
security blanket, without having intimate knowledge of the assemblies
on the client machine; content-based evidences, on the other hand,
require you to be intimately aware of the making of the assemblies.
Consequently, origin-based evidences are often used by framework
developers (such as Microsoft), while application developers favor
content-based evidences.
4. Code Groups and Security Policies
.NET uses code groups to classify assemblies when it decides on the security permissions granted for each assembly. A code group is a binding of a single permission set with a particular evidence (see Figure 2).
To
be granted the permissions in the permission set associated with the
code group, an assembly must first satisfy the evidence of that code
group. However, a meaningful security policy needs to be much more
granular than what a single code group with a single evidence and
permission set can express. A .NET security policy is a collection of code groups.
Code groups in a policy are independent of one another in every
respect. They can all use the same evidence, different evidences, or a
mix. Similarly, different code groups can use different or identical
permission sets. The permissions granted by a policy to a given
assembly is the union of all the individual permissions granted by the
evaluated code groups in that policy whose evidence the assembly
satisfies. For example, consider the security policy in Figure 3.
In this figure, the assembly satisfies the evidences of code groups A,
B, and C, but not the evidences required by code groups D and E. As a
result, the assembly will be granted only the union of the permissions
granted by code groups A, B, and C.
4.1. Combining policies
.NET
allows administrators to provide multiple security policies. The
benefit of having multiple security policies is that it enables
policies to have different scopes. Some policies can be restrictive and
should be applied only in specific cases, such as with individual users
or machines with limited privileges. Some policies can be more
permissive and apply to all machines and users in an organization.
Therefore, it's quite possible that an assembly is granted some
permissions by one policy but is denied the same permissions by another
policy. Because all the policies must concur on the allowed
permissions, the actual permissions granted to an assembly are the
intersection of all the permissions granted by all the security policies (see Figure 4).
4.2. Policy levels
In
actuality, there are only four types (or levels) of security policies,
and .NET is aware of these four levels. Although technically
administrators can configure these policy levels in any way, the
convention is to use them according to their intent. The Enterprise policy should define a policy that affects all machines in the enterprise. Each machine should have a Machine policy defining a policy specific to that machine, and the User policy
should define a policy for each individual user. The system
administrator configures these three policy levels. The last policy
level is the Application Domain policy,
which applies only to code running in a specific application domain.
You can only configure the Application Domain policy programmatically,
by calling the SetAppDomainPolicy( ) method of the AppDomain
class. Customizing the Application Domain policy is primarily for
advanced cases—for example, for creating an app domain with
deliberately low permissions so that you can load untrusted code into
that domain. The default Application Domain policy grants all code full
trust. Tool vendors can also take advantage of the App Domain policy.
For example, Visual Studio 2005 supports partial-trust debugging, as
described later in this chapter. Partial-trust debugging relies on
installing a custom App Domain security policy. (App Domain security
policies are beyond the scope of this chapter. For additional
information, see the MSDN Library.)
System
administrators typically take advantage of the hierarchical nature of
the policy levels, placing policies that are more restrictive
downstream and the more liberal policies upstream. This allows overall
flexibility with granular security
policies, tight in some places and looser in others. For example, the
Enterprise policy is likely to contain only the known, must-be-blocked
web sites or vendors. Other than that, the Enterprise policy can be
very liberal, permitting all other operations and zones. Individual
machines can be restricted if necessary, via the Machine policy. For
instance, a development machine can have more permissions
than a public machine in a reception area. Similarly, some users (such
as system administrators) can have liberal, if not unrestricted, User
policies, while non-technical staff can have very restricted User
policies, even if they all share the same machine.
5. How It All Works Together
When
.NET loads an assembly, it computes the permissions that assembly is
granted: for each security policy, .NET aggregates the permissions from
the evaluated code groups satisfied in that policy, and then .NET
intersects the policies to find the combined overall collection of
permissions the assembly is granted. That set of permissions is
calculated only once (per app domain), and it persists in memory for as
long as the assembly remains loaded. Whenever an assembly invokes calls
on one of the .NET Framework classes (or any other class, including
your own, as explained later), that class may demand from .NET that the
assembly calling it have the required security permissions to access
it. For example, the file I/O classes demand appropriate file I/O
permissions, and Windows Forms applications demand user interface
permissions. If the assembly doesn't have the appropriate security
permissions, a security exception is thrown. However, it isn't
sufficient that the assembly that called the demanding class has the
requested permissions—if .NET were to check for permissions only on the
assembly immediately up the call chain, that could constitute a
security breach. Imagine a malicious assembly that doesn't have the
required permissions to access a class such as FileStream.
That assembly could work around the lack of permissions by calling
instead a benign assembly that has the permissions to do its dirty work
for it. Therefore, whenever a class demands security permission checks,
.NET traverses the entire call stack, making sure that every assembly
up the call chain has the required permissions. This is known as the
security permission stack walk.
When the first assembly without permissions is found during the stack
walk, .NET aborts the stack walk and throws an exception at the point
where the original security demand took place.
If an exception is raised because of lack of a security permission, you can find which permission is missing by examining the PermissionType property of the SecurityException object. Other useful properties of SecurityException include which method demanded the security permission (the MethodFailedAssemblyInfo property), and the origin of that assembly (the Zone and Url properties). property), the assembly that failed the call (the |
|