Thursday, April 26, 2012

Decoding AD ACL's (Powershell)

Today I was looking at a deep dive video on creating modules and found out about the BSonPosh module.  This contains some interesting commandlets, one of which is similar to some of the work I have been doing lately related to managing AD object ACL's.  The commandlets for get-adacl in this module are similar to what you can get with the Microsoft AD module using get-acl pointing to the AD PSDrive.  The thing that makes BSOnPosh great in comparison is that it works great on a multi-domain environment, and you don't need to go out of your way to point to 2008 servers if you have a mixed environment.  In any case, if you look at my previous post related to decoding GUID's in ACL's by converting GUID's into an ldap searchable format, we can use this to extend the work done by BSonPosh.  First, lets look again at what the acl entries look like:

ActiveDirectoryRights : CreateChild, DeleteChild
InheritanceType : None
ObjectType : bf967aa8-0de6-11d0-a285-00aa003049e2
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : ObjectAceTypePresent
AccessControlType : Allow
IdentityReference : S-1-5-32-550
IsInherited : False
InheritanceFlags : None
PropagationFlags : None

Here, you can see many of the enumerations are decoded into text, but we have guid's and SID's that may show up without translation.  With BSonPosh, there is a function for converting SID's already, though in this case I'm modifying it to handle SID's that do not translate with that code.  Combining that with another helper function to decode the GUID's to the ldap display attribute listed in the schema,  you can get a result that is more readable like this:

Get-ADACL -distinguishedname "dc=contoso,dc=com" | convert-adacl

ActiveDirectoryRights : ReadProperty, WriteProperty, Delete
InheritanceType          : Descendents
ObjectType                : msRTCSIP-UserPolicies
InheritedObjectType   : user
ObjectFlags                : ObjectAceTypePresent, InheritedObjectAceTypePresent
AccessControlType    : Allow
IdentityReference        : CONTOSO\RTCDomainUserAdmins
IsInherited                   : False
InheritanceFlags          : ContainerInherit
PropagationFlags        : InheritOnly

Below is the code.  One function was changed (ConvertTO-Name).  The rest is added to BSActiveDirectory.psm1.  It is important to note, that this conversion function creates its own custom PSobject, so it is not longer an ACL that you can play with and try to write back.  This was more designed for auditing purposes.



#region Convert-ADACL 

