Using Scripts to Manage Active Directory Security

Part 1: Extended Rights

This is the first of a two-part series that will introduce you to using scripts to manage Active Directory Security. This document (Part 1) will discuss extended rights, and demonstrate how you can grant users permission to do such things as change or reset someone else’s password. Part 2  discusses Active Directory property sets, and show you can delegate the ability to read and write predefined user attributes (for example, a set of attributes revolving around user logon).

Note that this article assumes that you have a basic understanding of security descriptors, particularly discretionary access control lists (DACLs) and access control entries (ACEs).

Part 1: Granting Extended Rights

Whenever people try to explain what Active Directory is (or isn’t), they invariably use the analogy, “Active Directory is like a phone book.” That’s a perfectly valid analogy. After all, phone books are used to store relevant information about people, businesses, government organizations, and other entities. Likewise, Active Directory is used to store relevant information about users, computers, printers, and other entities. Compare the ability to quickly and easily look up information in a phone book with the ability to quickly and easily look up information in Active Directory, and you have a very handy – and very appropriate – metaphor.

Of course, an equally valid analogy would be to say that “Active Directory is like the file system.” The file system is composed of securable objects: you can control, to a very fine degree, the files and folders users can access, and what type of access each user is allowed. If you want to block a group of users from accessing a folder and its contents you can do so. If you want to give another group of users read-only access to that folder, you can do that as well. You can even allow User A unlimited access to the folder … well, except for this file, which User A is allowed to read, but not to modify. All of these access permissions are managed using the security descriptor attached to each file and folder.

Active Directory operates in a similar fashion. Objects stored in Active Directory – user accounts, computer accounts, what-have-you – are all securable objects: you can control, to a very fine degree, the objects users can access, and what type of access each user is allowed. Active Directory objects all have a security descriptor very similar to the security descriptor attached to files and folders. For example, in Active Directory Users and Computers, right-click a user account and then click Properties. In the resulting dialog box, click the Security tab. You should see something very similar to this:

Security descriptor dialog box

As you can see, it’s a security descriptor, one that lists trustees (users or groups who have been granted or denied access to the object), along with the permissions (Read, Write, Full Control, etc.) they have been granted (or denied). Allowing for the differences between an Active Directory object and a file or folder, this security descriptor looks very much like a security descriptor you would find attached to a file system object.

Note. If you’re interested in a script that can read the security descriptor attached to an Active Directory object or in other Active Directory security scripts, click here.

Active Directory Extended Rights

Much of Active Directory security involves giving a user or group read or write access to an object, or to a specified property of that object. (For example, you can give someone the right to modify another user’s home phone number without giving them the right to modify any other attribute of that account.) However, there are additional rights – known as extended rights – that don’t involve read/write access to individual attributes; instead, they involve carrying out specific tasks, such as changing a user’s password. There are 39 extended rights, including 4 that apply to user accounts. These four extended rights are shown in Table 1. A complete set of extended rights can be found on the Extended Rights Reference page.

Table 1. Extended Rights Applicable to User Accounts

Extended Right Description
   
Change Password
{ab721a53-1e2f-11d0-9819-00aa0040529b} [Text]
Enables you to change the password on a user account. You must know the user’s current password in order to provide them with a new password. [Text]
Reset Password
{00299570-246d-11d0-a768-00aa006e0529} [Text]
Enables you to reset the password on a user account. You do not need to know the user’s current password in order to provide them with a new password. [Text]
Receive As
{ab721a56-1e2f-11d0-9819-00aa0040529b} [Text]
Exchange right that enables you to receive mail as a given mailbox. [Text]
Send As
{ab721a54-1e2f-11d0-9819-00aa0040529b} [Text]
Exchange right that enables you to send mail as the mailbox. [Text]

If you look closely at Table 1, you might have notice a few crazy-looking things like this: {ab721a53-1e2f-11d0-9819-00aa0040529b}. These are object GUIDS: globally unique identifiers. Extended rights are actually objects within Active Directory; consequently, when you grant someone the right to change a user’s password, you must specify the object GUID for the change password extended right in your script. But don’t worry: we’ll show you how to do that.

Writing a Script to Grant an Extended Right to a User

Let’s take a look at a typical script that grants an extended right to a user. In this sample script, we’ll give Ken Myer (FABRIKAM\kmyer) the right to reset the password for user Rob Young.

Warning. If you have a weak stomach, you might not want to look at the script right away; there’s no doubt that security scripts such as this look very intimidating. However, keep in mind two things. First, a lot of the script involves setting constant values; on top of that, a considerable amount of the script is boilerplate. You can use this script as a template; all you have to do is replace values as needed.

Second, we’ll explain what each section of the script does, explanations which should make this much easier to digest. In other words, don’t panic. At least not yet.

