Python and the Active Directory SECURITY_DESCRIPTOR

By | May 11, 2014

A bit of history first,

I was looking into automating user account management at my place of work.  We have quite a large number of user accounts that change often, being K-12 school district, and have a few systems, each of which tracks its own set of user accounts.  These primarily include our Student Information System, Powerschool, a Microsoft Active Directory domain (we are primarily a Windows-PC based network), and our Google Apps for Education domain.  We looked into various single-sign on implementations and they were deemed either too expensive, too complex to implement, or lacking in certain features that we required.

It has always bothered me that creation of new user accounts required separate entry of the same data into multiple databases.  This manual process can be unnecessarily slow and prone to consistency issues.

So I decided to write some code that synchronizes the important information between the disparate databases.  The language I chose for this, mostly out of desire to learn it, is Python and the operating system is Linux.  Being a larger school district, we do have special licensing with Microsoft which basically allows us an unlimited number of virtual server 2008 or 2012 R2 servers, as well as MSSQL Server databases and development tools for .NET … Naturally, from the perspective of rapid development, C# or VB/.NET would probably be the first choice because of it’s robust libraries for integration with Active Directory.  I’m guessing Powershell would actually do a really good job of this too.  That said, I wanted this tool to be flexible enough to work with (or at least be modified to work with) other LDAP databases on free and open source platforms as well as with proprietary solutions.  That, along with my desire to expand my Linux OS skills and a new programming language led to the Linux/Python implementation decision.

All had been going wonderfully.. I used the cx_oracle Python module to integrate with the Powerschool database and pull the data into the accounts database which I have hosted on a MySQL/Ubuntu VM.  I had teacher and student account information syncing from Oracle to the accounts database and I had implemented a module using python-ldap to create user accounts and populate them into the correct AD security groups and OUs based on what information had been entered on the users into Powerschool.

At this point I ran into the dilemma this article is about.  Some of our user accounts need to be set so they cannot change their own passwords.  Unfortunately, this particular option, as I found out while trying to implement, is not directly manipulable through setting a specific LDAP attribute.

In Active Directory, the “user cannot change password” option is controlled by an access control list.  Access control lists of this type are seen in use in a lot of Windows-based solutions – they’re usually attached to NTFS-based file level security, but are also used in Active Directory to define access and auditing rights on objects.  They are typically attached to a larger structure, called a “Security Descriptor”, which contains additional security information (such as the owner) on the object.  Whenever you modify permissions to an object, you are modifying the one of the two ACLs located within the Security Descriptor.

The issue I ran into with Python over Linux is a generic LDAP connection does not provide you with Microsoft-specific methods for modifying the security descriptor attribute.  Your options for doing this appear to be limited to using ADSI’s WinNT provider, which provides an LDAP-like interface for querying AD with additional functionality, or to use Win32 COM calls.  While these options ARE doable with Python, they are only doable with Python running on a Windows machine.

Being somewhat stubborn about sticking with the Linux accounts server option, I did some digging and found that Microsoft does have some excellent documentation on how a security descriptor is structured.  I also discovered that, when opening an LDAP query with an account with domain administrative privileges (Unfortunately… This may turn some admins off to the idea), you can directly query the security descriptor attribute, ‘nTSecurityDescriptor’ from AD objects.

Microsoft’s information on security structures, including security descriptors, ACLs and more, can be found at the following link:

http://msdn.microsoft.com/en-us/library/cc230313.aspx

With this knowledge at hand, I set about writing some Python that is able to read and modify Active Directory’s nTSecurityDescriptor attribute as queried through a standard LDAP connection.  I want to post the code here, in the hopes that some other folks may find this useful in the future.  As it is currently implemented, I have successfully used it to set/unset the ‘user cannot change password’ flag for AD user accounts.

Disclaimers:

First of all, as I mentioned, the account querying AD must be a member of ‘Domain Admins’ in order to query the nTSecurityDescriptor attribute.  I tried locating the specific permission necessary but was unable to.  Use of a service account with ‘Domain Admin’ rights may be unacceptable for certain applications. I won’t go into too much detail on mitigating the risk in using an account with admin rights, as there are other individuals who would be more qualified than I to do so, however I have taken the following steps:

  1. I use LDAPS for the connection.
  2. I set a very strong password on the service account, and this is the only purpose I have for this particular service account.
  3. The server running the queries is located on the same network segment as the Active Directory domain controller that it is connecting to.  Ideally this network is isolated from any public, non-administrative network traffic.

Secondly, this code should not be considered ‘production’ code.  First of all, I am not a professional developer by any means, and I’m sure those of you who are will recognize that when you see how I’ve structured the code (I’m sure there must be more efficient, better ways to implement this).  If you use this code in your environment, plan on testing it thoroughly.  Additionally, I’ve implemented enough to set the ‘user cannot change password’ flag as well as set permissions directly on an object. I have not implemented inherited/inheritable permissions, though I leave room for it to be implemented in the future should the need arise.

Anyway, on to the code:

The actual code I use to set the user cannot change password flag (ADCon is an instance of a helper class that I used to establish some common methods for querying Active Directory via python-ldap):

The implementation of setUserCannotChangePasswordFlag is as follows:

Three different Access Control Entries (ACEs, individual entries in an ACL) on a user object control their ability to change their own password. One grant ACE explicitly grants permission for users to change their passwords. Two deny ACEs will take the place of the grant ACE when the ‘user cannot change password’ option is selected.

The code uses the following modules to manipulate the nTSecurityDescriptor. These modules are as follows:

SDOperations.py – Methods that take the security descriptor byte structure as queried from LDAP. You can use the aclBytes() method to return one of the ACL byte structures contained within the security descriptor byte structure:

