Mr. Script

Password Workout

Flex your analytical muscle with a little XOR-cise.

I’m not generally a very introspective person, but lately I’ve been reflecting on my life. In doing so, I’ve realized that I’ve always been fixing things. When I was a kid, I loved to take things apart and try to repair them. As a young adult I became very interested in computers: writing software, adding hardware, hooking them together—I did it all. I’d be willing to bet that most of you have similar backgrounds. I’ve no doubt that an analytical mind and heightened powers of deduction are common traits among network admin-types like us.

Homework, Part 1

Last month, I put your analytical powers to the test when I asked you to write a script using Active Directory Services Interface (ADSI) to reset every user password in a domain while simultaneously checking for—and, if necessary, changing—the PasswordNeverExpires flag. Let’s see how you did.

' ResetPW.vbs
Option Explicit
Dim objContainer, colUsers, lFlag
Set objContainer=GetObject("WinNT://domain")

ObjContainer.Filter=Array("User")

For Each colUsers in objContainer
IFlag=colUsers.Get("UserFlags")
If (lFlag AND &H10000) <> 0 Then
ColUsers.Put "UserFlags", lFlag XOR &H10000
End If
colUsers.Put "PasswordExpired", 1
colUsers.SetInfo
Next

As you can see, we didn’t change the script much. All we added was a simple IF…THEN section to check the UserFlags property. Setting it was easy; finding it was the part that likely forced you to stretch those analytical muscles. You see, when you use User Manager to change an individual user’s properties, setting the PasswordNeverExpires property is as simple as putting a check mark in the appropriate box.

Unfortunately, this box doesn’t directly translate into a user property that can be set to “True” or “False” like the PasswordExpired property. For good or bad, the PasswordNeverExpires property (which is actually the ADS_UF_DON’T_EXPIRE_PASSWD user flag) is part of a collection of user flags implemented as a preset constant value of ADS_USER_FLAG. This collection also contains ACCOUNTDISABLE, LOCKOUT, PASSWD_CANT_CHANGE, and many more.

Figure 1 shows all of these flags viewed in the Visual Basic Object Browser.

Figure 1. ADS_USER_FLAG contains a collection of user flags implemented as preset constant values.

If you don’t have VB, you can use Xray.exe to view it. Just browse to \WinNT\System32\activeds.tlb. The value of each flag is listed in both decimal and hexadecimal formats.

In order to determine if a particular user flag has been set, we use a process called ANDing. If you’ve taken the TCP/IP exam, you remember that ANDing is used by IP to determine if a host address is part of the local subnet. In line 8 of the script above, we take the value of the ADS_UF_DON’T_EXPIRE_PASSWD (&H10000 or 65536) and AND it with the value of the UserFlags property. If the flag is set, the AND operation returns 65536. If the flag isn’t set, the operation returns 0. Once we’ve determined that the flag is set, we must reset it without changing any of the other flags. This is accomplished via an exclusive OR operation (XOR).

You may be wondering why we don’t simply perform this XOR operation on every user account. Well, an XOR operation is really like a toggle. From a binary standpoint, if &H10000 is there, an XOR will remove it. If it isn’t there, an XOR will add it. If we run the operation on every account, it disables the flag on those accounts where it’s set, but enables it on every other account. Not a good idea. This is why we check for it with the AND operation first. (For more information from Microsoft’s Visual Basic Scripting Edition Language Reference on logical operators AND, OR, XOR, and NOT, go to http://msdn.microsoft.com/scripting/vbscript/doc/
vsidxlogical.htm
.)

A Bit About Binary Math

Military strategist Clausewitz once said, “Everything is very simple in war, but the simplest thing is difficult.” The same can be said for binary math. The math itself is very simple, but understanding it can be difficult. As such, performing logical operations on decimal numbers can get a bit confusing. It’s important to realize, however, that these are binary operations. If we approach them from that perspective, we can gain a clearer understanding. ADSI uses a 21bit number to hold all of the UserFlags. Although I complained about it in my column, this is actually a smart way to maintain multiple properties. Why? Binary math, that’s why! Instead of taking one or possibly even two bytes, each property is represented by a single bit. Figure 2 shows how your computer sees it.

Figure 2.The 21bit UserFlags property used by ADSI.

Each bit corresponds to a particular user flag value. That’s why there are no user flags with a value of 3, 5, 6, 7, etc.; these numbers require more than one bit. While 65,536 may seem like an obscure number, to the computer it’s just “bit 17.” Is bit 17 on or off? Hmm… I’ll check. It’s on. I guess the user’s password never expires! When we perform an XOR operation, it compares our “bit” to the comparable user flag “bit.” Table 1 shows this binary comparison.

Table 1. The DON'T_EXPIRE_PASSWD flag is removed via XOR... (Click the table to view a larger version.)

In an XOR operation, a bit can only be set in one number OR the other—not both. Since we are only “looking” at bit 17, all of the other bits are compared to zero and unaffected. Let’s perform the operation again and see what happens.

Table 2. ...and, as if by magic, it's back! (Click the table to view a larger version.)

Using XOR, you can toggle the DON’T_EXPIRE_PASSWD bit off and on all day long!
—Chris Brooke

Homework, Part 2

Now that we’ve reset all of the passwords, let’s go ahead and set up the security policy we created last month.

' SetPolicy.vbs
Option Explicit
Dim objDomain, lMinPWLength, lMinPWAge, lMaxPWAge,
lPWHistory

Set objDomain=GetObject("WinNT://domain")
lMinPWLength=8
lMinPWAge=0
lMaxPWAge=30
lPWHistory=10

objDomain.MinPasswordLength=lMinPWLength
objDomain.MinPasswordAge=lMinPWAge
objDomain.MaxPasswordAge=lMaxPWAge
objDomain.PasswordHistoryLength=lPWHistory
objDomain.SetInfo

Once we’ve bound to the DOMAIN container, we can set these and other properties by assigning the desired values directly to the properties.Once we execute the SetInfo method, the changes are saved to the domain. Really Cool Stuff! We’ve established that using scripts can accomplish tasks faster than using GUI tools such as User Manager. But some properties can’t be viewed with the GUI tools. ADSI to the rescue!

' CheckBadLogins.vbs
Option Explicit
On Error Resume Next

Dim objContainer, colUsers, iBadCount
Set objContainer=GetObject("WinNT://domain")

ObjContainer.Filter=Array("User")
For Each colUsers in objContainer
IBadCount=0
IBadCount=colUsers.BadLoginCount
If Err.Number<>0 Then Err.Clear
Wscript.Echo colUsers.Name & " has "
& iBadCount & " failed login attempts."
Next

You’ll notice I put a bit of error handling in this script. This is because the BadLoginCount property is only created when a login attempt has failed. Until then, it doesn’t exist. When we try to query it in line 9, it returns the error “The Active Directory property cannot be found in the cache.” By using ON ERROR RESUME NEXT and clearing the error immediately after it’s raised, we avoid unexpected script termination.

The WinNT namespace has lots more to offer. Indeed, if your “Scripting Suggestions” warrant it, we’ll come back to this at a later date. Now, if you’ll excuse me, I’m sure something is waiting to be fixed.

comments powered by Disqus
Upcoming Events

Redmond Tech Watch

Sign up for our newsletter.

I agree to this site's Privacy Policy.