Here’s what the script looks like:

Const ADS_ACETYPE_ACCESS_ALLOWED_OBJECT = &H5
Const ADS_FLAG_OBJECT_TYPE_PRESENT = &H1
Const ADS_RIGHT_DS_CONTROL_ACCESS = &H100

Set objSdUtil = GetObject("LDAP://CN=Rob Young, OU=Finance, DC=fabrikam, DC=Com")
Set objSD = objSdUtil.Get("ntSecurityDescriptor")
Set objDACL = objSD.DiscretionaryACLSet

Set objAce = CreateObject("AccessControlEntry")
objAce.Trustee = "FABRIKAM\kmyer"
objAce.AceFlags = 0
objAce.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT
objAce.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT
objAce.ObjectType = "{00299570-246d-11d0-a768-00aa006e0529}"
objAce.AccessMask = ADS_RIGHT_DS_CONTROL_ACCESS
objDACL.AddAce objAce

objSD.DiscretionaryAcl = objDACL
objSDUtil.Put "ntSecurityDescriptor", Array(objSD)
objSDUtil.SetInfo

Assuming you didn’t faint, let’s go ahead and walk through the script section by section. Although the task might seem overwhelming, a script such as this can be broken down into four sections:

  • Defining a series of constants
  • Retrieving the security descriptor from the object
  • Creating a new access control entry (ACE) and setting the properties of that ACE
  • Writing the new ACE back to the security descriptor

Section 1: Defining a Series of Constants

This first step is actually optional; you don’t have to use constants in your code. Instead, you could use the values those constants represents. For example, instead of this line of code:

objAce.AccessMask = ADS_RIGHT_DS_CONTROL_ACCESS

You could use this line of code:

objAce.AccessMask = &H100

So why do we use constants if we don’t have to? Two reasons. First, constants make the script easier to read. A constant named ADS_ACETYPE_ACCESS_ALLOWED_OBJECT, cryptic as it might be, is still easier to understand than a value like &H5. Equally important, you can hop on the Internet, search for ADS_ACETYPE_ACCESS_ALLOWED_OBJECT and find information relevant to what you’re doing. Who knows what might happen if you went to the Internet and searched for &H5? (We were too scared to try.)

In other words, step 1 of our extended rights script simply involves defining the constants we’ll need when creating the new ACE:

Const ADS_ACETYPE_ACCESS_ALLOWED_OBJECT = &H5
Const ADS_FLAG_OBJECT_TYPE_PRESENT = &H1
Const ADS_RIGHT_DS_CONTROL_ACCESS = &H100

How did we know which constants to use, and what the values of those constants are? Why, we just looked at the Active Directory Security Descriptor Constants  page, of course.

Section 2: Retrieving the Security Descriptor of the Object

Granting an extended right is actually a simple process: all we’re doing is adding a new ACE to a security descriptor. To do that, however, we first need to programmatically get hold of that security descriptor. Again, there’s nothing magical about that: if you want to use Active Directory Users and Computers to add an ACE to an object then you’re going to have to bring up the security descriptor for that object. The same thing is true here. These lines of code retrieve the security descriptor for the Rob Young user account:

Set objSdUtil = GetObject("LDAP://CN=Rob Young, OU=Finance, DC=fabrikam, DC=Com")
Set objSD = objSdUtil.Get("ntSecurityDescriptor")
Set objDACL = objSD.DiscretionaryACL

All we’re doing here is binding to the Rob Young user account by calling GetObject and passing the ADsPath to the object, exactly the way we would bind to the account to retrieve, say, the phone number or office address. After binding to the object we use the Get method to retrieve the security descriptor, and then set an object reference (objDACL) to the discretionary access control list (DACL) for that security descriptor.

