Mr. Script
Password Workout
Flex your analytical muscle with a little XOR-cise.
- By Chris Brooke
- 12/01/2000
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.