Reading NTFS and Share security with VbScript

NTFS (File System) and Share security can be enumerated using the Win32_LogicalFileSecuritySetting and Win32_LogicalShareSecuritySetting WMI classes. This post demonstrates how to use each class to read the security descriptors.

In each case the WMI class contains a GetSecurityDescriptor method used to retrieve the Security Descriptor (as a Win32_SecurityDescriptor).

The references section at the bottom of this article include details of sources for all constants. A little PowerShell creeps in to show how the numeric values were retrieved and checked.

DACLs and SACLs

A DACL or Discretionary Access Control List is the most heavily used, it contains Access Control Entries that define who can, and who cannot, access a resource or object. These are seen when viewing the Security tab of an object.

An SACL or System Access Control List defines which actions will be audited when accessing an a resource or object. Seen by accessing the Audit tab through Security and Advanced.

NTFS security vs Share security

Both the NTFS and the Share security descriptor can be read in exactly the same way. However, there are important differences between them.

NTFS security descriptor

  • Supports inheritance on descriptor and on individual Access Control Entries
  • Can hold a Discretionary Access Control List and an System Access Control List

Share security descriptor

  • Has no owner or Primary Group
  • Control flags will be set (and usually limited) to SelfRelative and DiscretionaryACLPresent
  • Access Control Entry (ACE) Flags will not be set
  • Can hold a Discretionary Access Control List only
  • Supports a sub-set of Access Masks (rights) on each ACE

Getting the security descriptor

NTFS

Dim strComputer : strComputer = "." ' Connect to WMI on strComputer Dim objWMI : Set objWMI = GetObject("winmgmts://" & strComputer & "/root/cimv2") ' Get an instance of LogicalFileSecuritySetting for C: Dim objSecuritySettings : Set objSecuritySettings = objWMI.Get("Win32_LogicalFileSecuritySetting='C:'") Dim intReturnValue : Dim objSD ' Request the Security Descriptor as objSD intReturnValue = objSecuritySettings.GetSecurityDescriptor objSD

Share

Dim strComputer : strComputer = "." ' Connect to WMI on strComputer Dim objWMI : Set objWMI = GetObject("winmgmts://" & strComputer & "/root/cimv2") ' Get an instance of LogicalShareSecuritySetting for ShareName Dim objSecuritySettings : Set objSecuritySettings = objWMI.Get("Win32_LogicalShareSecuritySetting='ShareName'") Dim intReturnValue : Dim objSD ' Request the Security Descriptor as objSD intReturnValue = objSecuritySettings.GetSecurityDescriptor objSD

Error handling

The GetSecurityDescriptor method of each WMI class (Win32_LogicalFileSecuritySetting and Win32_LogicalShareSecuritySetting) has a return value to allow errors to be handled. The return codes are represented by the constants below.

Const SUCCESS = 0 Const ACCESS_DENIED = 2 Const UNKNOWN_FAILURE = 8 Const PRIVILEGE_MISSING = 9 Const INVALID_PARAMETER = 21

Values in the security descriptor

The security descriptor contains three fields in addition to the DACL and SACL which are described later.

Control flags

Each security descriptor has a ControlFlags field which dictates how the descriptor behaves. This includes settings such as DACLProtected which indicates that the DACL cannot inherit values from a parent.

The script loads the values above into a Scripting.Dictionary object to simplify enumeration.

Dim objControlFlags : Set objControlFlags = CreateObject("Scripting.Dictionary") objControlFlags.Add 32768, "SelfRelative" objControlFlags.Add 16384, "RMControlValid" objControlFlags.Add 8192, "SystemAclProtected" objControlFlags.Add 4096, "DiscretionaryAclProtected" objControlFlags.Add 2048, "SystemAclAutoInherited" objControlFlags.Add 1024, "DiscretionaryAclAutoInherited" objControlFlags.Add 512, "SystemAclAutoInheritRequired" objControlFlags.Add 256, "DiscretionaryAclAutoInheritRequired" objControlFlags.Add 32, "SystemAclDefaulted" objControlFlags.Add 16, "SystemAclPresent" objControlFlags.Add 8, "DiscretionaryAclDefaulted" objControlFlags.Add 4, "DiscretionaryAclPresent" objControlFlags.Add 2, "GroupDefaulted" objControlFlags.Add 1, "OwnerDefaulted"

Note that they are intentionally entered in order from highest to lowest. A For Each loop will start with the highest value (because it was added first), then compare it to the ControlFlags value. In bitwise comparison, if the integer value can be there, then it must be there, that would all go wrong unless it starts with the highest possible value.

