Stuff I've learned along the way
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:
fRSRootSecurity
msDFS-LinkSecurityDescriptorv2
msDS-AllowedToActOnBehalfOfOtherIdentity
msDS-GroupMSAMembership
nTSecurityDescriptor
pKIEnrollmentAccess
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.
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:
Visual Studio will add a new DLL file to your compiled application called
Interop.ActiveDS.dll
. That is a wrapper around the Windows nativeactiveds.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
).
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();
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();
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