LDAP and AD Security Descriptors
LDAP is used to search AD objects and change their attributes. Impacket uses two clients for different purposes. When following the code, first check which client the command calls. For the meaning of attribute changes and LDIF examples, see LDAP Modification Operations.
| Implementation | Commands | Suitable Operations |
|---|---|---|
impacket.ldap.LDAPConnection | GetADUsers, GetUserSPNs, GetNPUsers, GetLAPSPassword, findDelegation, etc. | Bind, search, simple modify |
ldap3 (init_ldap_session) | dacledit, owneredit, rbcd, badsuccessor, and addcomputer’s LDAPS path | Changes with SD controls, complex modify operations |
ASN.1 is in ldapasn1.py (RFC 4511).
AD security descriptors are in ldaptypes.py.
Extended-rights GUIDs are in msada_guids.py.
impacket/acl.py is not for AD.
It handles SMB file FileSecInformation and SID resolution through LSARPC.
Use ldaptypes when working with nTSecurityDescriptor.
URLs and Connections
Section titled “URLs and Connections”LDAPConnection(url, baseDN='', dstIp=None, signing=True, timeout=None):
| Scheme | Port | Signing Default | TLS |
|---|---|---|---|
ldap:// | 389 | On | None |
ldaps:// | 636 | Off (the channel itself uses TLS) | pyOpenSSL TLS_METHOD |
gc:// | 3268 | Off | None (Global Catalog) |
dstIp overrides only the endpoint. The Kerberos SPN ldap/<URL host>
still uses the hostname.
LDAPS channel binding puts the RFC 5929 tls-server-end-point: prefix and
the peer certificate’s SHA-256 in a structure, then passes its MD5 as
channel_binding_value. NTLM Type3 AV_CHANNEL_BINDINGS and Kerberos AP-REQ
CheckSumField.Bnd consume it. On a DC that requires CBT, bind fails without it.
Use CheckLDAPStatus to test this.
login(..., authenticationChoice='sasl'):
| Choice | Mechanism |
|---|---|
simple | user@domain or DOMAIN\user plus password |
sicilyNegotiate | Sicily NTLM (Type1 in negotiate, Type3 in response) |
sasl (default) | GSS-SPNEGO + NTLMv2; adds a MIC to Type3 and uses SPNEGOCipher when signing |
kerberosLogin():
- Look for
ldap/<host>withCCache.parseFile - If absent, call
getKerberosTGT→getKerberosTGS - SPNEGO + AP-REQ with checksum
0x8003; CONF|INTEG when signing, or CBT inBndfor LDAPS - BindRequest SASL
GSS-SPNEGO
With signing enabled, send() writes a four-byte length plus a wrapped message.
Kerberos uses GSS_Wrap_LDAP; NTLM uses SPNEGOCipher.encrypt. recv_raw() decrypts it.
The ldap3 path (impacket.examples.utils.init_ldap_session) passes
NTLM/Kerberos to ldap3’s SASL and uses
security_descriptor_control for modifications. dacledit and related commands use this path.
Search / modify
Section titled “Search / modify”LDAPConnection.search defaults to wholeSubtree and neverDerefAliases.
Filters follow RFC 4515 (& | !, =, >=, <=, :=, *, \XX).
Paging uses control OID 1.2.840.113556.1.4.319
(SimplePagedResultsControl, default size 1000) and iterates until
the server cookie is empty. Removing filters, as with -stealth on a large domain,
can truncate results without paging.
Modify operation constants: ADD=0, DELETE=1, REPLACE=2, INCREMENT=3.
Use control OID 1.2.840.113556.1.4.801 (SDFlagsControl) for SD changes.
Flags: OWNER=1, GROUP=2, DACL=4, SACL=8. dacledit reads only the DACL
(0x04) in many cases.
Security Descriptors
Section titled “Security Descriptors”This is a self-relative SD from [MS-DTYP] 2.4.6.
SR_SECURITY_DESCRIPTOR Revision, Control OffsetOwner / OffsetGroup / OffsetSacl / OffsetDacl OwnerSid, GroupSid, Sacl, DaclACL AceCount + ACE[]ACE AceType, AceFlags, AceSize + bodyCommon ACEs:
| AceType | Class | Purpose |
|---|---|---|
| 0x00 | ACCESS_ALLOWED_ACE | Ordinary allow ACE; also used to enumerate RBCD SIDs |
| 0x01 | ACCESS_DENIED_ACE | Deny |
| 0x05 | ACCESS_ALLOWED_OBJECT_ACE | Object GUID; DCSync / ResetPassword |
| 0x06 | ACCESS_DENIED_OBJECT_ACE | Object deny ACE |
The combination of ACE_OBJECT_TYPE_PRESENT / INHERITED_OBJECT_TYPE_PRESENT
and GUIDs matters for object ACEs. Do not remove one merely because its SID matches another.
Representative masks:
| Name | Value | Meaning |
|---|---|---|
WRITE_DACL | 0x40000 | Change DACL |
WRITE_OWNER | 0x80000 | Change owner |
READ_CONTROL | 0x20000 | Read SD |
ADS_RIGHT_DS_CONTROL_ACCESS | 0x100 | Extended right |
WRITE_PROP | 0x20 | Write attributes |
CREATE_CHILD / DELETE_CHILD | 0x1 / 0x2 | Child objects |
Examples from msada_guids.EXTENDED_RIGHTS:
00299570-246d-11d0-a768-00aa006e0529— User-Force-Change-Password1131f6aa-9c07-11d1-f79f-00c04fc2dcd2— DS-Replication-Get-Changes1131f6ad-9c07-11d1-f79f-00c04fc2dcd2— Get-Changes-All (one part of DCSync)ab721a53-1e2f-11d0-9819-00aa0040529b— User-Change-Password
RECALC_ACE_SIZE (True by default) recalculates ACE sizes on serialization.
Because this can change the padding held by AD, backups and restores
retain the original binary blob (dacledit’s JSON backup).
Command Mapping
Section titled “Command Mapping”| Operation | Attribute / Target | Command |
|---|---|---|
| Edit DACL | nTSecurityDescriptor | dacledit |
| Change owner | SD Owner | owneredit |
| RBCD | msDS-AllowedToActOnBehalfOfOtherIdentity (contains an SD) | rbcd |
| Enumerate delegation | UAC, msDS-AllowedToDelegateTo, etc. | findDelegation |
| LAPS | ms-Mcs-AdmPwd / Windows LAPS + GKDI | GetLAPSPassword |
| dMSA | Delegated MSA attributes | badsuccessor |
An RBCD value is a self-relative SD, not a SID array. The mask in create_allow_ace
is 983551 (equivalent to full control). flush clears the entire attribute;
remove deletes only the ACE for the specified SID.
Checks When Making Changes
Section titled “Checks When Making Changes”- Decide whether to change
LDAPConnectionorldap3first - Reading
nTSecurityDescriptorwithout an SD control omits data - Preserve ACE order, unknown ACEs, and inheritance flags on a round trip
- Related tests:
tests/SMB_RPC/test_ldap.py

