Windows Server DomainController find LDAP binds

The estimated reading time 9 minutes

Some time ago Microsoft announced the changing of default domain controller behavior for ldap and ldap signing. See LINK.
This affects every supported version of Windows Server (from 2008R2 till 2019). There is another LINK ADV190023 with detailed explanation.

I think there should be no discussion to change your domaincontroller to ldap signing only. But what about 3rd party software? How can you find these software accessing your DCs and using unsigned ldap?

If you don’t have any certificate for ldap on your domaincontroller, have a look on the requirements, it’s important. How to enable LDAP over SSL.

I recommend to activate LDAP loggin on every domain controller in your environment, and extend the Eventlog “Directory Service” so you can go back in the past to see most of the ldap connections.

How does it look like when an ldap connection is logged as unsigned:

Application and Service Logs -> Directory Service-> Event ID 2889

As you can see IP Adress and User who does the ldap bind is logged.

First you have to enable LDAP loggin on your DCs.
I’ll use a gpo set the registry keys on all DCs in my test environment, but you can also set the key manually:
REGKEY LDAP loggin (should already be there but set to 0): HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics -> DWORD -> “16 LDAP Interface Events” -> Value “2”

REGKEY extend eventlog: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Directory Service -> DWORD -> “MaxSize” -> Value= “2147483648” (2GB)

You can also use the reg add commands:

Reg Add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics /v "16 LDAP Interface Events" /t REG_DWORD /d 2

Reg Add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Directory Service" /v "MaxSize" /t REG_DWORD /d "2147483648"

After adding this regkeys there need to be some time so that DCs can log connections and hopefully see all unsigned ldap connections. (best case is to see no ldap connections)

If you want to try ldap and ldaps connection you can go on your dc or any other windows server and use the LDP.exe to check. It is located in C:\Windows\SYSTEM32\ folder.

If you do also a simple bind the connection is logged in your eventlog

Please check also if you can connect your ldap with SSL Port 636

After finishing you can be sure your DCs accept LDAPS and are logging LDAP connections. Now you have to wait some days so that you see most of the LDAP connections (you may see not all because if the device does no ldap query there is nothing to protocol).

Now it’s scripting time, because it can be really painful to search eventlogs and find all events with the IP addresses inside the event message.
See the following script:

<#
#### requires ps-version 3.0 ####
<#
.SYNOPSIS
Analyses Eventlog from DomainControllers and searches for LDAP access 
.DESCRIPTION
Creates a working directory on your specified path. After creation it copies evtx files from all Domaincontrollers via robocopy into this working directory.
Every domaincontroller eventlog creates four files (cleared and raw) 
.PARAMETER WorkDir 
Path where you want to have your logs and results (please check diskspace, evtx file may have some GBs)
.INPUTS
-
.OUTPUTS
YYYY-MM-DD-HH-MM-SS-NameofDC.evtx (copied but untouched)
YYYY-MM-DD-HH-MM-SS-NameofDC-LDAP-cleared.csv
YYYY-MM-DD-HH-MM-SS-NameofDC-LDAP-raw.csv
YYYY-MM-DD-HH-MM-SS-NameofDC-LDAP-time.csv
YYYY-MM-DD-HH-MM-SS-NameofDC-LDAP-time-cleared.csv
.NOTES
   Version:        0.1
   Author:         Alexander Koehler
   Creation Date:  Tuesday, March 10th 2020, 11:07:01 pm
   File: ad-ldap-audit-0-1.ps1
   Copyright (c) 2020 blog.it-koehler.com
HISTORY:
Date      	          By	Comments
----------	          ---	----------------------------------------------------------
.LINK
 blog.it-koehler.com/en/Archive/2951
.COMPONENT
 Required Modules: none

.LICENSE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the Software), to deal
in the Software without restriction, including without limitation the rights
to use copy, modify, merge, publish, distribute sublicense and /or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
 
.EXAMPLE
C:\temp\ad-ldap-audit-0-1.ps1 -WorkDir "C:\temp\ldapaudit"
#
#>

# define parameters (Working Directory) and check if it's a directory and if it does not exist, create it.
[CmdletBinding()]
Param(
    [Parameter(Mandatory = $True)]
    [String] $WorkDir)
