Gabe's Code

Stuff I've learned along the way


profile for Gabriel Luci at Stack Overflow, Q&A for professional and enthusiast programmers

Active Directory: Handling NT Security Descriptor attributes

Active Directory has several attributes that store permissions. The attribute type is called “NT Security Descriptor”, or String(NT-Sec-Desc). These are the attributes I know of:

The nTSecurityDescriptor attribute is a special one. It contains the access permissions for the AD object itself. It’s what you see when you look at the ‘Security’ tab in AD Users and Computers. Most methods of access AD objects will have an easy way to read this data. For example, DirectoryEntry has an ObjectSecurity attribute to read this. But there is no obvious way to work with the other ones.

In these examples, I’ll focus on the msDS-AllowedToActOnBehalfOfOtherIdentity attribute, since this is used when configuring Resource-Based Kerberos Constrained Delegation, which can, for example, help you solve PowerShell’s dreaded double-hop problem.

PowerShell makes this easier by making exposing a property called PrincipalsAllowedToDelegateToAccount in Get-ADUser and Set-ADUser, which just reads and writes the msDS-AllowedToActOnBehalfOfOtherIdentity attribute. Even if we try to access the raw data, it gives us an ActiveDirectorySecurity object. That’s handy.

PS C:\> $u = Get-ADUser SomeUsername -Properties "msDS-AllowedToActOnBehalfOfOtherIdentity"
PS C:\> $u."msDS-AllowedToActOnBehalfOfOtherIdentity".GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    ActiveDirectorySecurity                  System.Security.AccessControl.DirectoryObjectSecurity

But it’s not so obvious how to work with this attributes, and the others, in .NET. So here we’ll look at two options.

Getting the value from DirectoryEntry

The documentation for String(NT-Sec-Desc) says that we’ll get “A COM object that can be cast to an IADsSecurityDescriptor.” That’s exactly what we see when we try to get the value from DirectoryEntry: a COM object.

But to be able to use the IADsSecurityDescriptor interface, we will need to add a COM reference in Visual Studio to Active DS Type Library:

  1. Right-click ‘References’ in the Solution Explorer
  2. Click ‘Add Reference…’
  3. Click ‘COM’ on the left side
  4. Select “Active DS Type Library” from the list
Adding a COM reference
Adding a COM reference

Visual Studio will add a new DLL file to your compiled application called Interop.ActiveDS.dll. That is a wrapper around the Windows native activeds.dll. Make sure you deploy that DLL with your application.

Now we can do this:

var act = (IADsSecurityDescriptor)
            user.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"].Value;

You can just work with it like that, but it would be a little easier if we get it into a managed type.

Remeber I mentioned that DirectoryEntry gives us a handy property to read/write the ntSecurityDescriptor attribute called ObjectSecurity, which is of type ActiveDirectorySecurity. How does DirectoryEntry translate the ntSecurityDescriptor attribute into an ActiveDirectorySecurity attribute? And more importantly, how to we do it?

The source code for .NET Core is now available, so we can look at the source code for DirectoryEntry to find out. The key is in the GetObjectSecurityFromCache method. It does a few things:

  1. Cast the NativeObject property to IAdsPropertyList.
  2. Call IAdsPropertyList::GetPropertyItem to retrieve the attribute, and casts it to IADsPropertyEntry.
  3. Cast the first value in the IADsPropertyEntry.Values array to IADsPropertyValue.
  4. Get the raw byte array of the attribute using IADsPropertyValue.OctetString.
  5. Use the byte[] to create a new ActiveDirectorySecurity object.

Unfortunately, we can’t do step 5 ourselves because the constructor that it uses is marked internal.

internal ActiveDirectorySecurity(byte[] sdBinaryForm, SecurityMasks securityMask)
    : base(new CommonSecurityDescriptor(true, true, sdBinaryForm, 0))
{
    _securityMaskUsedInRetrieval = securityMask;
}

So what’s the next best thing?