ACLOperations.py – Methods for reading and manipulating ACL byte structures:

ACEOperations.py – Methods for reading and manipulating ACE byte structures:

SIDOperations.py – Methods for reading and manipulating Security Identifier byte structures:

One last important thing to note: python-ldap’s modifymodlist method does not play nicely when pushing the nTSecurityDescriptor back up to AD. You must force a ‘replace’ operation by manually creating a replace modlist and calling ldap.modify with that. See as follows for how I implemented that:

Hopefully somebody out there will find this useful.. If you have any questions feel free to drop me a comment.

11 thoughts on “Python and the Active Directory SECURITY_DESCRIPTOR

  1. Richard Sharpe

    Great article and I used this approach (and much of your code) for a PoC of modifying the machine account of a new Samba member server.

    BTW, shouldn’t getSidSubAuthorityCount be getSIDSubAuthorityCount by your usual naming scheme?

    Perhaps a project could be put up on GitLab that pulls all of this together as a set of Python classes?

    Reply
    1. Rob Post author

      Glad you found it useful…

      If I were to come up with something to put on GitLab I’d probably rewrite the code more closely following PEP8 guidelines.. I wrote this code up quick to get something done at work and used conventions not typically used in Python, before I was aware of them..

      Reply
  2. Hannes JvV

    Thank you for putting this code out there, and for the clear background information. I’ve been looking for a way to build these elaborate-looking Security Descriptors from Python without touching the horrid, platform-constrained mess that is Windows COM API.

    I’m a developer and sysadmin for our company, currently working on some Python 3 code for managing our AD security groups over LDAPS. I’m working with the ldap3 module (on PyPI) after getting frustrated with and giving up on python-ldap and python3-ldap. I have a Samba 4 instance configured as “new style” (not NT4) Active Directory Domain Controller, which serves our user accounts and security groups to all other internal services (which generally use LDAP, except for the separate Samba file server which authenticates over ADS). I administrate it with Microsoft RSAT tools — RSAT’s ADUC tool running on a domain-joined Windows PC/VM is probably what you want to use to Delegate Control to a specific admin user account for modifying Security Descriptors on entries in a specific OU of your AD tree (OU=staff in your case). I’m not sure what control item it would be, but I reckon it would be something like “Modify nTSecurityDescriptor” in the custom control delegation list.

    Reply
  3. Rob Post author

    It’s approved now. Thanks. Get a lot more spambots trying to post things than actual people, so…

    Reply
  4. Hannes JvV

    I ended up not using/adapting this code for our current needs after all. I was writing code to create new groups, so I thought I needed to populate the nTSecurityDescriptor field with some basic permissions data since it shows up as mandatory in the AD LDAP schema for Group class. Turns out it’s not so: strangely, I can just ignore this “mandatory” attribute and the (Samba) DC will construct it implicitly when commiting the new entry to the directory. Setting any dummy empty string value in nTSecurityDescriptor causes the server to reject it though.

    As for finer-grained permissions to adjust this field: I couldn’t find the specific permission type in ADUC’s Delegate Control wizard the way I expected. I conclude this is because of the way nTSecurityDescriptor’s semantics are tightly bound to the way AD works, so it’s handled different from LDAP fields in general: different parts of the security descriptor are associated with different access control items (eg. ability to reset password flag). Apparently it’s possible to read part (most?) of it by adjusting the bits of a certain flag sent along with the LDAP search request (StackOverflow question 40771503) so that you don’t request SACL info. I didn’t pursue this further since I’ve reached my goals for now, so I can’t say how to apply that to a python-ldap or ldap3 context.

    Sorry if I went somewhat off-topic there, but I hope it helps the next person researching this topic in the right direction.

    Reply
  5. PS

    Thanks Rob,
    Havent checked the full code. But if i just want to check if for a user “User cannot change password” is checked or not. Can i do that

    Reply
    1. Rob Post author

      I didn’t write a function that just ‘gets’ the flag but if you look at how the code for setting it works, it would be pretty easy to implement one.

      Reply
  6. Andreas Kasidis

    Rob, very detailed work. Thank you for that !

    I am looking for a way to simply gather/get a domain user’s ACEs using python (from a domain-joined linux machine) to audit them. For example to check if another domain user may have the “authorization”, through an ACE, to change the under-investigation user’s password.

    Is there a way that your code could help me with that? Or could I adapt it to suit my needs?
    Sorry for the silly question…

    Best,
    Andreas

    Reply
    1. Rob Post author

      Andreas – There aren’t any implemented functions specifically for that purpose, but you could use the existing code for parsing the ACL to assist you in finding the ACE entry that specifies which users/groups have change password permissions on the target user’s account. The ACE would likely contain the SIDs of the users/groups in AD that have permissions to make such modifications. You’d have to do a bit of digging to find which ACE describes that permission. It is all documented on Microsoft’s site.

      Reply
      1. Andreas Kasidis

        That’s great. When you find some free time, can you please point me to where in your code should I start for parsing the ACL?

        Reply
        1. Rob Post author

          You would use the aclBytes() function in SDOperations.py to pull the DACL (discretionary access control list) from the target user’s security descriptor. The DACL is what contains the ACEs (Access Control Entries) that define who has permissions to perform which operation on a user object. You’re looking for any ACEs with the SID of the person you want to check has permission to change the password as the trustee. There is a function in ACEOperations.py that queries out the trustee SID.

          The specific access right you’re probably looking for is an extended object access right (ref https://technet.microsoft.com/en-us/library/ff405676.aspx) – User-ChangePassword …

          Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

*