Disable RC4-HMAC (And Others) in Active Directory

My long-distance pal Mathias Jessen pointed out this article today on Twitter, in which the article's author attempts to make us all shudder in fright at the idea that Microsoft Active Directory has a terrifying security vulnerability that will cause the world's corporate infrastructure to crumble and shatter humanity's socio-political status quo as script-kiddies take over the now destabilized Earth.

OK, it's not all that bad...

Of the several encryption types that AD supports for Kerberos authentication, RC4-HMAC is among the oldest and the weakest.  The reason the algorithm is still supported?  You guessed it... backwards compatibility.  The problem is that in this (perhaps ill-conceived, but hindsight's 20/20) implementation of RC4-HMAC, as outlined in RFC 4757, the encryption key that is used is the user's NT/MD4 hash itself!  What this means is that all I need is the NT hash of another user, and by forcing an AD domain controller to negotiate down to RC4-HMAC, I can be granted a Kerberos ticket as that other user.  (Getting another user's NT hash means I've probably already owned some random domain-joined workstation, for instance.)

As you probably already know, an NT hash is essentially password-equivalent and should be treated with the same level of sensitivity as the password itself.  And if you didn't know that, then you should read my earlier blog post where I talk a lot more about this stuff.

This was a deliberate design decision - that is, Microsoft is not going to just patch this away.  The reason they chose to do it this way was to ease the transition from NTLM to Kerberos back around the release of Windows 2000.  Newer versions of Windows such as Vista, 2008, 2008 R2, etc., use newer, better algorithms such as AES256_HMAC_SHA1 that do not use an NT hash.  Newer versions of Windows on a domain will automatically use these newer encryption types by default, but the older types such as the dreaded RC4-HMAC are still supported and can be used by down-level clients... or malicious users pretending to be down-level domain members.

As an administrator, you're free to turn the encryption type off entirely if you do not need the backwards compatibility.  Which you probably don't unless you're still rockin' some NT 4.0 servers or some other legacy application from the '90s.

(Which probably means most companies...)

Edit the Default Domain Controllers (or equivalent) Group Policy and look for the setting:

Computer Configuration > Policies > Windows Settings > Security Settings > Local Policies > Security Options > Network Security: Configure encryption types allowed for Kerberos.

This setting corresponds to the msDS-SupportedEncryptionTypes LDAP attribute on the domain controller computer objects in AD.  Enable the policy setting and uncheck the first three encryption types.

And of course, test in a lab first to ensure all your apps and equipment that uses AD authentication can deal with the new setting before applying it to production.

CustomAddADUser v1.0

I uploaded a new project on Github today, named CustomAddADUser.

If you have a lot of Active Directories and/or employee account records to maintain, or even if you don't but you're just obsessive compulsive like me, you might require a certain level of completeness, accuracy, and use of custom attributes that the old Active Directory Users and Computers doesn't really give you.  For instance, let's say that your HR system requires that you populate the "Employee ID" attribute on your user accounts.  The ADUC GUI doesn't provide that as part of the "new user" dialog. You have to create the user first, then enable "Advanced Features," then go and click on them again, open their properties sheet, go to the "Attribute Editor" tab, and type it in there.  And even then it's still prone to typos, which will make your identity management a struggle and your HR system won't be able to accurately track the user accounts.  You can't just mark the "employeeID" attribute as mandatory unless you want to modify the AD schema. And even if you did that, you still can't ensure that the employee ID matches a very particular ID format that your company uses.

Well CustomAddADUser aims to make all that possible.

Almost everything is customizable via a configuration file, including which attributes are mandatory, the application's icon, the window title, the company logo that appears on the "About" tab, the help text that appears on the About tab, and the regular expressions that are used to validate the input. Furthermore, you'll notice as you enter the user's account details that names are automatically capitalized and trimmed for you, etc., to encourage a clean and consistent user database.

(Gah people that don't capitalize the first letters of names drives me up the wall!)

So let's say that you need all your employees to have their employee ID attributes filled out, and your company uses employee IDs that look like F4348277 for full-time employees, and P4348277 for part time employees.  No problem, just edit the config file to use this regex pattern:

<add key="employeeIDRegex" value="\b[fp]\d{7}\b" />

Now the application will not allow the user to be created until the employee ID matches that regex pattern.  It will politely remind the administrator that the attribute needs to match that pattern.

All the other attributes have their own regex patterns too. If you don't care about the format of the attribute, just leave the regex pattern as (.+) to match anything.

Additionally, since it's very rare that AD users are created and aren't assigned to any security groups, you can easily copy the security group members from another existing user during creation.  When you change the "Create in" drop-down list, the list of available users from which to copy group memberships changes accordingly to show only users who are also in that branch of the directory.

The app is about 36 hours old so I will likely continue adding new features pretty quickly.  And of course, I wouldn't have put it on Github if I wasn't welcoming to anyone who wanted to submit bugs, feature requests, etc.  One of my missions with this application is to make it significantly better than the standard ADUC Users and Computers interface that people might actually want to use it... so I will be adding more features to it.

Writing and Distributing a Powershell Module via GPO

I recently wrote a bunch of Powershell Cmdlets that were related to each other, so I decided to package them all up together as a Powershell module.  These particular Cmdlets were tightly integrated with Active Directory, so it made sense that they would often be run on a domain controller, or against a domain controller, usually by a domain administrator.

First, to create a Powershell script module, you create a folder, and then place your *.psm1 and *.psd1 files in that folder and they must have exactly the same name as that folder. (You can also compile your Powershell module into a DLL, but let's just talk about script modules today.)  So for instance, if you name your module "CloudModule," you would create a directory such as:

C:\Program Files\PSModules\CloudModule\

And then you'd place your files of the same name in that folder:

C:\Program Files\PSModules\CloudModule\CloudModule.psm1
C:\Program Files\PSModules\CloudModule\CloudModule.psd1

I think that a subdirectory of Program Files works well, because the Program Files directory is protected from modification by non-administrators and low-integrity processes.

The psd1 file is the manifest for the PS module. You can easily create a new manifest template with the New-ModuleManifest cmdlet, and customize the template to suit your needs. The module's manifest is simply the "metadata" for the Powershell module, such as what version of Powershell it requires, other prerequisite modules it requires, the module's author, etc. The cool thing is that with current versions of Powershell, just by saying:

RequiredModules = @('ActiveDirectory')

in your manifest file, Powershell will automatically load the ActiveDirectory module for you the first time any cmdlet from your module is run. So you really don't need to put an Import-Module or a #Requires -Module anywhere.

The psm1 file is the collection of Powershell Advanced Functions (cmdlets) that make up your module. I would recommend naming all of your cmdlets with a common theme, using supported verbs (Get-Verb) and a common prefix. You know how the Active Directory module does Get-ADUser, Remove-ADObject, Set-ADGroup, etc.? You should do that too. For example, if you work for McDonald's, do Show-McSalary, Deny-McWorkersComp, Stop-McStrike, etc.

Now that you've got your module directory and files laid out, you need to add that path to your PSModulePath environment variable so that Powershell knows where to look for it every time it starts. Do not try to cheat and put your custom module in the same System32 directory where Microsoft puts their standard PS modules.

I decided that I wanted all my domain controllers to automatically load this custom module. And I don't want to go install and maintain this this thing on 50 separate machines.  So for a centrally-managed solution, let's utilize Group Policy and SYSVOL.

First, let's put something like this in the Default Domain Controllers (or equivalent) GPO:


(*Click to enlarge*)

And secondly, let's put our Powershell module in:

C:\Windows\SYSVOL\sysvol\Contoso.com\scripts\PSModules\CloudModule\

Of course, since this directory is replicated amongst all domain controllers, we only need to do this on one domain controller for the files to become available on all domain controllers.  Likewise, any time you update the module, you only need to update it on one DC, and all DCs will get the update.

Lastly, since this Powershell module is only for Domain Administrators, and SYSVOL by design is a very public place, let's protect our module's directory from prying eyes:

Careful that you only modify the permissions of that one specific module directory... you don't want to modify the permissions of anything else in Sysvol.

SceCli Warning Event 1202, Domain Local Groups, and Alias_Object sAMAccountType, OH MY

I haven't posted in forever, so today it's time to get back to my roots by troubleshooting some good old-fashioned Active Directory problems. I saw this issue in the wild recently, so I thought I'd write about it while it was still fresh on my mind.

An admin came to me asking for help, and explained how one of his customers was experiencing warning events in the Application event log every 5 minutes on their domain controllers, but not their member servers, looking like this:


Warning 1202


Log Name:      Application
Source:        SceCli
Date:          5/1/2014 5:25:56 PM
Event ID:      1202
Task Category: None
Level:         Warning
Keywords:      Classic
User:          N/A
Computer:      DC01.CONTOSO.COM
Description:   Security policies were propagated with warning. 0x4b8 : An extended error has occurred.


A couple of things will let you know immediately that this problem has something to do with Group Policy. First, that the event is logged every 5 minutes on each domain controller, which just so happens to be the GPO refresh interval on DCs. Second, that the event source is SceCli, which loosely stands for Security Configuration Client-Side Extension. You'll also notice an accompanying Error ID 7016 in the GroupPolicy event log that gives you even less helpful information:


Error 7016 GroupPolicy


If you were to search for information about this event on the web, you'll no doubt find Microsoft's KB 324383: Troubleshooting SCECLI 1202 Events, and find that the article is almost completely irrelevant to this scenario, except for the little bit at the end that tells you how to enable Winlogon logging. We need to enable that logging on one of the DCs to get a better understanding of what's going on. To enable said logging:

  • Locate and then click the following registry subkey:
    • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\GPExtensions\{827D319E-6EAC-11D2-A4EA-00C04F79F83A}
  • Add or Edit the following registry value:
    • Value name: ExtensionDebugLevel
    • Data type: DWORD
    • Value data: 2

The log should begin filling up at %SYSTEMROOT%\Security\logs\winlogon.log right away. In that log, I found the underlying cause of the error:

----Configure Group Membership...
                Configure Domain Admins.
                Configure LABS\Domain Admins.
                                object already member of Administrators.
                Configure LABS\IT_Department_Admins.
                Aliases cannot be members of other groups.

                Group Membership configuration was completed with one or more errors.


So something in Group Policy is attempting to configure group membership on this computer (that happens to be a DC) and is encountering an error while doing so. There are only a couple things in Group Policy that are designed to configure group membership. Group Policy Preferences is one... Restricted Groups is another. Let's dig through the GPOs in this domain and see which one is manipulating group memberships:



A GPO linked at the domain level (which means it applies to all domain computers including DCs,) was using Restricted Groups to ensure that the listed security groups were members of the BUILTIN\Administrators group on every computer in the domain. This was working fine on member servers, but causing errors on DCs. There were also other security groups being added to the BUILTIN\Administrators group on member servers and domain controllers successfully. So what was different about the IT_Department_Admins group? Why was it the only group causing us an error? Let's examine the IT_Department_Admins security group:



Spot the difference? The IT_Department_Admins security group is a Domain Local group, while the other groups that were giving us no problems were Global security groups. To cut to the chase instead of droning on about the difference between DL and Global groups, one of the things you'll notice about a Domain Local group object in Active Directory is that it has a sAMAccountType of OBJECT_ALIAS. Page 103 of [MS-SAMR] has this to say about alias accounts:

An alias object refers to a database object whose objectClass attribute is group or derived from group, and whose groupType contains GROUP_TYPE_RESOURCE_GROUP.

Two domains are exposed from a given server: an account domain and a built-in domain; this fact is true for both DC and non-DC configurations. The account domain refers to the object with objectClass domainDNS. The built-in domain refers to the object with the objectClass builtinDomain.

The built-in domain has the characteristic that its objectSid value is invariant (S-1-5-32) through all deployments and only contains aliases. There is exactly one built-in domain for every account domain.

So to fix the issue, we simply converted the Domain Local group to a Global group and called it a day. The issue stems from the fact that domain controllers don't really have a local SAM the same way that standalone Windows machines and Windows domain members do. When you create a DC, the accounts that were part of the "BUILTIN" domain on the computer are removed from the local SAM and put into NTDS.dit and can be found in the Builtin Container using a tool like ADUC.  Accounts in the Builtin container in AD have ... you guessed it: Domain Local scope.

To Scriptblock or Not to Scriptblock, That is the Question

I was doing some work with the Active Directory Powershell cmdlets recently.  Well, I work with them almost every day, but they still get me with their idiosyncrasies from time to time.

I needed to check some group memberships on various privileged groups within the directory.  I'll show you an abridged version of the code I started with to get the point across, the idea of which is that I iterate through a collection of groups (a string array) and perform some actions on each of the Active Directory groups in sequence:

Foreach($ADGroupName In [String[]]'Domain Admins',     `
                                  'Enterprise Admins', `
                                  'Administrators',    `
                                  'Account Operators', `
                                  'Backup Operators')
{
    $Group = Get-ADGroup -Filter { Name -EQ $ADGroupName } -Properties Members
    If ($Group -EQ $Null -OR $Group.PropertyNames -NotContains 'Members')
    {
        # This condition only occurs on the first iteration of the Foreach loop!
        Write-Error "$ADGroupName was null or was missing the Members property!"
    }
    Else
    {
        Write-Host "$ADGroupName contains $($Group.Members.Count) members." 
    }
}

Before I continue, I'd just like to mention that I typically do not mind very long lines of code nor do I avoid verbose variable names, mostly because I'm always using a widescreen monitor these days.  Long gone are the days of the 80-character-wide terminal.  And I agree with Don Jones that backticks (the Powershell escape character) are to be avoided on aesthetic grounds, but for sharing code in formats that are less conducive to long lines of code, such as this blog or a StackExchange site with their skinny content columns, I'll throw some backticks in to keep the horizontal scrolling to a minimum. 

Anyway, the above code exhibited the strangest bug. At least I'd call it a bug. (I'll make sure and let Jeffrey Snover know next time I see him. ;P) Only on the first iteration of the Foreach loop, I would get the "error" condition instead of the expected "Domain Admins contains 5 members" output.  The remaining iterations all behaved as expected.  It did not matter in what order I rearranged the list of AD groups; I always got an error on the first element in the array.

For a moment, I settled on working around the "bug" by making a "Dummy Group," including that as the first item in the array, gracefully handling the expected exception because Dummy Group did not exist, and then continuing normally with the rest of the legitimate groups.  This worked fine, but it didn't sit well with me.  Not exactly my idea of production-quality code.  I wanted to find the root cause of this strange behavior.

Stackoverflow has made me lazy.  Apparently I go to Serverfault when I want to answer questions, and Stackoverflow when I want other people to answer questions for me.  Simply changing line 7 above to this:

$Group = Get-ADGroup -Filter "Name -EQ '$ADGroupName'" -Properties Members

Made all the difference.  That is, using a string with an expandable variable inside it instead of the script block for a filter.  (Which itself is a little confusing since single quotes (') usually indicate non-expandable variables.  Oh well.  Just syntax to remember when playing with these cmdlets.

Nevertheless, if the code would not work correctly with a script block, I wish the parser would mark it as a syntax error, instead of acting weird.  (Behavior exists in PS 2 and PS 4, though in PS 4 the missing property is just ignored and I get 0 members, which is even worse.)