The values and names were taken from the .NET Framework, they can be displayed in PowerShell with the following command.

[Enum]::GetValues([System.Security.AccessControl.ControlFlags]) | Select-Object @{ n='Name';e={ [String]$_ }}, @{ n='Value';e={ $_.value__ }}

That logic is applied within the code as follows.

' Read the value of Control Flags from the descriptor dblControlFlags = objSD.ControlFlags WScript.Echo "Control Flags:" ' For each possible flag For Each dblFlag in objControlFlags ' If it is possible for the flag to be there, then it must be there ' Something like bitwise AND If dblControlFlags >= dblFlag Then ' Echo the flag, indicating it is present WScript.Echo " " & objControlFlags(dblFlag) ' Remove this value from the control flag value, ' already found this flag dblControlFlags = dblControlFlags - dblFlag End If Next

Primary group

The Primary Group is not very interesting unless Services for Unix is in use. In many cases it will be blank.

If set, it can be enumerated as a Win32_Trustee, an object containing a Domain, Username, SID array, SID length and SIDString.

Owner

As with the Primary Group, the owner is stored as a Win32_Trustee object. It can be read from the descriptor as follows.

WScript.Echo "Domain: " & objSD.Owner.Domain WScript.Echo "Username: " & objSD.Owner.Name WScript.Echo "SID: " & objSD.Owner.SIDString

Access control entries in the DACL and SACL

Both the DACL and SACL, if present, consist of one or more Access Control Entries (ACE). The ACE, like the security descriptor, breaks down into a number of different fields.

Access Mask

The access mask defines which rights should be used by the Access Control Entry. In the case of System ACL it defines which actions should be audited.

A number of the right names are omitted from the list below as they carry the same numeric value (mask the same bit), the meaning only changes in the context the right is applied.

Dim objAccessRights : Set objAccessRights = CreateObject("Scripting.Dictionary") objAccessRights.Add 2032127, "FullControl" objAccessRights.Add 1048576, "Synchronize" objAccessRights.Add 524288, "TakeOwnership" objAccessRights.Add 262144, "ChangePermissions" objAccessRights.Add 197055, "Modify" objAccessRights.Add 131241, "ReadAndExecute objAccessRights.Add 131209, "Read" objAccessRights.Add 131072, "ReadPermissions" objAccessRights.Add 65536, "Delete" objAccessRights.Add 278, "Write" objAccessRights.Add 256, "WriteAttributes" objAccessRights.Add 128, "ReadAttributes" objAccessRights.Add 64, "DeleteSubdirectoriesAndFiles" objAccessRights.Add 32, "ExecuteFile" objAccessRights.Add 16, "WriteExtendedAttributes" objAccessRights.Add 8, "ReadExtendedAttributes" objAccessRights.Add 4, "AppendData" objAccessRights.Add 2, "CreateFiles" objAccessRights.Add 1, "ReadData"

Note that a number of the rights in the list are composites of simpler rights, including Read, FullControl, Write and Modify.

The list can be retrieved, using PowerShell again, with the following command:

[Enum]::GetValues([System.Security.AccessControl.FileSystemRights]) | Select-Object @{n='Name';e={ [String]$_ }}, @{n='Value';e={ $_.value__ }}

Holding the flags in this fashion allows the Access Mask to be converted to a friendly form in the same way as the Control Flags were displayed.

' For each access control entry in the discretionary access control list For Each objACE in objSD.DACL WScript.Echo "Access Mask:" ' Read the value of the access mask from the Access Control Entry dblAccessMask = objACE.AccessMask ' For each possible Right For Each dblAccess in objAccessRights ' If the right can be there then it must... If dblAccessMask >= dblAccess Then ' Echo the right name WScript.Echo " " & objAccessRights(dblAccess) ' Remove the value from the access mask, ' already found this right. dblAccessMask = dblAccessMask - dblAccess End If Next Next

Flags

The flags on an Access Control Entry defines whether the entry applies to Leaf or Container objects, how it behaves when inheritance is calculated, and whether or not the ACE is inherited or explicit.

Dim objAceFlags : Set objAceFlags = CreateObject("Scripting.Dictionary") objAceFlags.Add 128, "FailedAccess" objAceFlags.Add 64, "SuccessfulAccess" objAceFlags.Add 16, "Inherited" objAceFlags.Add 8, "InheritOnly" objAceFlags.Add 4, "NoPropagateInherit" objAceFlags.Add 2, "ContainerInherit" objAceFlags.Add 1, "ObjectInherit"

Heading back to PowerShell again we can find the the names and values for these.

