I'm back with an Active Directory / VBS article for a change. Many of those who work in a corporate environment may be challenged by business entities and folder/share/application owners to deliver a report of who has access to a folder/share/application.
In simple Active Directory implementations, group membership is granted directly to an user and therefore it is merely a matter of minutes for the administrator to log into the ADUC console through dsa.msc plugin (Active Directory Users and Computers) and export the contents of a security group through a few screenshots or by running a simple enumeration script.
In corporate environments, it is more than likely that you will encounter more sophisticated access rights delegation schemes due to the increased complexity of the firm (various business units, large filesystems, complexity of everyday management..). There's an extremely high chance that you will be facing an AGDLP-based permission schema. If you aren't familiar with AGDLP, I would recommend you to look up on google, but to make it short I will quote the relevant wikipedia article that states the following :
"AGDLP is the acronym used to describe the practice of taking Accounts (A) and placing them into Global Groups (G) often for organizational purposes, such as grouping all sales people together. Then the Global Group is placed inside or nested within the Domain Local Group (DL) which will be used on the NTFS or share Access Control List (ACL) to provide permission. So Accounts go into Global Groups, Global Groups go into Domain Local Groups and the permission is assigned to the Domain Local Group: AGDLP. The main thrust of this technique is to focus a single permission set on a single group at the ACL level (Read only, read/write, etc) and then populate that single group in Active Directory whenever and as often as the assigned permission is needed."
Basically, if you have AGDLP, this means that you have users into groups, these groups are into other groups, and so on. You can have several levels of nested membership until you actually get to the group that grants permission to an application, share or folder.
Having been confronted to this problem, and willing to spend as few time as possible on tedious, repetitive tasks, I started searching on the net, as it is well known that there is no point in reinventing the wheel. If found a script that I adapted for my own use. I would like to send million blessings to its author, however I haven't been able to find out who the creator was. Whoever you are, you deserve a million thanks and due credit for your work!
My adaptations to the existing script were as follows :
– specify a filename as parameter to automatize the processing. The files are plain text files containing the list of security groups to be scanned.
– branch instructions to generate the proper output string
– add functions to write the output to a CSV file
In practice …
Let's see a typical usage example. Say we work for ACME and have an application called GIZMO, whose access is granted through Active Directory membership. The GIZMO Application is accessed by people located in France, Germany and Sweden. There are therefore three AD groups granting access :
I will create a file called GIZMO.grp with the following content :
Then I will call the VBS script with GIZMO.grp as parameter :
this will start processing all the groups contained in the GIZMO.grp file, it will process all of the subgroups and will indicate the various membership levels, what group is part of which group and the output will be stored in the gizmo.csv file. It is recommended to run the VBS from the command line straight from the folder where you plan to store your VBS file and your various GRP files, the command line parameter (input file) being used as a variable to store the application name. If you launch it from explorer, you will have the full path instead of just GIZMO.
As you browse the CSV file, you will find some interesting details about the group nested memberships, for example :
You will find out a more interesting and varied reality than what you had initially seen without an in-depth searching. Providing such a list to a business unit/application owner will allow them a full insight on who has access to their work, and will prevent both unnecessary info disclosure as well as conflicts of interest.
Feel free to try the script for yourself, on your own Active Directory structure. I am sure you will enjoy it! I have limited the script to 4 sub-levels, which currently suits my business needs. However it will take 5 minutes to any decent admin to adapt it to your needs.
Give me code!
Here is the script content below :
Option Explicit Dim Iteration Dim objGroup, InputFile, InputString, StrInheritedFrom, StrGroupName, OutText Dim oFSO, sFile, oFile, wFSO, wsFile, woFile Dim Level2,Level3,Level4 If wscript.arguments.count <> 1 Then wscript.echo "NO FILENAME PASSED" wscript.echo "Usage: nested <filename.grp>" wscript.quit End If Iteration = 0 Set oFSO = CreateObject("Scripting.FileSystemObject") sFile = wscript.Arguments(0) If oFSO.FileExists(sFile) Then Set oFile = oFSO.OpenTextFile(sFile, 1) OutText = Left(sFile,Len(sFile)-4) Set wFSO = CreateObject("Scripting.FileSystemObject") wsFile = OutText & ".csv" Set woFile = oFSO.CreateTextFile(wsFile, true) woFile.WriteLine "Application,Username,LogonName,Primary Group,Level 2 Group, Level 3 Group, Level 4 Group" Do While Not oFile.AtEndOfStream InputString = oFile.Readline enumMembers getGroup(InputString), "" Iteration=1 Loop End If Function getGroup(strGroupName) Dim objConn, objRecSet, strQueryString, objRootDSE, strQueryFrom Const adsOpenStatic = 3 Set objRootDSE = GetObject("LDAP://RootDSE") strQueryFrom = "LDAP://" & objRootDSE.get("defaultNamingContext") Set objConn = wscript.CreateObject("ADODB.Connection") objConn.Provider = "ADsDSOObject" objConn.Open strQueryString = "SELECT AdsPath FROM '" & strQueryFrom & "' " & "WHERE sAMAccountName = '" & strGroupName & "'" Set objRecSet = wscript.CreateObject("ADODB.Recordset") objRecSet.Open strQueryString, objConn, adsOpenStatic If objRecSet.recordCount = 1 Then Set getGroup = GetObject(objRecSet("AdsPath")) End If End Function Sub enumMembers(byRef objGroup, strInheritedFrom) Dim objMember,strRecord,SubscanDepth SubScanDepth = 0 For Each objMember In objGroup.Members If lcase(objMember.class) = "group" Then Select Case Iteration Case 2 Level2=objMember.CN Case 3 Level3=objMember.CN Case 4 Level4=objMember.CN Case Else End Select enumMembers objMember, objMember.samAccountName Else If objMember.displayname <> "" Then If strInheritedFrom <> "" Then SubscanDepth = 1 End If If Level2 <> "" Then SubscanDepth = 2 End If If Level3 <> "" Then SubscanDepth = 3 End If If Level4 <> "" Then SubscanDepth = 4 End If Select Case SubscanDepth Case 4 strRecord=OutText &","& objMember.displayname &","& objMember.samAccountName &","& InputString &","& strInheritedFrom &","& Level2 &","& Level3 &","& Level4 Case 3 strRecord=OutText &","& objMember.displayname &","& objMember.samAccountName &","& InputString &","& strInheritedFrom &","& Level2 &","& Level3 Case 2 strRecord=OutText &","& objMember.displayname &","& objMember.samAccountName &","& InputString &","& strInheritedFrom &","& Level2 Case 1 strRecord=OutText &","& objMember.displayname &","& objMember.samAccountName &","& InputString &","& strInheritedFrom Case Else strRecord=OutText &","& objMember.displayname &","& objMember.samAccountName &","& InputString End Select End If End If If strRecord <> "" Then woFile.WriteLine strRecord End If Iteration=Iteration+1 Next End Sub
Hints for the lazy ones
Because I am
lazy efficient, I like to automatize things. I have also made a batch file that runs all the grp files in the folder then concatenates the various csv files. Just make a bat file with the various command lines and a COPY concatenation of the CSV output files e.g. :
COPY GROUP1.csv+GROUP2.csv+GROUP3.csv+...+GROUP20.csv AccessReview.csv
This will give you a full CSV file almost ready to work with.
I have a bulk version of nested, nestedblk.vbs, that removes the woFile.WriteLine "Application,Username,LogonName,Primary Group,Level 2 Group, Level 3 Group, Level 4 Group" line.
I use the nested for the first batch command, then nestedblk for all the others, so that I don't have to remove the headers when concatenating all the CSV files.
Anyways, I hope this article wasn't boring, although it was long, painful and verbose!
Take care and see you soon for another article!