Friday, August 30, 2013

LDAP and SAN Certificates

Hey everyone - I ran across an interesting problem with certificates and services. The problem was that I needed to see which certificate is being presented to the client on a non-HTTPS service; specifically - binding to LDAP over SSL (LDAPS). So the question is, how can you be sure what certificate is being presented?

Windows, so far as I've been able to find, does not offer any native help in this regard. Luckily there is a solution, but first let me give you a slightly longer description of the scenario so you can appreciate what we're talking about a little bit better.

An environment exists with some number of Domain Controllers, let's say ten. Within this environment, third party applications (think Java apps, Linux systems, etc) need to bind to LDAP to enumerate groups or validate authentication or whatever. These systems, however, can only have a single (or at best, two) LDAP hosts configured. What do you do? Pick two DCs? Round-Robin DNS?

Well, in my case, a load-balancer had been configured to direct LDAP(S) traffic to the various domain controllers and a DNS record configured for that load balancer.

This works great except that when using a load balancer, a generic name (like LDAPS.domain.com) must be used since it is unknown which server will be connected to. This is a problem for SSL.

If I connect to LDAPS.domain.com and am presented with a certificate whose subject reads "dc01.domain.com" then my SSL bind will fail. The solution to that is to create certificates with Subject Alternate Names[1] such that the "LDAPS.domain.com" name exists in addition to each of the certificates used by each of the Domain Controllers.

Starting with Server 2008, you can bind certificates to specific services (though this was NOT true with Server 2003). So, after binding the SAN cert to AD DS, we want to verify which certificate we're seeing when binding to LDAPS. If you want to actually see the certificate details - here are the steps to view the presented cert:
  1. Install OpenSSL[2]
  2. Open a PowerShell session or Cmd prompt
  3. From the OpenSSL\bin directory, issue the following command:
  4. openssl s_client -connect server:port | openssl x509 -noout -text
(you may have to hit the space bar or enter key for the data to be displayed)

In my case, I wanted to verify that I was seeing the certificate with all the Subject Alternate Names. This proved to be exactly what I needed to see the certificate presented to a binding client. Sadly - I cannot give you screen shots since the data is private and I don't have a lab with all of the certificate infrastructure built. Feel free to comment though, if you need a hand with the commands.

G. Samuel Hays

Monday, August 26, 2013

HomeDirectory name inconsistency and LDAP Query weirdness

Recently I was doing some work where I had to archive some home directories of people who were no longer associated with the company. A typical configuration looks something like this:


User:samAccountName  -> \\Server\Home\%username%

Such that the samAccountName property has the same name as the directory in whatever home share is being used.

What I ran up against, though, was that over the years - through marriages or divorces or other personal reasons, sometimes people would request that their username be changed. The company I was working with sometimes accommodates these requests. My job was to identify if any of the home folders were associated with accounts that had different names.

For example - consider this timeline:

1. User bjones is created with home directory \\Server\Home\bjones
2. bjones gets married and her account name is changed to bsmith

Now user bsmith has \\server\home\bjones for her home directory. This typically isn't a problem technically but administratively it's a pain.  The question is, how do we quickly find out which folder is associated with which Active Directory user?

First, I'll tell you what did not work - and this is surprising!  Within Powershell, I browsed to the Home Folder share and issued the following command:

dir | select fullname | % { Get-ADUser -LDAPFilter "(homeDirectory=$($_.fullname))" }

(Logic: the folder names correspond with the samAccountName so I could use that for the ADUser and for the homeDirectory LDAP query)

This command worked but only on some accounts. In some cases, even though the homeDirectory matched exactly, no result was returned from Active Directory. Why?   I don't know. Independent testing of this has not yielded any solutions, though it has yielded the same results.  So - this is a problem I'm going to have to re-approach.

Still - the problem had to be solved so I came up with another way to get that information. That solution was this:

Get-ADUser -Filter * -Properties homedirectory,enabled -ResultSetSize $null | ? {$_.homeDirectory -ne $null -and $_.homeDirectory -notmatch $_.samAccountName }| select na

e,samaccountname, homedirectory, enabled, @{l="path_Exists";e={test-path $_.homedirectory}} | ft 

The logic here as as follows:
1. Get a list of all AD users and include the homedirectory (and just for fun) the enabled property
2. Use "where-object" (?) to filter out users without a homeDirectory and require that their samAccountName is not found in the path of their homeDirectory
3. Select the properties I want and build a new property for whether or not the folder name defined in homeDirectory actually exists