[Enum]::GetValues([System.Security.AccessControl.AceFlags]) | Select-Object @{n='Name';e={ [String]$_ }}, @{n='Value';e={ $_.value__ }}

A few are intentionally left out as they are composites of values more interesting to display separately.

Enumeration of the Access Control Entry flags is performed as follows.

' For each access control entry in the discretionary access control list For Each objACE in objSD.DACL WScript.Echo "ACE Flags:" ' Read the value of ACE Flags from the Access Control Entry dblAceFlags = objAce.AceFlags ' For each possible flag For Each dblFlag in objAceFlags ' If the flag can be there then it must... If dblAceFlags >= dblFlag Then ' Echo the name of the flag WScript.Echo " " & objAceFlags(dblFlag) ' Found it, remove it to note that it has been found. dblAceFlags = dblAceFlags - dblFlag End If Next Next

Trustee

The value for the trustee (Win32_Trustee), the security principal (user, group, computer, etc) the right applies to is enumerated in the same way as the Owner above.

For Each objACE in objDACL WScript.Echo "Domain: " & objACE.Trustee.Domain WScript.Echo "User: " & objACE.Trustee.Name WScript.Echo "SID: " & objACE.Trustee.SIDString Next

Type

Finally, the ACEType consists of three values which define whether the ACE permits access, denies access or is an audit control.

Dim objAceTypes : Set objAceTypes = CreateObject("Scripting.Dictionary") objAceTypes.Add 0, "Allow" objAceTypes.Add 1, "Deny" objAceTypes.Add 2, "Audit"

Once again, we can discover all of the possible values for AceFlags using PowerShell.

[Enum]::GetValues([System.Security.AccessControl.AceType]) | Select-Object @{n='Name';e={ [String]$_ }}, @{n='Value';e={ $_.value__ }}

However, this time the only values that are used in the script are 0 (Allow), 1 (Deny), and 2 (Audit).

' For each access control entry in the discretionary access control list For Each objACE in objDACL ' Echo the type: Allow, Deny or Audit. WScript.Echo "ACE Type: " & objAceTypes(objAce.AceType) Next

Listing permissions for all Shares

Time to put all of that together. This sample lists the NTFS and Share permissions for each share configured on strComputer.

