Gabe's Code

Stuff I've learned along the way

Active Directory

more...

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

Active Directory: Handling NT Security Descriptor attributes in C#

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 accessing 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 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 attribute (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. Can we use that?

The only public constructor for ActiveDirectorySecurity just creates an empty object. That’s not helpful. There is a SetSecurityDescriptorBinaryForm method that could help us, but only if we had the security descriptor in a byte array. Can we get that?

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 adSecurity = new ActiveDirectorySecurity();
adSecurity.SetSecurityDescriptorBinaryForm(byteArray);

Now you can read/write the security descriptor using the ActiveDirectorySecurity object. You’ll likely want to use the GetAccessRules method. For example, this code will loop through the access rules and just print them out to the console:

foreach (ActiveDirectoryAccessRule rule in adSecurity.GetAccessRules(true, false, typeof(SecurityIdentifier))) {
    Console.WriteLine($"{rule.IdentityReference} {rule.AccessControlType} {rule.ActiveDirectoryRights} {rule.ObjectType}");
}

The ObjectType is a Guid that refers to either a specific attribute, a property set (a way of assigning permissions to a group of attributes at once), or an extended right (if the ActiveDirectoryRights property is set to ExtendedRight).

Writing the value back

If you plan on updating the value, you need to get it back into a byte[]. Fortunately, that’s made easy with the GetSecurityDescriptorBinaryForm method. For example:

var newByteArray = adSecurity.GetSecurityDescriptorBinaryForm();

Then you can write it back into the DirectoryEntry object:

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

The code

Here is the code all together. This assumes 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 adSecurity = new ActiveDirectorySecurity();
adSecurity.SetSecurityDescriptorBinaryForm(byteArray);

//modify security object here

var newByteArray = adSecurity.GetSecurityDescriptorBinaryForm();

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

Getting the value from DirectorySearcher

There are a few cases (in general) where DirectorySearcher will give you values 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 an ActiveDirectorySecurity 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 adSecurity = new ActiveDirectorySecurity();
adSecurity.SetSecurityDescriptorBinaryForm((byte[]) result.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"][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 newByteArray = adSecurity.GetSecurityDescriptorBinaryForm();

var user = result.GetDirectoryEntry();
user.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"].Value = newByteArray;
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.
Comments are moderated. Your comment will be reviewed by a human before being posted to this page. Any comment made for the purpose of advertising will not be approved.