Best of all, this is all boilerplate: you can copy this code as-is and paste it into your scripts. The only thing you’ll need to change is the ADsPath (unless, of course, by some amazing coincidence you actually do want to bind to LDAP://CN=Rob Young, OU=Finance, DC=fabrikam, DC=Com).

Section 3: Creating and Configuring a New ACE

This is the heart-and-soul (or meat-and-potatoes) of our extended rights script. Remember, when we grant an extended right to a user we’re simply creating a new ACE on a security descriptor. In this section we create the new ACE and configure the various properties of that ACE; in section 4, we then write the ACE back to the security descriptor.

Creating the ACE in memory (something we’ll discuss in a minute) requires just one line of code:

Set objAce = CreateObject("AccessControlEntry")

After the ACE has been created, we then assign values to the ACE properties. These properties (and an explanation of the values we assign to them) are discussed in the following section.

Trustee. The trustee is simply the user (or group) to which you want to grant the extended right. Simply specify the name using the domain\name format. For example, to assign Ken Myer (kmyer) as the Trustee use this code:

objAce.Trustee = "FABRIKAM\kmyer"

AceFlags. AceFlags is a bitflag property that indicates whether the ACE can be inherited by other objects. In this sample script we’re granting the right to change the password on a single user account; that means there is no child object that can inherit this ACE. Consequently, we set AceFlags to 0:

objAce.AceFlags = 0

AceType. The AceType property indicates whether this ACE will grant the trustee access to the object or whether the ACE will be used to deny the trustee access to the object. In general, it’s a good idea to avoid explicitly denying users or groups access to objects; troubleshooting permission problems can be complicated when there is a mixture of access allowed and access denied ACEs. Instead of giving everyone access and then denying access to certain accounts, provide access only to accounts that are supposed to have access.

To grant an extended right, set the AceType to ADS_ACETYPE_ACCESS_ALLOWED_OBJECT, the standard permission given when working with extended rights or property sets:

objAce.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT

Remember, make sure you defined this constant at the beginning of the script. Sample code found in the ADSI SDK often omits these constants; that’s because much of that sample code is written in Microsoft® Visual Basic®. Programming languages such as Visual Basic have access to the ADSI type libraries, and thus “know” what ADS_ACETYPE_ACCESS_ALLOWED_OBJECT is equal to. Scripting languages such as VBScript do not have access to type libraries; because of that, you need to explicitly tell VBScript what ADS_ACETYPE_ACCESS_ALLOWED_OBJECT is equal to.

Flags. The Flags property indicates whether the ACE has an object type and/or an inherited object type. The object type – as we’ll discuss in the following section – represents the right being granted. The inherited object type refers to the type of objects (such as user accounts) that will inherit the ACE. This is something we’ll talk about more in the section of this article titled Granting an Extended Right Over All the Users in an OU.

For a script that grants an extended right over a single user account we need to specify only that an object type is present in the ACE; as we noted, there is no inherited object type. Thus:

objAce.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT

ObjectType. When granting an extended right, the ObjectType must be set to the GUID that represents the right being granted. As we saw in Table 1, the object GUID for resetting a user’s password is {00299570-246d-11d0-a768-00aa006e0529}. Thus we assign that GUID to the ObjectType:

objAce.ObjectType = "{00299570-246d-11d0-a768-00aa006e0529}"

AccessMask. The AccessMask is a bitflag property containing all the access privileges being granted (or denied) by the ACE. As we saw earlier, Active Directory objects support at least some of the same permissions – Read, Write, Full Control – that you see when working with the security descriptors on files or folder. When working with an extended right, however, you must assign the ADS_RIGHT_DS_CONTROL_ACCESS right to the AccessMask. Therefore, every extended rights script you write will include a line of code similar to this:

objAce.AccessMask = ADS_RIGHT_DS_CONTROL_ACCESS

After configuring all the ACE properties, we then call the AddAce method to add the new ACE to the DACL:

objDACL.AddAce objAce

Note that all this merely adds the ACE to the copy of the DACL that exists in memory. With ADSI scripts, a copy of the object you are working on is created in memory (in the local cache), and operations you perform take place on that copy. The actual object itself remains unchanged until you call the SetInfo method to write the changes back to the object. If our script ended right here, the new ACE would not be added to the Rob Young user account in Active Directory; that’s because we haven’t called SetInfo. To actually apply the new security descriptor to the Active Directory object, we need to move on to Section 4.

Section 4: Writing the New ACE to the Security Descriptor

After the ACE has been created and added to the cloned security descriptor (the one that exists only in memory on our local computer), the only thing left to do is write that revised security descriptor back to the actual Rob Young user account object. That’s what we do in these lines of code:

objSD.DiscretionaryAcl = objDACL
objSDUtil.Put "ntSecurityDescriptor", Array(objSD)
objSDUtil.SetInfo

As you might recall, objSD is an object reference to the security descriptor on the Rob Young user account. In the first line of code, we set the DiscretionaryAcl property to the new (and improved) DACL we just created. This gives us a complete and fully-functional security descriptor in memory (remember, up until now we’ve been working out of the local cache and working on a copy of the actual security descriptor). At this point all that’s left to do is write the changes we made back to the real security descriptor, the one in Active Directory; that’s what the Put method does. After calling Put we then call the SetInfo method to save the changes to the user account. The moment we call SetInfo, Ken Myer will have the right to reset Rob Young’s password. It’s just that easy, and just that quick.

Granting an Extended Right Over All the Users in an OU

There might be times when you want to give a single user (such as Ken Myer) the right to reset the password for one or two additional user accounts. More likely, however, you will want to give Ken the right to reset passwords for a large group of users, such as all the users in a specific OU. That way any time a user in that OU forgets his or her password they can simply contact Ken and ask him to provide them with a new password. This is the whole idea of delegation of control in Active Directory: you grant a specific user permission to carry out a specific task within a specific OU.

Active Directory makes it very easy to grant an extended right that applies to all the users in an OU. Instead of modifying the security descriptor for each user in the OU, you modify the security descriptor on the OU itself, and configure the ACE so that the security settings are propagated to each and every user account in the OU. Let’s take a look at a script that does this very thing, and then discuss how it works:

Const ADS_ACETYPE_ACCESS_ALLOWED_OBJECT = &H5
Const ADS_FLAG_OBJECT_TYPE_PRESENT = &H1
Const ADS_FLAG_INHERITED_OBJECT_TYPE_PRESENT = &H2
Const ADS_RIGHT_DS_CONTROL_ACCESS = &H100
Const ADS_ACEFLAG_INHERIT_ACE = &H2

Set objSdUtil = GetObject("LDAP://OU=Finance, DC=fabrikam, DC=Com")
Set objSD = objSdUtil.Get("ntSecurityDescriptor")
Set objDACL = objSD.DiscretionaryACL
Set objAce = CreateObject("AccessControlEntry")

objAce.Trustee = "FABRIKAM\kmyer"
objAce.AceFlags = ADS_ACEFLAG_INHERIT_ACE
objAce.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT
objAce.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT OR ADS_FLAG_INHERITED_OBJECT_TYPE_PRESENT
objAce.ObjectType = "{00299570-246d-11d0-a768-00aa006e0529}"
objACE.InheritedObjectType = "{BF967ABA-0DE6-11D0-A285-00AA003049E2}"
objAce.AccessMask = ADS_RIGHT_DS_CONTROL_ACCESS
objDACL.AddAce objAce

objSD.DiscretionaryAcl = objDACL
objSDUtil.Put "ntSecurityDescriptor", Array(objSD)
objSDUtil.SetInfo

If this script looks familiar, you’re already catching on to the fine art of reading and interpreting Active Directory security scripts. In fact, this script differs from the first script we showed (the one that granted the right to reset the password only for the Rob Young user account) in just four respects.

First of all, this new script obviously does not bind to the Rob Young user account; instead, it binds to the Finance OU:

Set objSdUtil = GetObject("LDAP://OU=Finance, DC=fabrikam, DC=Com")

Second, we noted in the original script that there was no need for any child objects to inherit the ACE; that’s because a single, solitary user account doesn’t have any child objects that can inherit the ACE. That’s not the case with this revised script. Instead, we’re assigning our new ACE to the OU, and we want all sorts of child objects – in particular, all the user accounts – to inherit this ACE. Therefore, we have to configure the AceFlags property to ensure that the ACE is passed on to the appropriate child objects:

objAce.AceFlags = ADS_ACEFLAG_INHERIT_ACE

As we noted, we want this ACE to be inherited by user accounts and only user accounts. Therefore, we need to set the InheritedObjectType property to the GUID for the user account object. That’s exactly what we did here, with {BF967ABA-0DE6-11D0-A285-00AA003049E2} representing the GUID for the user account object in Active Directory. (In case you’re wondering, we were able to find this GUID by looking up the User object in the Active Directory Schema Reference on MSDN.)

objACE.InheritedObjectType = "{BF967ABA-0DE6-11D0-A285-00AA003049E2}"

Finally, we need to define and employ an additional constant (ADS_FLAG_INHERITED_OBJECT_TYPE_PRESENT ); this is required because this ACE has both an object type and an inherited object type. So, first we define the constant:

Const ADS_FLAG_INHERITED_OBJECT_TYPE_PRESENT = &H2

We then modify the Flags property to indicate that we have both an object and an inherited object in the ACE. Note that in the code below we use the logical OR operator. For our purposes, OR functions more like the word and: it simply means we have both an object and an inherited object. In other words:

objAce.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT OR ADS_FLAG_INHERITED_OBJECT_TYPE_PRESENT

As you can see, granting an extended right over an entire OU isn’t all that different from granting an extended right over a single user account. And, again, much of the code is boilerplate that can be used any time you want to grant an extended right that covers an entire OU.

What’s Next?

If you’re thinking to yourself, “OK, that was tough, but at least now I know everything there is to know about using scripts to manage Active Directory security,” well …. Extended rights are a very important part of delegating control of Active Directory, and you’ve been introduced to a number of concepts – things like Trustee, AccessMask, AceFlags, etc. – which will pop up in other Active Directory security scripts. As good as all that is, however, we still have a ways to go. Next month we’ll take a look at property sets in part 2 , and how property sets can be used to give a user the right to change certain types of information (email information, user logon information, general user account information) for Active Directory objects.