Option Explicit ' WMI Constants Const WBEM_RETURN_IMMEDIATELY = &H10 Const WBEM_FORWARD_ONLY = &H20 ' Constants and storage arrays for security settings ' GetSecurityDescriptor Return values Dim objReturnCodes : Set objReturnCodes = CreateObject("Scripting.Dictionary") Const SUCCESS = 0 Const ACCESS_DENIED = 2 Const UNKNOWN_FAILURE = 8 Const PRIVILEGE_MISSING = 9 Const INVALID_PARAMETER = 21 ' Security Descriptor Control Flags Dim objControlFlags : Set objControlFlags = CreateObject("Scripting.Dictionary") objControlFlags.Add 32768, "SelfRelative" objControlFlags.Add 16384, "RMControlValid" objControlFlags.Add 8192, "SystemAclProtected" objControlFlags.Add 4096, "DiscretionaryAclProtected" objControlFlags.Add 2048, "SystemAclAutoInherited" objControlFlags.Add 1024, "DiscretionaryAclAutoInherited" objControlFlags.Add 512, "SystemAclAutoInheritRequired" objControlFlags.Add 256, "DiscretionaryAclAutoInheritRequired" objControlFlags.Add 32, "SystemAclDefaulted" objControlFlags.Add 16, "SystemAclPresent" objControlFlags.Add 8, "DiscretionaryAclDefaulted" objControlFlags.Add 4, "DiscretionaryAclPresent" objControlFlags.Add 2, "GroupDefaulted" objControlFlags.Add 1, "OwnerDefaulted" ' ACE Access Right Dim objAccessRights : Set objAccessRights = CreateObject("Scripting.Dictionary") objAccessRights.Add 2032127, "FullControl" objAccessRights.Add 1048576, "Synchronize" objAccessRights.Add 524288, "TakeOwnership" objAccessRights.Add 262144, "ChangePermissions" objAccessRights.Add 197055, "Modify" objAccessRights.Add 131241, "ReadAndExecute" objAccessRights.Add 131209, "Read" objAccessRights.Add 131072, "ReadPermissions" objAccessRights.Add 65536, "Delete" objAccessRights.Add 278, "Write" objAccessRights.Add 256, "WriteAttributes" objAccessRights.Add 128, "ReadAttributes" objAccessRights.Add 64, "DeleteSubdirectoriesAndFiles" objAccessRights.Add 32, "ExecuteFile" objAccessRights.Add 16, "WriteExtendedAttributes" objAccessRights.Add 8, "ReadExtendedAttributes" objAccessRights.Add 4, "AppendData" objAccessRights.Add 2, "CreateFiles" objAccessRights.Add 1, "ReadData" ' ACE Types Dim objAceTypes : Set objAceTypes = CreateObject("Scripting.Dictionary") objAceTypes.Add 0, "Allow" objAceTypes.Add 1, "Deny" objAceTypes.Add 2, "Audit" ' ACE Flags Dim objAceFlags : Set objAceFlags = CreateObject("Scripting.Dictionary") objAceFlags.Add 128, "FailedAccess" objAceFlags.Add 64, "SuccessfulAccess" objAceFlags.Add 16, "Inherited" objAceFlags.Add 8, "InheritOnly" objAceFlags.Add 4, "NoPropagateInherit" objAceFlags.Add 2, "ContainerInherit" objAceFlags.Add 1, "ObjectInherit" Sub ReadNTFSSecurity(objWMI, strPath) WScript.Echo " Displaying NTFS Security" Dim objSecuritySettings : Set objSecuritySettings = _ objWMI.Get("Win32_LogicalFileSecuritySetting='" & strPath & "'") Dim objSD : objSecuritySettings.GetSecurityDescriptor objSD Dim strDomain : strDomain = objSD.Owner.Domain If strDomain "" Then strDomain = strDomain & "" WScript.Echo " Owner: " & strDomain & objSD.Owner.Name WScript.Echo " Owner SID: " & objSD.Owner.SIDString WScript.Echo " Basic Control Flags Value: " & objSD.ControlFlags WScript.Echo " Control Flags:" DisplayValues objSD.ControlFlags, objControlFlags WScript.Echo Dim objACE ' Display the DACL WScript.Echo " Discretionary Access Control List:" For Each objACE in objSD.DACL DisplayACE objACE Next ' Display the SACL (if there is one) If Not IsNull(objSD.SACL) Then WScript.Echo " System Access Control List:" For Each objACE in objSD.SACL DisplayACE objACE Next End If End Sub Sub ReadShareSecurity(objWMI, strName) WScript.Echo " Displaying Share Security" Dim objSecuritySettings : Set objSecuritySettings = _ objWMI.Get("Win32_LogicalShareSecuritySetting='" & strName & "'") Dim objSD : objSecuritySettings.GetSecurityDescriptor objSD WScript.Echo " Basic Control Flags Value: " & objSD.ControlFlags WScript.Echo " Control Flags:" DisplayValues objSD.ControlFlags, objControlFlags WScript.Echo Dim objACE ' Display the DACL WScript.Echo " Discretionary Access Control List:" For Each objACE in objSD.DACL DisplayACE objACE Next End Sub Sub DisplayValues(dblValues, objSecurityEnumeration) Dim dblValue For Each dblValue in objSecurityEnumeration If dblValues >= dblValue Then WScript.Echo " " & objSecurityEnumeration(dblValue) dblValues = dblValues - dblValue End If Next End Sub Sub DisplayACE(objACE) Dim strDomain : strDomain = objAce.Trustee.Domain If strDomain "" Then strDomain = strDomain & "" WScript.Echo " Trustee: " & UCase(strDomain & objAce.Trustee.Name) WScript.Echo " SID: " & objAce.Trustee.SIDString WScript.Echo " Basic Access Mask Value: " & objACE.AccessMask WScript.Echo " Access Rights: " DisplayValues objACE.AccessMask, objAccessRights WScript.Echo " Type: " & objAceTypes(objACE.AceType) WScript.Echo " Basic ACE Flags Value: " & objACE.AceFlags WScript.Echo " ACE Flags: " DisplayValues objACE.AceFlags, objAceFlags WScript.Echo End Sub ' ' Main Code ' ' The system to execute this script against Dim strComputer : strComputer = "." ' Connect to WMI Dim objWMI : Set objWMI = GetObject("winmgmts:" & strComputer & "rootCIMV2") ' Return all of the shares (Type = 0 means File Shares only, exclude ' are Administrative, Printer, etc) Dim colItems : Set colItems = _ objWMI.ExecQuery("SELECT * FROM Win32_Share WHERE Type='0'", "WQL", _ WBEM_RETURN_IMMEDIATELY + WBEM_FORWARD_ONLY) Dim objItem For Each objItem in colItems WScript.Echo WScript.Echo "Security for " & objItem.Path & _ " (Shared as " & objItem.Name & ")" ReadNTFSSecurity objWMI, objItem.Path ReadShareSecurity objWMI, objItem.Name Next

References

Reading NTFS and Share security with VbScript
Share this