function Convert-ADACL
{
        
    <#
        .Synopsis 
            Translates the AD Object ACL to a more readable format by converting SID and GUID values to text
            
        .Description
            Translates the AD Object ACL to a more readable format by converting SID and GUID values to tex
            
        .Parameter ACL
            ACL Object to Apply
            
        .OUTPUTS
            Object
            
    #>
        [cmdletbinding()]
        Param(
    
            [Parameter(ValueFromPipeline=$true)]
            [System.DirectoryServices.ActiveDirectoryAccessRule]$ACL
            
        )

  Begin {
   $results = @()
  }
  
  process {
   if ($_.ActiveDirectoryRights -eq "ExtendedRight") {
    $myresult = New-Object PSobject -Property @{
     ActiveDirectoryRights = $_.ActiveDirectoryRights
     InheritanceType = $_.InheritanceType
     ObjectType = Convert-GUIDToName -guid $_.objecttype -extended
     InheritedObjectType = Convert-GUIDToName -guid $_.inheritedobjecttype
     ObjectFlags = $_.ObjectFlags
     AccessControlType = $_.accesscontroltype
     IdentityReference = ConvertTo-Name -sid $_.identityReference
     IsInherited = $_.isinherited
     InheritanceFlags = $_.InheritanceFlags
     PropagationFlags = $_.PropagationFlags
    }
   } else {
          $myresult = New-Object PSobject -Property @{
     ActiveDirectoryRights = $_.ActiveDirectoryRights
     InheritanceType = $_.InheritanceType
     ObjectType = Convert-GUIDToName -guid $_.objecttype
     InheritedObjectType = Convert-GUIDToName -guid $_.inheritedobjecttype
     ObjectFlags = $_.ObjectFlags
     AccessControlType = $_.accesscontroltype
     IdentityReference = ConvertTo-Name -sid $_.identityReference
     IsInherited = $_.isinherited
     InheritanceFlags = $_.InheritanceFlags
     PropagationFlags = $_.PropagationFlags
    }  
   }
   $results += $Myresult

  }
  end {
   $results |Select-Object ActiveDirectoryRights,InheritanceType,ObjectType,InheritedObjectType,ObjectFlags,`
    AccessControlType,IdentityReference,IsInherited,InheritanceFlags,PropagationFlags
  }
}
    
#endregion 


#region ConvertTo-Name 

function ConvertTo-Name
{
    param($sid)
 Write-Verbose $sid
 try {
     $ID = New-Object System.Security.Principal.SecurityIdentifier($sid)
     $User = $ID.Translate( [System.Security.Principal.NTAccount])
     $User.Value
 } catch {
  switch($sid) {
   #Reference http://support.microsoft.com/kb/243330
   "S-1-0" { "Null Authority" }
   "S-1-0-0" { "Nobody" }
   "S-1-1" {"World Authority" }
   "S-1-1-0" { "Everyone" }
   "S-1-2" { "Local Authority" }
   "S-1-2-0" { "Local" }
   "S-1-2-1" { "Console Logon" }
   "S-1-3" { "Creator Authority" }
   "S-1-3-0" { "Creator Owner" }
   "S-1-3-1" { "Creator Group" }
   "S-1-3-4" { "Owner Rights" }
   "S-1-5-80-0" {"All Services" }
   "S-1-4" { "Non Unique Authority" }
   "S-1-5" { "NT Authority" }
   "S-1-5-1" { "Dialup" }
   "S-1-5-2" { "Network" }
   "S-1-5-3" { "Batch" }
   "S-1-5-4" { "Interactive" }
   "S-1-5-6" { "Service" }
   "S-1-5-7" { "Anonymous" }
   "S-1-5-9" { "Enterprise Domain Controllers"}
   "S-1-5-10" { "Self" }
   "S-1-5-11" { "Authenticated Users" }
   "S-1-5-12" { "Restricted Code" }
   "S-1-5-13" { "Terminal Server Users" }
   "S-1-5-14" { "Remote Interactive Logon" }
   "S-1-5-15" { "This Organization" }
   "S-1-5-17" { "This Organization" }
   "S-1-5-18" { "Local System" }
   "S-1-5-19" { "NT Authority Local Service" }
   "S-1-5-20" { "NT Authority Network Service" }
   "S-1-5-32-544" { "Administrators" }
   "S-1-5-32-545" { "Users"}
   "S-1-5-32-546" { "Guests" }
   "S-1-5-32-547" { "Power Users" }
   "S-1-5-32-548" { "Account Operators" }
   "S-1-5-32-549" { "Server Operators" }
   "S-1-5-32-550" { "Print Operators" }
   "S-1-5-32-551" { "Backup Operators" }
   "S-1-5-32-552" { "Replicators" }
   "S-1-5-32-554" { "Pre-Windows 2000 Compatibility Access"}
   "S-1-5-32-555" { "Remote Desktop Users"}
   "S-1-5-32-556" { "Network Configuration Operators"}
   "S-1-5-32-557" { "Incoming forest trust builders"}
   "S-1-5-32-558" { "Performance Monitor Users"}
   "S-1-5-32-559" { "Performance Log Users" }
   "S-1-5-32-560" { "Windows Authorization Access Group"}
   "S-1-5-32-561" { "Terminal Server License Servers"}
   "S-1-5-32-561" { "Distributed COM Users"}
   "S-1-5-32-569" { "Cryptographic Operators" }
   "S-1-5-32-573" { "Event Log Readers" }
   "S-1-5-32-574" { "Certificate Services DCOM Access" }
   "S-1-5-32-575" { "RDS Remote Access Servers" }
   "S-1-5-32-576" { "RDS Endpoint Servers" }
   "S-1-5-32-577" { "RDS Management Servers" }
   "S-1-5-32-575" { "Hyper-V Administrators" }
   "S-1-5-32-579" { "Access Control Assistance Operators" }
   "S-1-5-32-580" { "Remote Management Users" }
   
   default {$sid}
  }
 }
}
    
#endregion 

#region Convert-GUIDToName

 #helper module to convert schema GUID's to readable names

function Convert-GUIDToName
{
 param(
  [parameter(mandatory=$true)][string]$guid,
  [switch]$extended
 )
 
 $guidval = [Guid]$guid
 $bytearr = $guidval.tobytearray()
    $bytestr = ""
    
 foreach ($byte in $bytearr) {
          $str = "\" + "{0:x}" -f $byte
          $bytestr += $str
    }
 
 if ($extended) {
  #for extended rights, we can check in the configuration container
  $de = new-object directoryservices.directoryentry("LDAP://" + ([adsi]"LDAP://rootdse").psbase.properties.configurationnamingcontext)
  $ds = new-object directoryservices.directorysearcher($de)
  $ds.propertiestoload.add("displayname")|Out-Null
  $ds.filter = "(rightsguid=$guid)"
  $result = $ds.findone()
 } else {
  #Search schema for possible matches for this GUID
  $de = new-object directoryservices.directoryentry("LDAP://" + ([adsi]"LDAP://rootdse").psbase.properties.schemanamingcontext)
  $ds = new-object directoryservices.directorysearcher($de)
  $ds.filter = "(|(schemaidguid=$bytestr)(attributesecurityguid=$bytestr))"
  $ds.propertiestoload.add("ldapdisplayname")|Out-Null
  $result = $ds.findone()
 } 
 if ($result -eq $null) {
  $guid
 } else {
  if ($extended) {
   $result.properties.displayname
  } else {
   $result.properties.ldapdisplayname 
  }
 }
 
}
#endregion

Update: 1/29/2013

Just an additional note. Since you need distinguishednames for the commandlets, this can be a pain at times. If you like to use dsquery for quick and short results you can pipe these in, however you need to deal with the quotations that are put around the dsquery output. Example:

dsquery group -name Mygroup | foreach {$_.replace("`"","")} | 
get-adacl | where {$_.isinhertied -eq $false} |convert-adacl


