A familiar security saying goes: “A system is as secure as its weakest link.” For example, you might implement full-blown, high-level security features in your application, but if somebody could simply access the physical database file directly, all the work you’ve spent on developing security might be in vain.
One of the fundamental security best practices is that you shouldn’t rely on a single, big security barrier alone. Instead, it is often wiser to build security on different layers, and the previously mentioned database security is a great example. Unless you secure the database file itself, your application cannot be considered very secure.
Especially in complex IT environments, figuring correct file level security settings can be a tough job. Furthermore, actually applying and maintaining those settings can be time consuming. But with automation, the administrator can save hours and hours of work—and automation is something developers enjoy doing. Before checking what C# and the .NET Framework can do to help you secure your file systems, see what basic security options Windows gives.
File Access Control Lists
Especially with networked file servers and their file shares, you might have run into the Windows error message “Access denied.” Although file shares also have their own security settings, most probably these errors result because somebody, likely the administrator, has restricted the user’s access to certain files or folders.
In Windows, the NTFS file system supports file-level security settings; FAT-based file systems are out of luck. Simply put, these settings allow the creator of a given file (or the administrators) to specify who can access that file, and what can be done with it. For example, an administration might set file security so that Marketing can access certain files, but Sales cannot. This is called discretionary access control.
Figure 1 shows an example of security settings associated with a folder in Windows. With the Security tab, users with proper rights can precisely control in what way users and groups can access, modify, and delete files and folders. In Windows, these file-level security settings are one of the most low-level security settings that can be made. This means that you should build other security-related features on top of file-level security.
Figure 1: The Security tab in the Windows folder properties shows the access control entries associated with the object.
Technically speaking, file-level security settings are stored in so-called access control lists, or ACLs for short. Each ACL can contain zero or more (yes, and empty ACL is considered valid) access control entries, or ACEs. An ACE contains a security identifier (or SID) of a user or a group, entry type (access allowed or access denied), and a mask specifying which operations are either allowed or denied, depending on the entry type.
For example, a typical ACE might say that modifying a certain file is allowed for Jane, but denied from Peter. Although simple in concept, in the real world setting up access control lists isn’t exactly as simple because you can make access control inheritable from parent directories. Similarly, the exact order of access control entries in the list is important. Luckily, the .NET Framework takes reasonably good care of the developer and modifying file security is much easier than it used to be when using the older Win32 API directly.
An Example of .NET’s Support for File Access Control
When the .NET Framework 2.0 came along, it contained support for file access control lists built-in. In previous framework versions (namely 1.0 and 1.1), such support didn’t exist, and you needed to use P/Invoke to call the operating system APIs directly.
Beginning with .NET 2.0, access control lists are represented using objects, most of which are defined in the System.Security.AccessControl namespace. In the case of files, the class named FileSecurity represents file ACLs. Getting a FileSecurity object instance is easy. You can call the static GetAccessControl method of the File class in the System.IO namespace, like this:
using System.IO; using System.Security.AccessControl; ... string filename = @"C:\MyFolder\My File.txt"; FileSecurity security = File.GetAccessControl(filename);
With the FileSecurity object, you are able to read and modify access control entries associated with the given file. For example, the GetAccessRules method returns a collection of access control entries (or actually, objects that represent them) that match the given parameters. For example, Windows allows access control lists to inherit from parent directories, and when you call the GetAccessRules method, you need to select whether you want to include such entries as well, for instance.
The FileSecurity object also supports multitude of other features, such as controlling audit policies (technically system ACLs), and adding, editing, and deleting (discretionary) access control entries, which are the focus of this article.
To enumerate access control entries inside a FileSecurity object, you would call its GetAccessRules method. This method returns an AuthorizationRuleCollection object, which, as the name implies, is a collection all the access rules (access control entries) for the given file. Calling the GetAccessRules method requires two boolean values (to indicate whether explicit and inherited ACEs should be included) and a target type.
Of these parameters, the type is the most difficult one to specify; therefore, an explanation is in order. You might be aware that Windows uses SIDs or security identifiers to uniquely identify users or groups. SIDs are often represented as alphanumeric strings. For example, the SID string “S-1-1-0” specifies “Everybody”. However, it might be easier for users to manipulate user accounts or groups with their names, for instance, “Administrator” or “Backup Operators”.
The third parameter of the GetAccessRules method requires the aforementioned target type. This type specifies whether you want to read the actual SID values, or the account objects (and their names). Thus, you can either give the method the SecurityIdentifier type or the NTAccount type, respectively. Both these classes live in the System.Security.Principal namespace.
Retrieving ACE Information
To retrieve ACE information from a given file, use code similar to this:
FileSecurity security = File.GetAccessControl(filename); AuthorizationRuleCollection acl = security.GetAccessRules( true, true, typeof(System.Security.Principal.NTAccount)); foreach (FileSystemAccessRule ace in acl) { // Do something with the ACE here... }
When you call the GetAccessRules of the FileSecurity class, the returned AuthorizationRuleCollection contains FileSystemAccessRule objects. An example application that is able to display there entries is shown in Figure 2. The application uses the following code to construct string-based dump of the given ACE object:
private string GetAceInformation(FileSystemAccessRule ace) { StringBuilder info = new StringBuilder(); string line = string.Format("Account: {0}", ace.IdentityReference.Value); info.AppendLine(line); line = string.Format("Type: {0}", ace.AccessControlType); info.AppendLine(line); line = string.Format("Rights: {0}", ace.FileSystemRights); info.AppendLine(line); line = string.Format("Inherited ACE: {0}", ace.IsInherited); info.AppendLine(line); return info.ToString(); }
Figure 2: The sample application is able to retrieve file access control lists and display them.
The properties of the FileSystemAccessRule class allow you to read whether the ACE is an allowing or denying ACE, who is (or are) affected by the setting, and finally the rights that are affected. If you are interested in SID values, identical code works fine, except that you must give the SecurityIdentifier type as the third parameter to the GetAccessRules method.