ActiveDirectorySecurity inhertis from DirectoryObjectSecurity, but that’s an abstract class, so we can’t use that. But it does create a CommonSecurityDescriptor object from the byte[], and that is something we can do. So we’ll do that.

So we just need to get the raw, binary data of the attribute and push it into the constructor for CommonSecurityDescriptor. We can replicate steps 1-4 of what DirectoryEntry.GetObjectSecurityFromCache does, but it turns out that there’s an even easier way.

The IADsSecurityUtility interface is another COM object, designed to work with security descriptors. One of the methods it provides is ConvertSecurityDescriptor , which “converts a security descriptor from one format to another”. We can use that to convert an IADsSecurityDescriptor to a byte[].

var secUtility = new ADsSecurityUtility();
var byteArray = (byte[]) secUtility.ConvertSecurityDescriptor(
                            act,
                            (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_IID,
                            (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_RAW
                         );

var security = new CommonSecurityDescriptor(true, true, byteArray, 0);

Now you can read/write the security descriptor using the CommonSecurityDescriptor object. You’ll likely want to work with the DiscretionaryAcl property.

Note that you can also create a RawSecurityDescriptor object using new RawSecurityDescriptor(byteArray, 0). I really don’t know the difference between RawSecurityDescriptor and CommonSecurityDescriptor. If you know why you would use one over the other, let me know in the comments.

Writing the value back

If you plan on updating the value, you need to get it back into a byte[]. Both RawSecurityDescriptor and CommonSecurityDescriptor inherit from GenericSecurityDescriptor, which has a GetBinaryForm method, which will put a binary represenation of the security descriptor into a byte[] you already created. For example:

var descriptor_buffer = new byte[security.BinaryLength];
security.GetBinaryForm(descriptor_buffer, 0);

Then you can write it back into the DirectoryEntry object:

user.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"].Value = descriptor_buffer;
user.CommitChanges();

The code

Here is the code all together. I assume you already have a DirectoryEntry object called user and you have added a COM reference to “Active DS Type Library” to your project.

var act = (IADsSecurityDescriptor)
            user.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"].Value;
var secUtility = new ADsSecurityUtility();
var byteArray = (byte[]) secUtility.ConvertSecurityDescriptor(
                            act,
                            (int) ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_IID,
                            (int) ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_RAW
                         );

var security = new CommonSecurityDescriptor(true, true, byteArray, 0);

//modify security object

var descriptor_buffer = new byte[security.BinaryLength];
security.GetBinaryForm(descriptor_buffer, 0);

user.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"].Value = descriptor_buffer;
user.CommitChanges();

Getting the value from DirectorySearcher

There are a few cases where DirectorySearcher will give you a value in a different format than DirectoryEntry. This is one of those cases. In fact, DirectorySearcher makes it even easier on us, since it gives us the raw binary value without a fight. That means we don’t need to add a COM reference to our project.

This example will find an account with the username myUsername, and create a CommonSecurityDescriptor object:

var search = new DirectorySearcher(
    new DirectoryEntry(),
    $"(sAMAccountName=myUsername)"
);
//Tell it that we only want the one attribute returned
search.PropertiesToLoad.Add("msDS-AllowedToActOnBehalfOfOtherIdentity");

var result = search.FindOne();

var security = new CommonSecurityDescriptor(
    true,
    true,
    (byte[]) result.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"][0],
    0
);

If we need to write the value back, we need a DirectoryEntry object for the account. We can use SearchResult.GetDirectoryEntry() to do that:

var descriptor_buffer = new byte[security.BinaryLength];
security.GetBinaryForm(descriptor_buffer, 0);

var user = result.GetDirectoryEntry();
user.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"].Value = descriptor_buffer;
user.CommitChanges();

Leave a comment

Your email address is used to display your Gravatar, if applicable, and subscribe you to replies using the Mailgun web service, which you are free to unsubscribe from when you get any emails. Your email address will not be displayed publicly or shared with anyone else.