In Active Directory there are some very confusing value formats. The title of "Most Confusing" should probably be awarded to the NtSecurityDescriptor attribute. It exists on LDAP objects in Active Directory and describes permissions against the object in Security Descriptor (binary) form. It contains the ACEs, owner information, and inheritance assigned to the object. That's the same information you'd see if you navigate to the object in Active Directory Users and Computers and go to the user's properties then navigate to the security tab. However, there are also certain property settings that relate to ACEs in the security descriptor. Such as whether or not a user has the ability to change their password.

When you query the NtSecurityDescriptor using a typical LDAP client (Be it PHP, Python, or Perl) you receive a binary blob value. To decode it you can use a method to convert to hexadecimal (such as PHPs bin2hex()). You're then left with a LOT of work to do to get it to a usable form. Luckily the format of a security descriptor is extremely well documented in Microsoft's Open Specification Document regarding data types (MS-DTYP) under section 2.5.1. The problem is that there exists very few examples of a full implementation for encoding/decoding this specific data type open-source world. Or more specifically where I was trying to do it...PHP.

So I created a full Security Descriptor encoder/decoder (along with a SDDL parser) in PHP for a LDAP library I work on in my free time. The result was several classes needed to abstract this data type into something workable:

Security Descriptor Class

I also have it fully documented to show typical usage:

AD Permissions Doc

Another common tripping point when trying to query a typical NtSecurityDescriptor via a LDAP client is permissions. There is a lot of misinformation out there stating that you need to be a Domain Administrator to query this attribute. You do not. The misunderstanding is how Active Directory handles things when you query for this attribute. The attribute itself contains 4 separate pieces of information: DACL (Discretionary ACL), SACL (System ACL), Owner, and Primary Group. By default when you query Active Directory it will attempt to retrieve all parts of the security descriptor. However, your account may not have access to all parts (Most notably, the SACL). When this happens AD decides to simply not return anything. Which isn't very helpful and is just plain confusing.

To get around the limitation above and still query the NtSecurityDescriptor you need to use a LDAP control to specify you do not want the SACL. The control is LDAP_SERVER_SD_FLAGS_OID. The only problem with this in PHP is the value this control expects a BER encoded ASN.1 structure. The easiest way to do this is cheat a little and use sprintf() to generate the value. A simple PHP example to select everything except the SACL using this control:

$user = 'user@example.local';
$pass = 'secret';
$server = 'dc1.example.local';

$ldap = ldap_connect($server);
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);

ldap_bind($ldap, $user, $pass);

// Set a special LDAP control to specify what parts of the security descriptor you want...
$sdFlags = 7;
$ctrl1 = array(
    "oid" => "1.2.840.113556.1.4.801",
    "iscritical" => true,
    // How should this value be set???
    "value" => sprintf("%c%c%c%c%c", 48, 3, 2, 1, $sdFlags)
if (!ldap_set_option($ldap, LDAP_OPT_SERVER_CONTROLS, array($ctrl1))) {
    echo "Failed to set server controls";

$searchUser = "user";
$dn = "dc=example,dc=local";
$attr = array("ntsecuritydescriptor");

$sr = ldap_search($ldap, $dn, $filter, $attr);
$info = ldap_get_entries($ldap, $sr);

// Should contain ntSecurityDescriptor binary blob...

Also, as noted in the example above, this control seems to interfere with LDAP paging. I'm not sure if this is just a PHP oddity or not. But it's worth mentioning.

Next Post Previous Post