With this information I am able to quickly see users who have an inconsistently named Home Directory folder, if their account is enabled, if the specified path exists, and of course, their full name.  Enough information to make some decisions on.


Friday, August 23, 2013

Net User and Reset Password Delegation

Hey Folks - I ran into an interesting issue recently where a user who has been given the right to change the password for another domain user, could not do so.  Let me give you a bit of background.

I have a user (let's call him "Account Owner") who manages another account that is used to bind to Active Directory (over LDAPS). Let's call that account "Bind User".  So, Account Owner has the rights to change the password for Bind User.

I told the Account Owner that he could issue the following command to change the bind_user account password:

Net User Bind_User * /domain

I informed him that this command would prompt him for a new password and he could manage the account that way (so he didn't need to install any additional management tools). I told him that this should work from any computer to which he is currently logged on.  The problem, as it turns out, was that I was wrong.  But why?

To answer that question, I configured one of my labs so that I can reproduce the issue. I'll walk you through the issue and solution(s).

The configuration


I created a new user called "bind_user" in an OU called "Delegated Rights Users".



In the "IT Dept" OU, I have a user called "Account_Owner".

The first step is to adjust the bind_user such that Account_Owner can reset the password. So I added those permissions:

Allow CREATION\account_owner          SPECIAL ACCESS for pwdLastSet
                                      WRITE PROPERTY
                                      READ PROPERTY
Allow CREATION\account_owner          Change Password
Allow CREATION\account_owner          Reset Password

This means that, outside of resetting the password (which includes writing the pwdLastSet property), Account_Owner cannot make any adjustments. (This should have been the end of our configuration, or so I thought.)

Testing The Config

As I mentioned earlier, I had directed Account Owner to use the "Net.exe" command to reset the password. But he got the following error.


"Access is Denied" is a surprising result given the permission that Account_Owner has been granted. This raises a question - what other attributes is the "Net.exe" command trying to alter?

To find out, I created another user account called "bind_user_full" and granted Full Control to the "Account_Owner" account.  Next, I took a snapshot of the attributes prior to issuing the command.

Note: I used "get-aduser bind_user -properties * | fl * -force" with a transcript running to capture the attributes into a text file.

Once I'd issued the Net.exe command, I took another snapshot of the AD attributes so I could make a comparison and see what has been modified.

Here are the attribute that have changed on the bind_user_full account after I reset the password with Net.exe:

accountExpires (original value):9223372036854775807
accountExpires (new value):     0

logonHours (original value): NULL
logonHours (new value):      {255, 255, 255, ... }  an array of values

Modified (original value): 8/23/2013 10:09:57 AM
Modified (new value):      8/23/2013 10:18:43 AM

modifyTimeStamp (original value): 8/23/2013 10:09:57 AM
modifyTimeStamp (new value):      8/23/2013 10:18:43 AM

passwordLastSet (original value): 8/23/2013 10:09:38 AM
passwordLastSet (new value):      8/23/2013 10:18:43 AM
* pwdLastSet matches this value

uSNChanged (original value):  32873
uSNChanged (new value):       32876

whenChanged (original value): 8/23/2013 10:09:57 AM
whenChanged (new value):      8/23/2013 10:18:43 AM

As we can see, quite a few attribute were changed after issuing the "Net.exe" command to reset the password. To identify which attributes were changed by the Net command and which were changed by Active Directory, I performed the same comparison, except using the ADSI provider in PowerShell to change the password of the original account*.

*Note: Using PowerShell to change the password instead of the Net command is how I stumbled upon this behavior in the first place.


With the password having been changed through the ADSI provider, I performed the same comparison as before.

Modified (original value): 8/23/2013 9:41:28 AM
Modified (new value):      8/23/2013 10:35:07 AM

modifyTimeStamp (original value): 8/23/2013 9:41:28 AM
modifyTimeStamp (new value):      8/23/2013 10:35:07 AM

passwordLastSet (original value): 8/23/2013 8:57:09 AM
passwordLastSet (new value):      8/23/2013 10:35:07 AM
* pwdLastSet matches this value

uSNChanged (original value):  32847
uSNChanged (new value):       32880

whenChanged (original value): 8/23/2013 9:41:28 AM
whenChanged (new value):      8/23/2013 10:35:07 AM


This behavior is much more consistent with what I expected.  Apparently the Net command makes a lot of assumptions about what you mean when you give it a command.  (Another example is "net user username /active:yes /domain"  both unlocks the account AND enables it, if it is disabled. - that can be a bad thing).

Conclusion


So it seems that the Net.exe command is doing quite a bit more behind that scenes that I'd expected. I could continue to investigate, identify all of the attributes that it wants to change, and grant them but I find that approach distasteful.

Instead, I directed Account Owner to use the PowerShell method to change the password until I get an opportunity to put a decent script together for him.

G. Samuel Hays

Wednesday, August 21, 2013

Get-Help vs Help - the paging/pager inconsistency

When I'm teaching PowerShell to folks, I often like to use the full name of the cmdlets - get-help instead of 'help', get-childitem instead of 'dir'. You get the idea.

I do this to help drive home the verb-noun syntax and because I can't necessarily expect everyone to know the shortcuts that I tend to use.

Well, the problem that I'm about to describe hasn't come up too much - but its annoying and I finally decided to take a minute and figure out what's going on.

The Problem (Powershell 2.0 or 3.0)

In powershell, if I issue the command:

get-help <cmdlet> -full

then I get a wall of text that is not paged (where paging is that "-- More  --" at the bottom of the screen that allows me to read a page at a time.

To be honest, I'd never really spent more than a second thinking about this annoyance because it seemed so trivial, but since I'm in the midst of putting together a curriculum for a powershell class, it got a little more important.

I had basically made the assumption that help (and man) were simply aliases to get-help. This assumption was wrong.  Have a look:


PS C:\> get-alias -Definition get-help
Get-Alias : This command cannot find a matching alias because alias with definition 'get-help' do n
ot exist.
At line:1 char:10
+ get-alias <<<<  -Definition get-help
    + CategoryInfo          : ObjectNotFound: (get-help:String) [Get-Alias], ItemNotFoundException
    + FullyQualifiedErrorId : ItemNotFoundException,Microsoft.PowerShell.Commands.GetAliasCommand

PS C:\>

That's surprising, no aliases at all for get-help? That makes me wonder what "man" is aliased to. Well as it turns out, "man" is pointing to "help".

PS C:\> get-alias man

CommandType     Name                                      Definition
-----------     ----                                      ----------
Alias           man                                       help

Alright, so what is "help" if it isn't an alias but does the exact same thing as get-help? And what's the deal with "man" aliasing to it?! Let's find out.

PS C:\> Get-Command help

CommandType     Name                                      Definition
-----------     ----                                      ----------
Function        help                                      ...

Ah ha! As we can see (highlighted for dramatic effect) - "help" is not an alias. It is a function! Let's have a look at this function.

PS C:\> cd function:
PS Function:\> dir help

CommandType     Name                                      Definition
-----------     ----                                      ----------
Function        help                                      ...


PS Function:\> Get-Content help

<#
.FORWARDHELPTARGETNAME Get-Help
.FORWARDHELPCATEGORY Cmdlet
#>
[CmdletBinding(DefaultParameterSetName='AllUsersView')]
param(
    [Parameter(Position=0, ValueFromPipelineByPropertyName=$true)]
    [System.String]
    ${Name},

    [System.String]
    ${Path},

    [System.String[]]
    ${Category},

    [System.String[]]
    ${Component},

    [System.String[]]
    ${Functionality},

    [System.String[]]
    ${Role},

    [Parameter(ParameterSetName='DetailedView')]
    [Switch]
    ${Detailed},

    [Parameter(ParameterSetName='AllUsersView')]
    [Switch]
    ${Full},

    [Parameter(ParameterSetName='Examples')]
    [Switch]
    ${Examples},

    [Parameter(ParameterSetName='Parameters')]
    [System.String]
    ${Parameter},

    [Switch]
    ${Online})
$outputEncoding=[System.Console]::OutputEncoding

      Get-Help @PSBoundParameters | more

PS Function:\>

And there we have it! The function (again, highlighted for dramatic effect) simply runs the get-help cmdlet put pipes the output to the "more" command. 

Well - that explains the different behavior, and that's good to know.

Interestingly - the "more" command is also simply a function. Output below for the sake of completeness:

PS Function:\> type more
param([string[]]$paths)
$OutputEncoding = [System.Console]::OutputEncoding

if($paths)
{
    foreach ($file in $paths)
    {
        Get-Content $file | more.com
    }
}
else
{
    $input | more.com
}

Interesting stuff.  I hope if any of you guys wondered about this, you now have the answer and can sleep better at night. :)