Operator On The Wire
← Back to Knowledge Base
OOTW / Chapter IV - Active Directory / 02. Protocols / LDAP / Techniques

Parsing Security Descriptors

Security descriptors store object ownership, DACLs, and ACEs. Raw LDAP can request nTSecurityDescriptor, but PowerView and Impacket are usually better because they parse rights, GUIDs, inheritance, object ACEs, and principals into usable output.

export DC=10.10.10.200
export BASE='DC=ootw,DC=local'
export DOMAIN=ootw.local
export USER='student'
export PASS='student'

Attempt raw LDAP read:

LDAPTLS_REQCERT=never ldapsearch -LLL -x -H ldaps://$DC:636 -D "$DOMAIN\\$USER" -w "$PASS" -b "CN=svc_web,OU=Service Accounts,OU=OOTW Lab,DC=ootw,DC=local" -s base nTSecurityDescriptor

Keep raw LDAP security descriptor pulls small.

LDAPTLS_REQCERT=never ldapsearch -LLL -x -H ldaps://$DC:636 -D "$DOMAIN\\$USER" -w "$PASS" -E pr=1000/noprompt -b "$BASE" '(|(objectClass=domain)(objectClass=organizationalUnit)(objectClass=groupPolicyContainer))' sAMAccountType distinguishedName objectSid nTSecurityDescriptor

Parse a base64 nTSecurityDescriptor blob into a raw discretionary ACL.

$b64 = '<BASE64_NTSECURITYDESCRIPTOR>'
$bytes = [Convert]::FromBase64String($b64)
$rsd = New-Object System.Security.AccessControl.RawSecurityDescriptor($bytes, 0)
$rsd.DiscretionaryAcl

Convert a base64 nTSecurityDescriptor blob into SDDL.

$b64 = '<BASE64_NTSECURITYDESCRIPTOR>'
$bytes = [Convert]::FromBase64String($b64)
$sd = [System.Security.AccessControl.RawSecurityDescriptor]::new($bytes, 0)
$sd.GetSddlForm('All')

Request owner, DACL, and SACL-aware output from a Windows host:

Get-ADObject -Identity 'CN=svc_web,OU=Service Accounts,OU=OOTW Lab,DC=ootw,DC=local' -Properties nTSecurityDescriptor |
    Select-Object -ExpandProperty nTSecurityDescriptor

Read with Impacket:

dacledit.py -action read -target svc_web "$DOMAIN/$USER:$PASS" -dc-ip $DC

Read a DN:

dacledit.py -action read -target-dn "CN=svc_web,OU=Service Accounts,OU=OOTW Lab,DC=ootw,DC=local" "$DOMAIN/$USER:$PASS" -dc-ip $DC

Parse for ownership and interesting ACEs:

Import-Module .\PowerView.ps1
$Acl = Get-DomainObjectAcl -Identity svc_web -ResolveGUIDs
$Acl | Select-Object SecurityIdentifier,ActiveDirectoryRights,ObjectAceType,AceType,InheritanceType,IsInherited
$Acl | Where-Object { $_.ActiveDirectoryRights -match 'GenericAll|GenericWrite|WriteDacl|WriteOwner|ExtendedRight|WriteProperty' }

Resolve SIDs from parsed ACEs:

Import-Module .\PowerView.ps1
Get-DomainObjectAcl -Identity svc_web -ResolveGUIDs |
    Select-Object -ExpandProperty SecurityIdentifier |
    Sort-Object -Unique |
    ForEach-Object { ConvertFrom-SID $_ }

Find ACLs where a specific principal has rights:

Import-Module .\PowerView.ps1
$Sid = ConvertTo-SID 'OOTW\student'
Get-DomainObjectAcl -LDAPFilter '(objectClass=*)' -ResolveGUIDs |
    Where-Object { $_.SecurityIdentifier -eq $Sid } |
    Select-Object ObjectDN,ActiveDirectoryRights,ObjectAceType,AceType,IsInherited

Grant FullControl:

dacledit.py -action write -rights FullControl -principal $USER -target svc_web "$DOMAIN/$USER:$PASS" -dc-ip $DC

Remove FullControl:

dacledit.py -action remove -rights FullControl -principal $USER -target svc_web "$DOMAIN/$USER:$PASS" -dc-ip $DC

PowerView direct reads:

Import-Module .\PowerView.ps1
Get-DomainObjectAcl -Identity svc_web -ResolveGUIDs
Get-ObjectAcl -DistinguishedName 'CN=svc_web,OU=Service Accounts,OU=OOTW Lab,DC=ootw,DC=local' -ResolveGUIDs

Filter for one principal:

Import-Module .\PowerView.ps1
$Sid = ConvertTo-SID 'OOTW\student'
Get-DomainObjectAcl -Identity svc_web -ResolveGUIDs |
    Where-Object { $_.SecurityIdentifier -eq $Sid }