Hey, Scripting Guy!We All Scream for Security Descriptors
The Microsoft Scripting Guys
Download the code for this article: ScriptingGuys05.exe (115KB)
Sometimes things are just more complicated than they should be. Take a simple little task like deciding which ice cream to order. Back when the Scripting Guys were growing up this was easy; for the most part, you had your choice between chocolate and vanilla. If you went to a fancy place you might get strawberry. But take that same question today: what kind of ice cream do you want? Vanilla? Do you mean Old-Fashioned Vanilla, French Vanilla, or Vanilla Bean? And when you say "chocolate" does that mean Dutch Chocolate, German Chocolate, or Chocolate Fudge?
This same sort of thing crops up every now and then with system administration scripting. Take security descriptors, for example. We get scores of e-mails from people asking what seems to be a very simple question: how can I write a script that manages security descriptors on files and folders? After all, scripts are so good at handling system management tasks and carrying them out quickly and easily. How hard could it be to write a script to read from and write to the security descriptor on a file or folder?
Well, unfortunately, it’s not quite as easy as it sounds. It’s not an impossible task by any means, but it can be a bit complicated, especially when you try to create a new security descriptor. And even the tasks that are reasonably simple—such as reading a security descriptor—tend to be anything but intuitive. It turns out that security descriptors are neither chocolate nor vanilla; instead, they’re something just a bit more esoteric. Think pistachio.
Therefore, in this column we’re going to walk you through the sometimes bewildering world of security descriptor scripting. We make no claims that this column is an exhaustive reference on security descriptor scripting, but it’s a start. Just remember: this can be a crazy and complicated subject area. You’ve been warned.
We should also note that this is definitely not a primer on the best way to secure files and folders. We’re going to explain the basic mechanics behind writing scripts for managing security descriptors, but that’s all we’re going to explain. If you need information on how to make the best use of those scripts (or information on how to make the best use of other tools for managing security descriptors) we recommend you visit the TechNet Security Center.
This is a Primer on Security, Right?
No, we said that this is not a primer on security. In fact, we’re going to assume that you already have a basic understanding of security descriptors and we’re going to use terms like trustee and discretionary access control list (DACL) without going into great depth about them. Why? Because if you don’t have a good understanding of security descriptors then you shouldn’t write scripts to manage them. But that’s true of everything: there’s no sense writing scripts to manage event logs or IIS unless you first understand how they work.
What if you don’t have a good understanding of security descriptors but are willing to learn? Well, you can take a look at the article "How Security Descriptors and Access Control Lists Work" in the Windows Server™ 2003 Technical Reference.
The only conceptual information we’re going to present is a couple of pictures that help you map scripting properties to the items found in the user interface. For example, suppose you right-click a folder in Windows® Explorer and choose Properties. If you click on the Security tab, you’ll see something like Figure 1.
Figure 1** Security Properties **
If you click the Advanced button, a dialog box similar to Figure 2 will pop up. Although the process of scripting security descriptors can be confusing, there’s definitely a logic behind it, and it does map very closely to the things you already know.
Figure 2** Advanced Security Settings **
Is It All Good News?
Well, not exactly all the news is good. We’ve chosen to use Windows Management Instrumentation (WMI) as our scripting technology, which has several advantages, beginning with the fact that it works just fine on Windows 2000, something that’s not entirely true when it comes to security descriptors and the Active Directory® Service Interface (ADSI).
However, WMI is not without its quirks with regards to security descriptors. For example, whenever you work with a security descriptor you’ll spend a lot of time using the Win32_Trustee and Win32_ACE WMI classes. Is that a problem? No, but you do need to understand that Win32_Trustee and Win32_ACE are not your typical WMI classes. For example, here’s a little script that attempts to retrieve a collection of all the access control entries (ACEs) on a computer:
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & _ strComputer & "\root\cimv2") Set colAces = objWMIService.ExecQuery _ ("Select * from Win32_ACE") Wscript.Echo colAces.Count
How many ACEs do you suppose this script will return? Hundreds? Thousands? Tens of thousands? Try zero.
That’s right, zero. That’s because Win32_Trustee and Win32_ACE are abstract classes that can’t be used to return data directly. Instead, you have to work with individual files and folders, grabbing the security descriptor from each one and then retrieving the ACEs and trustees for that particular file or folder. That’s not necessarily hard; it’s just different. If you want to use WMI to return all the services on a computer, you can do so without having to go out and grab each service individually. That’s not the case when it comes to security descriptors.
Confused? Well, don’t worry too much. There is definitely some weirdness associated with scripting security descriptors, but today we’re going to focus on reading a security descriptor, which isn’t too terribly weird. (Although, again, it’s a bit different from the WMI scripts you’re used to writing.)
Here’s a script that returns some information from a security descriptor:
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & _ strComputer & "\root\cimv2") Set objFile = objWMIService.Get _ ("Win32_LogicalFileSecuritySetting=‘c:\ _ scripts’") If objFile.GetSecurityDescriptor(objSD) = 0 Then Wscript.Echo "Owner: " & _ objSD.Owner.Domain & "\" & _ objSD.Owner.Name End If
This script retrieves the owner of the folder C:\Scripts, but more importantly, it also illustrates the basic method for retrieving security descriptor information from a file or a folder.
We’re going to show you how to retrieve information from the DACL of an object. The DACL represents all the access permissions granted or denied to the object. A second piece to the security descriptor puzzle, the system access control list (SACL), determines the actions that are audited and logged each time an object is accessed. For now we’re going to pretend the SACL doesn’t exist and focus exclusively on the access permissions found in the DACL.
This script starts out like most of our WMI scripts do: we assign the value dot (.) to a variable named strComputer, indicating that we want to connect to the local machine. If we wanted to connect to a remote computer, we’d assign the name of that remote machine to strComputer. We then go ahead and connect to the WMI service, which takes only two lines of code:
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & _ strComputer & "\root\cimv2")
In a typical WMI script this is where we’d call the ExecQuery method and retrieve a collection of WMI objects. But, like we said, that won’t work when we’re dealing with security descriptors. Instead, we have to bind to an individual file or folder, which is what we do with this line of code:
Set objFile = objWMIService.Get _ ("Win32_LogicalFileSecuritySetting=‘c:\ _ scripts’")
As you can see, we call the Get method and bind to the instance of the Win32_LogicalFileSecuritySetting class that’s equal to C:\Scripts.
Incidentally, you use the same approach when working with files as you do when working with folders: you simply pass the complete path name when calling the Get method. For example:
Set objFile = objWMIService.Get _ ("Win32_LogicalFileSecuritySetting=‘c:\ _ scripts\test.doc’")
Next we need to retrieve the security descriptor for the folder C:\Scripts. To do this, we call the GetSecurityDescriptor method and then check the return value. If the return value is 0, we managed to retrieve the security descriptor; if the return value is anything but 0, the method call failed:
If objFile.GetSecurityDescriptor _ (objSD) = 0 Then
You might have noticed that when we called the GetSecurityDescriptor method we had to supply an object reference; in this case we named our object reference objSD. You don’t have to name your object reference objSD. It’s just a regular old variable, so you can call it anything you want. Just make sure you supply such an object reference, because if our method call succeeds, objSD will give us a direct connection to the security descriptor for C:\Scripts.
Word of warning: it’s right about here that things start to get a little murky. Our object reference (objSD) refers to an instance of the Win32_SecurityDescriptor class. Having made a connection, the only thing we’re going to do in this script is echo back the value of the Name and Domain properties. However, the Win32_SecurityDescriptor class doesn’t actually have properties called Name and Domain. Instead, we’re going out and grabbing an instance of the Win32_Trustee class, which does have properties for the owner’s name and domain.
Incidentally, here’s the code that echoes back the name of the folder owner:
Wscript.Echo "Owner: " & objSD.Owner.Domain & _ "\" & objSD.Owner.Name
And yes, that is rather odd syntax: objSD.Owner.Domain and objSD.Owner.Name. Typically you’d expect to see something like this: objSD.Domain. So why don’t we see that? Well, like we said, Domain isn’t a property of the Win32_SecurityDescriptor class. Instead, we use the Owner property, which is really an embedded instance of the Win32_Trustee class, which does have properties named Domain and Name. Writing things out as objSD.Owner.Domain is a kind of shorthand that means "The Domain property of the Owner property (which is really an object) that belongs to the object reference objSD."
If this doesn’t make sense, well, don’t worry about it; it will become clearer as you begin working with security descriptor scripts. For now, just remember that the boilerplate code we just looked at enables you to retrieve the owner of a file or folder.
Yes, we know—that’s all fine and dandy, but there’s more to a security descriptor than just the owner. What we really want to know is who has access to the folder and, equally important, what kind of access do they have? So can we get to that information using a script? If so, how?
Yes, we can use a script to determine who has access to a folder and even to determine what kind of access they have. As for how we do that, we have one word for you: bitmasks. Rather than offer a dictionary definition of bitmasks, let’s try an analogy instead.
Do you have to listen to this story? No. But understanding bitmasks is a key part of working with security descriptors, so it probably wouldn’t hurt.
Suppose you have a fancy sundae maker that enables you to build your own sundae from several different flavors of ice cream. Let’s suppose further that this sundae maker is fully scriptable. In designing the object model for your sundae maker, you’d probably set up separate properties for each available flavor. That way, you could mix and match flavors using code similar to this:
objSundae.Chocolate = True objSundae.Vanilla = True objSundae.Strawberry = False objSundae.Sauerkraut = False objSundae.MapleNut = True
Actually, you did such a nice job with the sundae maker’s object model, we wish you would have designed the object model for the Win32_ACE class. (The two properties we’re about to look at—AceFlags and AccessMask—are both properties of Win32_ACE.) For better or worse, however, someone else designed Win32_ACE. And that person, instead of creating separate properties for each access mask, used a bitmask (a single property that can hold multiple property values at the same time). On a technical level there are undoubtedly advantages to using a bitmask; we’re just not sure if there are advantages to someone trying to write a script that reads a security descriptor.
Don’t worry; we’re going to explain how all this works. Let’s go back to the object model for our sundae maker and assume we have only one property—Flavor—with which to work. If that were the case, then how could we let people order multiple flavors, and how could we keep track of which flavors they ordered?
Who said bitmask? Good for you—you’re absolutely right. If we make Flavor a bitmask property, it can hold all sorts of values and combinations of values. To turn Flavor into a bitmask we first have to assign each ice cream flavor a unique integer value. For example, take a look at Figure 3.
Now, suppose someone wanted a sundae with just Maple Nut ice cream. That’s easy; all they’d have to do is set the value of the Flavor property to 16, which just happens to be the integer value assigned to Maple Nut:
objSundae.Flavor = 16
But suppose someone else wanted chocolate, vanilla, and strawberry. We have three flavors, but just one property. How do we deal with that?
We’re going to answer that question by asking a question. Keeping in mind that no single ice cream has a value of 7, what kind of ice cream does this person want in their sundae?
objSundae.Flavor = 7
That’s right: chocolate, vanilla, and strawberry. If you look at the table of values, you’ll see that there’s only one way to get a 7: chocolate (1) plus vanilla (2) plus strawberry (4). That’s how a bitmask can track multiple property values all at once. Want a strawberry and sauerkraut sundae? Then use this line of code:
objSundae.Flavor = 12
Again, the only way to get the value 12 is to add strawberry (4) plus sauerkraut (8). That’s what a bitmask is all about. Yes, you assign each individual property a unique value, but you also ensure that any combination of those properties will also result in a unique value. For example, Figure 4 is not a good example of a bitmask.
Why not? Well, suppose we get the value 5? That’s Maple Nut, right? Or is it chocolate (1) plus sauerkraut (4)? That’s why the numbers in a bitmask often seem crazy (oft-times 2x power). That’s good, except for one thing. Let’s say you have 20 or 30 possible flavors of ice cream. That means you might run your script and get back a value like this: 194729102710. What kind of advanced mathematics are you going to have to use to figure out the only possible way to derive a value like that?
Relax, you don’t have to use any math at all. To dust off an old analogy, think of property values in a bitmask as being like switches. If we want vanilla, then the switch for vanilla (with a value of 2) is turned on; if we don’t want vanilla, then the switch for vanilla is turned off. Using "bitwise logic" we can determine which flavors a user ordered merely by checking the value of each switch. For example, this code checks to see if the value of the vanilla switch is on:
If objSundae.Flavor AND 2
We aren’t going to worry about the details of bitwise logic. For now, all we care about is that the preceding line of code is True if the switch for vanilla is on and False if the switch for vanilla is off. That’s it. By repeating this process for each flavor we can check each switch and easily determine the exact combination of ice cream flavors ordered by the user.
So what’s the point of our little ice cream story? (Other than to point out that being able to create an ice cream sundae using a script would be very cool.) Well, the properties AceFlags and AccessMask—which are both crucial to deciphering a security descriptor—are bitmask properties. Taking a look at a script that retrieves those properties should demonstrate why we had to take that little side trip into bitwise logic. The code for retrieving the AceFlags and AccessMask properties is shown in Figure 5.
Figure 5 Retrieve AceFlags and AccessMask Properties
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set objFile = objWMIService.Get("Win32_LogicalFileSecuritySetting=‘c:\scripts’") If objFile.GetSecurityDescriptor(objSD) = 0 Then For Each objAce in objSD.DACL Wscript.Echo "Trustee: " & objAce.Trustee.Name Wscript.Echo "Ace Flags: " & objAce.AceFlags Wscript.Echo "Access Mask: " & objAce.AccessMask Wscript.Echo Next End If
As you can see in Figure 5, the script starts out exactly the way our get-owner-name script started (although in this one we don’t bother to grab the owner name). After we retrieve the security descriptor we run this line:
For Each objAce in objSD.DACL
This line of code retrieves the value of the DACL property, which happens to be a collection of all the ACEs found in the discretionary access control list.
For each ACE in the DACL we echo back the values of the Trustee.Name, AceFlags, and AccessMask properties. For any given ACE those values will look something like this:
Trustee: Users Ace Flags: 19 Access Mask: 1179817
In other words, getting back the values is easy; what we really need to do, however, is make sense of those values. And that’s where bitwise logic comes in. For example, AceFlags contains the properties you see in Figure 6.
Figure 6 AceFlags Properties
Our sample ACE returned an AccessFlags value of 19, meaning it must be a combination of OBJECT_INHERIT_ACE (1) plus CONTAINER_INHERIT_ACE (2) plus INHERITED_ACE (16). We could, of course, create a script that checked for every possible value (1; 1 + 2; 1 + 4; 1 + 8; and so on), but that’s not the way to handle bitmasks. The code in Figure 7 is the way to handle them.
Figure 7 Don't Do the Math
Wscript.Echo "Ace Flags:" If objAce.AceFlags AND 1 Then Wscript.Echo vbTab & "Child objects that are not containers inherit permissions." End If If objAce.AceFlags AND 2 Then Wscript.Echo vbTab & "Child objects inherit and pass on permissions." End If If objAce.AceFlags AND 4 Then Wscript.Echo vbTab & "Child objects inherit but do not pass on permissions." End If If objAce.AceFlags AND 8 Then Wscript.Echo vbTab & "Object is not affected by but passes on permissions." End If If objAce.AceFlags AND 16 Then Wscript.Echo vbTab & "Permissions have been inherited." End If
Again, we’re using the logical AND operator to test the value of each "switch." For example, to see if the switch for OBJECT_INHERIT_ACE (which has a value of 1) is on we use this line of code:
If objAce.AceFlags AND 1 Then
If this statement is True (meaning the switch is on), then we echo back an appropriate message; otherwise we do nothing and go on and check the next switch:
Wscript.Echo vbTab & "Child objects that" & _ "are not containers inherit permissions."
Make sense? If you add the code in Figure 7 to your script and run it, you’ll get output similar to this:
Trustee: Users Ace Flags: Child objects that are not containers inherit permissions. Child objects inherit and pass on permissions. Permissions have been inherited. Access Mask: 1179817
Of course, there’s still the issue of the AccessMask coming out as 1179817. But that’s OK; AccessMask is just another bitmask, albeit with a few more property values (see Figure 8).
Figure 8 AccessMask Values
Again, we use bitwise logic to test whether a switch is on or off. Want to know if a particular ACE includes the ability to write extended attributes (value 16)? Then use this code:
If objAce.AccessMask 16 Then Wscript.Echo vbtab & _ "Write extended attributes" End If
Figure 9 lists the code that checks all the AccessMask switches.
Figure 9 AccessMask Switches
Wscript.Echo "Access Masks:" If objAce.AccessMask AND 1048576 Then Wscript.Echo vbtab & "Synchronize" End If If objAce.AccessMask AND 524288 Then Wscript.Echo vbtab & "Write owner" End If If objAce.AccessMask AND 262144 Then Wscript.Echo vbtab & "Write ACL" End If If objAce.AccessMask AND 131072 Then Wscript.Echo vbtab & "Read security" End If If objAce.AccessMask AND 65536 Then Wscript.Echo vbtab & "Delete" End If If objAce.AccessMask AND 256 Then Wscript.Echo vbtab & "Write attributes" End If If objAce.AccessMask AND 128 Then Wscript.Echo vbtab & "Read attributes" End If If objAce.AccessMask AND 64 Then Wscript.Echo vbtab & "Delete dir" End If If objAce.AccessMask AND 32 Then Wscript.Echo vbtab & "Execute" End If If objAce.AccessMask AND 16 Then Wscript.Echo vbtab & "Write extended attributes" End If If objAce.AccessMask AND 8 Then Wscript.Echo vbtab & "Read extended attributes" End If If objAce.AccessMask AND 4 Then Wscript.Echo vbtab & "Append" End If If objAce.AccessMask AND 2 Then Wscript.Echo vbtab & "Write" End If If objAce.AccessMask AND 1 Then Wscript.Echo vbtab & "Read" End If
And here’s our new and improved output:
Trustee: Users Ace Flags: Child objects that are not containers inherit permissions. Child objects inherit and pass on permissions. Permissions have been inherited. Access Masks: Synchronize Read security Read attributes Execute Read extended attributes Read
Light at the End of the Tunnel
Not only are we almost finished, but we’ve saved the easiest properties—Trustee.Name, Trustee.Domain, and AceType—for last.
Let’s start with AceType, which simply tells us whether an ACE allows access or denies access. Because we’re dealing with the DACL, AceType can have only one of two values: 0 for an ACE that allows access, and 1 for an ACE that denies access. That makes checking the AceType pretty trivial:
If objAce.AceType = 0 Then strAceType = "Allowed" Else strAceType = "Denied" End If Wscript.Echo "Ace Type: " & strAceType
Very cool. All that’s left now is to determine the user or group that each ACE actually applies to. Here we use code similar to that used to retrieve the owner of the security descriptor. In fact, the Win32_ACE class also has a property (Trustee) that’s really an embedded instance of the Win32_Trustee class. For each ACE in our DACL we can use code like this to determine the trustee:
Wscript.Echo "Trustee: " & _ objAce.Trustee.Domain & "\" & _ objAce.Trustee.Name
For all intents and purposes, we now have the information we need to reconstruct the security descriptor using a script. Seeing as how we have all the pieces, creating a script that returns the information found on a file system security descriptor is pretty easy. The final product is available for download from the TechNet Magazine Web site (microsoft.com/technet/technetmag/code06.aspx). Sure, it’s a bit longer than most Script Center scripts, but that’s because we have to check the values of so many bitwise switches. And while it might be a tad long, it’s fairly straightforward.
So What’s Next?
We’ve only scratched the surface when it comes to scripting security descriptors. We’re now able to read that security descriptor, but we haven’t even mentioned such things as modifying an ACE, adding an ACE, deleting an ACE, and so forth. (In addition, we haven’t mentioned the SACL, the thing we decided not to even mention.) We’ll get to those sooner or later.
According to legend, the ice cream cone was introduced during the 1904 St. Louis World’s Fair. So, is this column the best thing that’s happened to ice cream since then? Write to us at email@example.com and let us know.
In the meantime, if you need information on how to make the best use of scripts for managing security descriptors or any other such tools, we recommend you visit the TechNet Security Center.
The Microsoft Scripting Guys spend most of their time at the beach...oh, um, no, we mean: The Scripting Guys are hard-working members of the Microsoft Windows Server User Assistance team. To find out more, go to Scripting Guys.
© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.