Update: Sept 2020
It looks like all the links to the BSOnPosh module are broken now. Below is the functions that were referenced from there.


 function Get-ADACL
{
        
    <#
        .Synopsis 
            Gets ACL object or SDDL for AD Object
            
        .Description
            Gets ACL object or SDDL for AD Object
            
        .Parameter DistinguishedName
            DistinguishedName of the Object to Get the ACL from
            
        .Parameter SDDL [switch]
            If passed it will return the SDDL string
            
        .Example
            Get ACL for ‘cn=users,dc=corp,dc=lab’
                Get-ADACL ‘cn=users,dc=corp,dc=lab’
            Get SDDL for ‘cn=users,dc=corp,dc=lab’
                Get-ADACL ‘cn=users,dc=corp,dc=lab’ -sddl
                
        .Outputs
            Object
            
        .Link
            N/A
            
        .Notes
            NAME:      Get-ADACL
            AUTHOR:    YetiCentral\bshell
            Website:   www.bsonposh.com
            #Requires -Version 2.0
    #>
    
    [Cmdletbinding()]
    Param(
    
        [Alias('dn')]
        [ValidatePattern('^((CN|OU)=.*)*(DC=.*)*$')]
        [Parameter(ValueFromPipeline=$true,Mandatory=$True)]
        [string]$DistinguishedName,
        
        [Parameter()]
        [switch]$SDDL
    )
    Write-Verbose " + Processing Object [$DistinguishedName]"
    $DE = [ADSI]"LDAP://$DistinguishedName"
    
    Write-Verbose "   - Getting ACL"
    $acl = $DE.psbase.ObjectSecurity
    if($SDDL)
    {
        Write-Verbose "   - Returning SDDL"
        $acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)
    }
    else
    {
        Write-Verbose "   - Returning ACL Object [System.DirectoryServices.ActiveDirectoryAccessRule]"
        $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
    }
}

