I have been tasked something special lately. To fulfill a compliance review, a client needed to check the permissions on the folders on a server. We started by looking around for tooling but the one we found were just generating a long list way too long to be understandable (tools like AccessEnum from Microsoft published in 2006!). In addition to satisfy the compliance requirements, he wanted to understand how shared folders are accessed. He wanted to be sure that not everyone connecting to the server had access to some folders (like employees and clients information). He wanted something that would enable him to get the current state as well as any changes from a previous run to quickly find what has changed instead of going through the full list once again (because those who tried to generate that kind of list knows it can be many thousands of rows to examine). We have not been able to find a simple tool that was helping satisfying these specs. So, I have been sent on a mission that surprisingly was not that difficult!
For the ease of demoing, I will output the results in a CSV file. My real solution is adding the data to a database for easier tracking of changes.
The downloadable code
This month’s downloadable demo solution contains both VB and C#. The solution was created using Visual Studio 2017 but can be used in most older versions as well.
I strongly encourage you to download the code as not everything will be pasted here.
An additional reference is required
To be able to use the security classes of the .Net Framework, we need to add a reference to our project: System.DirectoryServices.
Figure 1: Figure 1: Adding the missing reference
Building the UI
The UI for this demo is simple:
Figure 2: The demo application in action
The important code
The code of this application is not complex. It is mainly a big recursive loop to go through all folders and sub-folders from a given startup folder (which we have seen in earlier articles). What’s new here is the use of a special set of methods like GetAccessControl, GetAccessRules, GetOwner which are returning the additional data we need for this task.
All starts when you click the Run button (ensure you enter a valid path in the textbox first). The code behind this button is as simple as this:
Private Sub btnRun_Click(sender As Object, e As EventArgs) Handles btnRun.Click Try Cursor.Current = Cursors.WaitCursor btnRun.Enabled = False txtResults.Clear() txtResults.Text += "FolderPath,AccountSamAccountName,GroupSamAccountName,Inheritance,IsInherited,AccessControlType,Rights,Owner" + Environment.NewLine For Each strDrive As String In txtFolder.Text.Split(CType(";", Char)) If Not String.IsNullOrWhiteSpace(strDrive) Then ReadPermissions(strDrive, True) RecursiveSearch(strDrive) End If Next File.WriteAllText($"Results_{DateTime.Now:yyyyMMdd_HHmmss}.csv", txtResults.Text) Catch ex As Exception MessageBox.Show(ex.Message) Finally btnRun.Enabled = True Cursor.Current = Cursors.Default End Try End Sub
private void btnRun_Click(object sender, EventArgs e) { try { Cursor.Current = Cursors.WaitCursor; btnRun.Enabled = false; txtResults.Clear(); txtResults.Text += "FolderPath,AccountSamAccountName,GroupSamAccountName,Inheritance,IsInherited,AccessControlType,Rights,Owner" + Environment.NewLine; foreach (string strDrive in txtFolder.Text.Split(';')) { if (!string.IsNullOrWhiteSpace(strDrive)) { ReadPermissions(strDrive, true); RecursiveSearch(strDrive); } } File.WriteAllText($"Results_{DateTime.Now:yyyyMMdd_HHmmss}.csv", txtResults.Text); } catch (Exception exception) { MessageBox.Show(exception.Message); } finally { btnRun.Enabled = true; Cursor.Current = Cursors.Default; } }
As you can see in this snippet, you can pass more than one folder to process (each separated by a semi-colon). Each folder is processed by first calling ReadPermissions (for the root folder) and then start the recursive calls to this same method by calling the RecursiveSearch method.
When the loop completes, the WriteAllText saves the content of the Textbox to a csv file (that you will find in the folder where the application runs).
Without any doubts, the most important method to look is ReadPermissions. Most of the code in that method is used to correctly report the identity of the group or user to which permissions are set (that part is not shown here because it is just a boring long Select Case/Switch statement). But before getting there, you need to find these permissions. This is what is shown here:
TODO paste top part of ReadPermissions
'Process the Directory using the string provided Dim dirInfo As DirectoryInfo = New DirectoryInfo(pInput) If Not dirInfo.Exists Then Throw New DirectoryNotFoundException("Folder not found: " + pInput) 'Reads the Directory Security - This allows user to read ACLs Dim dirSec As DirectorySecurity = dirInfo.GetAccessControl() 'Gets Directory Access Control Lists - Collection is returned with ACLs so we can enumerate later Dim arrRules As AuthorizationRuleCollection = dirSec.GetAccessRules(True, True, GetType(Security.Principal.NTAccount)) Dim strOwner As String Try Dim objOwner As Security.Principal.IdentityReference = dirSec.GetOwner(GetType(Security.Principal.NTAccount)) strOwner = objOwner.ToString() Catch ex As Exception strOwner = "unknown" End Try 'Loops through all the rules For Each authorizationRule As FileSystemAccessRule In arrRules Dim strAclIdentityReference As String = authorizationRule.IdentityReference.ToString() Dim strInheritanceFlags As String = authorizationRule.InheritanceFlags.ToString() Dim strAccessControlType As String = authorizationRule.AccessControlType.ToString() Dim strFileSystemRights As String = authorizationRule.FileSystemRights.ToString() Dim strIsInherited As String = authorizationRule.IsInherited.ToString() ...
//Process the Directory using the string provided DirectoryInfo dirInfo = new DirectoryInfo(pInput); if (!dirInfo.Exists) throw new DirectoryNotFoundException("Folder not found: " + pInput); //Reads the Directory Security - This allows user to read ACLs DirectorySecurity dirSec = dirInfo.GetAccessControl(); //Gets Directory Access Control Lists - Collection is returned with ACLs so we can enumerate later AuthorizationRuleCollection arrRules = dirSec.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)); string strOwner; try { System.Security.Principal.IdentityReference owner = dirSec.GetOwner(typeof(System.Security.Principal.NTAccount)); strOwner = owner.ToString(); } catch { strOwner = "unknown"; } //Loops through all the rules foreach (FileSystemAccessRule authorizationRule in arrRules) { string strAclIdentityReference = authorizationRule.IdentityReference.ToString(); string strInheritanceFlags = authorizationRule.InheritanceFlags.ToString(); string strAccessControlType = authorizationRule.AccessControlType.ToString(); string strFileSystemRights = authorizationRule.FileSystemRights.ToString(); string strIsInherited = authorizationRule.IsInherited.ToString(); ...
Skipping inherited permissions
If you run this application on a server that has a lot of folders, you will find out that the list is just unusable. Why? It is just to long. And the main reason is that (usually) each folder inherits the permissions of its parent. And most of the time, these inherited permissions are kept unchanged. Why clutter the results with repeating values then?
This is why I have added the “Skip inherited” checkbox. By selecting this option, you will list the permissions of the root folder (because you need to know what you inherit!), and just the permissions specific to that folder.
For example, in figure 2, the root folder is C:\_temp. This folder contains many subfolders that are not shown here because they have no permissions specific to them but shows the specific permissions for the TestPermissions subfolder (manually highlighted in blue in figure 2).
To relate to the code, you will find a parameter named pFirstLevel in some methods just to be able to output the root folder (the first level).
Conclusion
Compliance has all kind of requests that we might see at first as funny. But this exercise revealed many folders that were accessible only to users that are not in the company anymore!
Some might have seen that only folders are processed here in this article. For my project I was only interested to that level. And believe me, it generates enough data to have fun for days. But a more complete solution would also process the permissions of files into the folders. I will leave this exercise to the readers!
Before you ask the question, this application will run a lot better from the server directly, otherwise user/group names will most probably show as GUID which are not very useful. Also, do I need to tell you that you need the privileges to go through all the folders?
In addition to comply to some rules forced by the industry, a good clean up was done on the server freeing disk space, backup spaces, …! That prove to be a good spring clean up after all.