Next month Microsoft will be changing the default behaviour for LDAP – Cleartext, unsigned LDAP queries against AD (over port 389) will be disabled by default – https://support.microsoft.com/en-gb/help/4520412/2020-ldap-channel-binding-and-ldap-signing-requirement-for-windows  .  You’ll still be able to over-ride that using registry keys or group policy, but the best advice is to configure all LDAP clients to use encrypted, signed LDAPS queries (over port 636).

The problem here is that on many networks there have been many years of integrating printers, phone systems, time clocks, SIEMs, firewalls and all sorts of things into LDAP, using the default (unsigned, cleartext) port of 389.  The challenge many organizations face is that finding the last several years of LDAP configuration using that port will be difficult.

How is this do-able?  The easy way is to get AD to do the work for you.

First, enable logging of LDAP events, as described here:
https://support.microsoft.com/en-ca/help/314980/how-to-configure-active-directory-and-lds-diagnostic-event-logging

In regedit, browse to HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesNTDSDiagnostics

Set the value of “16 LDAP Interface Events” – any non-zero value will start logging of some type.  I set it to “3”, which gives me sufficient logging for just finding the remote clients.

To set this quick on all DCs, store the following reg file and run it on each DC:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesNTDSDiagnostics]
“16 LDAP Interface Events”=dword:00000003

Or, this script will make the reg change on all DCs for you:

$DomainName = (Get-ADDomain).DNSRoot
$AllDCs = Get-ADDomainController -Filter * -Server $DomainName | Select-Object Hostname,Ipv4address,isglobalcatalog,site,forest,operatingsystem

$regFile = @”
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesNTDSDiagnostics]
“16 LDAP Interface Events”=dword:00000003
“@

ForEach ($DC in $AllDCS) {
    Invoke-Command -ComputerName $DC.hostname -ScriptBlock {param($regFile) $regFile | out-file $env:tempa.reg;
    reg.exe import $env:tempa.reg } -ArgumentList $regFile
}

Be sure that you make this change for all domain controllers!  Over the course of time, the DC that admins “prefer” will have likely drifted as servers are upgraded, new servers are installed and old ones are retired.  In a large complex network it’s likely that many DC’s are receiving LDAP queries from something or other.  If you miss the registry key or event collection from one DC, you are likely to miss any number of remote clients that only query that host.

Once this is done, let the LDAP events accumulate. 24 hours is usually sufficient, but depending on your environment you might want a longer interval.  Some LDAP queries only happen on login (vCenter and Linux come to mind for this), so you might want to login to various things if there’s any question about how authentication works on those things.

Once we let things accumulate, we are looking for event ID 2889, which indicates a insecure LDAP query.  The message reads:

The following client performed a SASL (Negotiate/Kerberos/NTLM/Digest) LDAP bind without requesting signing (integrity verification), or performed a simple bind over a clear text (non-SSL/TLS-encrypted) LDAP connection.
 
Client IP address:
aa.bb.cc.dd:sourceportnumber
Identity the client attempted to authenticate as:
domainnameuserid
Binding Type:
0

After reading that message, my first thought (as it is so many times with Windows events) is – “that sounds serious – why isn’t that message logged by default?”. 

That being said, with several DCs and clients, collecting this info in a usable form can be a challenge.  The main thing we normally want to collect is the IP’s of all of the clients making these queries, but the target DC and the userid being used can be useful as well.  For some reason, the log entries track the source port of each query, which is of course not of use to us, we’ll want to filter that out.  Removing duplicate information is key in this – on a typical network we’ll see hundreds or thousands of queries, but the goal is to distill this down to 10-15-20-ish unique IP addresses that we need to fix.

Putting all this together, and dropping the source port, then just collecting the information of interest, we have the script below.  Note that the $days variable should be set appropriately before you run this – for initial data collection, you’ll want a larger value (maybe 5-7 days).  After you start remediation, you might want this set lower, maybe 1-2 days so that you can more easily verify that any client fixes are working as intended.

$reqlist = @()
# define the Event Log query, event id 2889 for the past “$days”
# if all entries are desired, remove StartTime and EndTime

$days = -2
$filter = @{
    Logname = ‘Directory Service’
    ID = 2889
    StartTime =  [datetime]::now.AddDays($days)
    EndTime = [datetime]::now
}

# Get your ad information
$DomainName = (Get-ADDomain).DNSRoot
# Get all DC’s in the Domain
$AllDCs = Get-ADDomainController -Filter * -Server $DomainName | Select-Object Hostname,Ipv4address,isglobalcatalog,site,forest,operatingsystem

foreach($DC in $AllDCs) {
    if (test-connection $DC.hostname -quiet) {
        # collect the events
        write-host “Collecting from server” $dc.hostname
        $events = Get-WinEvent -FilterHashtable $filter -ComputerName $DC.Hostname

        foreach ($event in $events) {
            $b = $event.properties[0].value
            $tempobj = [pscustomobject]@{
                TIME = $event.timecreated
                # use substr instead of ConvertFrom-String to account for IPv6 clients
                IP = $b.SubString(0,$b.LastIndexOf(“:”))
                USERID = $event.properties[1].value
                DC = $DC.Hostname
                }
            $reqlist += $tempobj
            }
        }
        else {write-host “ERROR – HOST ” $DC.hostname ” is not available” }
    }

# now just collect unique addresses and userids
$clientips = $reqlist | select IP,USERID,DC | sort -property IP -unique
$clientips | out-gridview

Your output will look something like:

As always, modify this as needed – note that we’re collecting the times the connections occurred in the $reqlist variable, as well as the server being queried, this info just isn’t in the output.  This makes it easy to verify as remediation proceeds.

Note that you can cut/paste the $reqlist variable into excel to do any post-processing or further filtering that you might want (for instance – did the remediation I made 5 minutes ago work?)

Please – share what you find – use our comment form to let us know the oddest thing that you found that’s using LDAP in your environment (NDA and classification permitting of course).  Even preparing for a life-altering patch is a good time to be doing discovery and recon on your own network!

===============
Rob VandenBrink
[email protected]

(c) SANS Internet Storm Center. https://isc.sans.edu Creative Commons Attribution-Noncommercial 3.0 United States License.