3 comments:

  1. Hello Nathan

    this is a very helpful post,

    when using the convert-GUIDToName function I did notice it returned some odd results for the LDAP Display Name when it came to Property Sets, for example if you give a User the Write right to the Public Information property set for example. The LDAP Display Name returned was not reflective of this and therefore confusing (may just have a weird or incorrect LDAP Display Name in the Schema) any way I resolved this by adding some more code to your function, please see the following.

    function Convert-GUIDToName {
    param(
    [parameter(mandatory=$true)][string]$guid,
    [switch]$extended
    )

    begin {

    #### Added by Ernest Brant
    $HT = @{
    'Domain-Password-Information' = [guid]'c7407360-20bf-11d0-a768-00aa006e0529'

    'Email-Information' = [guid]'E45795B2-9455-11d1-AEBD-0000F80367C1'

    'General-Inforamtion' = [guid]'59ba2f42-79a2-11d0-9020-00c04fc2d3cf'

    'Membership' = [guid]'bc0ac240-79a9-11d0-9020-00c04fc2d4cf'

    'User-Account-Restrictions' = [guid]'4c164200-20c0-11d0-a768-00aa006e0529'

    'Personal-Information' = [guid]'77B5B886-944A-11d1-AEBD-0000F80367C1'

    'Public-Information' = [guid]'e48d0154-bcf8-11d1-8702-00c04fb96050'

    'RAS-Information' = [guid]'037088f8-0ae1-11d2-b422-00a0c968f939'

    'User-Logon' = [guid]'5f202010-79a5-11d0-9020-00c04fc2d4cf'

    'Web-Information' = [guid]'E45795B3-9455-11d1-AEBD-0000F80367C1'

    'DNS-Host-Name-Attributes' = [guid]'72e39547-7b18-11d1-adef-00c04fd8d5cd'

    'Domain-Other-Parameters' = [guid]'B8119fd0-04f6-4762-ab7a-4986c76b3f9a'
    }

    $PropertySetGUIDs = $HT.GetEnumerator() | ForEach-Object {$_.Value}


    ####



    }

    process {

    $guidval = [Guid]$guid
    $bytearr = $guidval.tobytearray()
    $bytestr = ""

    foreach ($byte in $bytearr) {
    $str = "\" + "{0:x}" -f $byte
    $bytestr += $str
    }

    if ($extended) {
    #for extended rights, we can check in the configuration container
    $de = new-object directoryservices.directoryentry("LDAP://" + ([adsi]"LDAP://rootdse").psbase.properties.configurationnamingcontext)
    $ds = new-object directoryservices.directorysearcher($de)
    $ds.propertiestoload.add("displayname")|Out-Null
    $ds.filter = "(rightsguid=$guid)"
    $result = $ds.findone()
    }
    else {

    #### added by Ernest Brant

    if ($GUID -in $PropertySetGUIDs) {

    $result = ($HT.GetEnumerator() | where {$_.Value -eq $GUID} | select -ExpandProperty Name) + " (Property Set)"
    return $result

    }
    ####

    else {


    #Search schema for possible matches for this GUID
    $de = new-object directoryservices.directoryentry("LDAP://" + ([adsi]"LDAP://rootdse").psbase.properties.schemanamingcontext)
    $ds = new-object directoryservices.directorysearcher($de)
    $ds.filter = "(|(schemaidguid=$bytestr)(attributesecurityguid=$bytestr))"
    $ds.propertiestoload.add("ldapdisplayname")|Out-Null
    $result = $ds.findone()


    }


    }


    if ($result -eq $null) {
    $guid
    }
    else {
    if ($extended) {
    $result.properties.displayname
    }
    else {
    $result.properties.ldapdisplayname
    }
    }

    ####

    }

    end {}
    }

    Now if you give someone rights to a Property Set the function will return a meaning full name for the ObjectType

    Thanks
    Ernest Brant

    ReplyDelete
  2. I just came across this and have a need to run the Get-ADACL cmdlet that you reference and pipe to the new Convert-ADACL that you've provided here, however all reference links appear to be dead for the BSonPosh module. I found their Github page, but unfortunately it's not there either and I don't think there's any way I can contact them directly on there either. Any chance you have this laying around still and you could provide to me?

    ReplyDelete
  3. Thank you very much this helped me find what objecttype name was the weird number in AD.

    ReplyDelete