if (-not (Test-Path $WorkDir -PathType Container)) {
    
    try {
        New-Item -Path $WorkDir -ItemType Directory -ErrorAction Stop | Out-Null
    }
    catch {
        Write-Error -Message "Unable to create directory '$WorkDir'. Error was: $_" -ErrorAction Stop
    }
    "Successfully created directory '$WorkDir'."
}
else {
    "Directory already existes"
}
#get date as string (is needed for filenames)
$date=((Get-Date).ToString('yyyy-MM-dd-HH-mm-ss'))
#getting all DCs in environment without AD PowerSehll Module 
$DCs = ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites | % { $_.Servers } | select Name).Name
#executing code for every domaincontroller
foreach($DC in $DCs){
#copy evtx file in working dir with robocopy
$path = "\\$DC\c$\Windows\System32\winevt\Logs\"
robocopy "$path" "$workdir" "Directory Service.evtx" 
Rename-Item "$workdir\Directory Service.evtx" "$date-$DC.evtx"
$evtxfile = "$workdir\$date-$DC.evtx"
#define output file prefix
$csvoutputpath = "$workdir\$date-$DC-LDAP"
#searching for event 2889
$Events = Get-WinEvent @{Path=$evtxfile;Id=2889}
#define array for outputs
$eventarraytime = @()
$eventarrayraw =@()
  ForEach ($Event in $Events) {           
    # Convert the event to XML           
    #see https://docs.microsoft.com/de-de/archive/blogs/ashleymcglone/powershell-get-winevent-xml-madness-getting-details-from-event-logs
    $eventXML = [xml]$Event.ToXml()
    #getting timestamp
    $time = ($Event.TimeCreated)
    #getting user from eventlog
    $ldapconn = ($eventXML.Event.EventData.Data)
    #creating custom object and put all together
    #customobject with timestamp
    $eventobj = New-Object System.Object
    $eventobj | Add-Member -type NoteProperty -name AccessTime -Value $time
    $eventobj | Add-Member -type NoteProperty -name LDAPAccess -Value (@($ldapconn) -join " ")
    #custom object witout timestamp
    $eventraw = New-Object System.Object
    $eventraw | Add-Member -type NoteProperty -name LDAPAccess -Value (@($ldapconn) -join " ")
    #appending content to array
    $eventarraytime += $eventobj
    $eventarrayraw += $eventraw
        }    
    #output data  in rawformat to csv
    $eventarraytime  | Export-Csv -Path "$csvoutputpath-time.csv" -Encoding UTF8 -Delimiter ";" -NoTypeInformation
    $eventarrayraw  | Export-Csv -Path "$csvoutputpath-raw.csv" -Encoding UTF8 -Delimiter ";" -NoTypeInformation
    #cleaning up double entries
    $eventstimecleared =  $eventarraytime | Sort-Object -Unique LDAPAccess -Descending
    $eventstimecleared | Export-Csv -Path "$csvoutputpath-time-cleared.csv" -Encoding UTF8 -Delimiter ";" -NoTypeInformation
    $eventarraycleared =  $eventarrayraw | Sort-Object -Unique LDAPAccess -Descending
    $eventarraycleared  | Export-Csv -Path "$csvoutputpath-cleared.csv" -Encoding UTF8 -Delimiter ";" -NoTypeInformation
 }

You can copy the complete script into a ps1-file and trigger it with its parameter -WorkDir “Specify the path where it should copy all outpout and evtx files”

NOTE: if evtx file is really large (1-2GB) the execution of this script can take several hours, be patient!

If you are a powershell pro, you can stop reading at this point, but if you’d like to know how it’s working, I’ll give some explanation.

LINE 82:
$DCs = ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites | % { $_.Servers } | select Name).Name
This is a .Net Function to find all DCs with their FQDN, all code after this line will be executed for every dc in variable $DCs (ForEach Loop)

LINE93:
$Events = Get-WinEvent @{Path=$evtxfile;Id=2889}
Searching inside the evtx file for event id 2889 and get them in variable $Events
After this variable is available we need to have a look on every single event with another foreach loop.

LINE 100:
$eventXML = [xml]$Event.ToXml()
We need to convert the event to XML so we get objects which can be searched and filtered by PowerShell.

LINE 104:
$ldapconn = ($eventXML.Event.EventData.Data)
In this case information for ldap connection is in this object (depends on structure

All other code is mostly responsible to convert data and create a readable content for CSV files

In my testing environment there are only few ldap connections manually generated.

IP and user is separated by semicolon so importing and sorting in excel should be easy.

It’s also possible to run the script more than one time, because it adds the timestamp to every file nothing will be overwritten. If you have any questions do not hesitate to contact me and if you like this article please click on “Helpful”.
Lock down your AD and close LDAP!

Print Friendly, PDF & Email
Was this article helpful?
YesNo
0 0 votes
Article Rating
Subscribe
Notify of
guest
10 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
trackback
1 year ago

[…] Windows Server DomainController find LDAP binds – it … […]

trackback
2 years ago

[…] Windows Server DomainController find LDAP binds – it … […]

trackback
2 years ago

[…] Windows Server DomainController find LDAP binds – IT … […]

trackback
2 years ago

[…] Windows Server DomainController find LDAP binds – it … […]

HOW Bali
2 years ago

Thanks for sharing! So helpful!

Soclikes
3 years ago

Good instruction! You helped me, thanks

Seth Allums
Seth Allums
3 years ago

Hi Alex,

Thanks for this! I do have a question however. Is there any way to remove the 5 numeric digits after the IP address? The problem is that the extremely large list it generates for me, I have multiple of the same IP (but they have different sets of numbers after the IP, and therefore many duplicates). Given your example above, I would have many lines of 192.168.178.69 (with varying :50141 and another line :52137) etc. (I’m assuming they’re port numbers). What can be done just to gather a list of unique IP’s and not IP:port etc?

Kind regards,
-Seth

Alex
Alex
3 years ago

Hi, thanks for the article as its very useful. Just one question regarding the script. Whenever I execute the script it goes through the process of getting the event viewer files from the DCs successfully but only saves a fraction of the data of Event ID 2889 in the CSVs. I can see thousands of such events in Event Viewer, but in the output file it only displays like 200. Is there a way to get all the 2889 events? Many thanks.