Microsoft ActiveDirectory - updating user passwords in external storage

The article is more focused on those who administer or create a hybrid system environment, where the basis is the Windows domain and it is required to be able to authorize users on other platforms and systems with a centralized credential storage system. In most cases, you will not have problems, but if you need to implement authorization, for example, using CRAM-MD5 or another authorization scheme (which is not in AD), and generally does not support LDAP (or do not understand LDAP in Microsoft understanding ), then this article is for you.


In general, Microsoft has this service: Password Change Notification Service (PCNS). Its purpose is to trigger an event before changing the user password and after, respectively. With these events, the password can be transmitted in clear text (naturally via secure channels), this is what we need.
To work with PCNS, there are a number of paid products with rich functionality, etc., but we will try to make our bike out of free and affordable materials, for which we take:

Everything below was done for Windows Server 2008R2. So let's go!


First of all, you need to install our PasswdHk library on all domain controllers, it will handle the user password change event (the event is generated on the controller with write rights selected by the user automatically, and after changing the password, the hash is distributed to other controllers).
Download the installer (for x64), and regularly set the library. Then you need to copy C: \ Windows \ System32 \ passwdhk.dll to C: \ Windows \ SysWOW64 \.
Along with the DLL, there is also a graphical configuration utility, an example configuration file (reg file) and a work test (bat file). All settings are made in the registry and are applied at system startup (when changing, you need to restart the system).
Deficiencies in the utility !!!
Please note! Each click on the Apply button will add one more line to the registry parameter "SYSTEM \ CurrentControlSet \ Control \ Lsa \ Notification Packages", which will have to be adjusted by handles.

Here are the settings for our laboratory system:

Settings in the text
The Pre and Post Change sections specify actions, respectively, before changing the password (you can implement your own password complexity filter) and after changing it.
  • Program: C: \ Windows \ system32 \ cmd.exe (The program that is run to process the event);
  • Arguments: / c start C: \ Windows \ system32 \ WindowsPowerShell \ v1.0 \ powershell.exe -File d: \ control \ check-user.ps1 (Command-line arguments passed to the program above);
  • Password Escaping sets escaping of password characters, but unfortunately only it. Username escaping does not occur, in addition, the dll is clearly not up to date with encodings such as UTF-8, so a login in Cyrillic will look extremely sad (can anyone manage to decode the result in normal form?)

The option described above with launching through the start command is not accidental; it allows you to bypass a specific problem when the program launched to process the password will execute more than the specified timeouts (Wait Time). Since I started all this on a heavily loaded host with virtual machines, in a machine with extremely modest resources, starting the PowerShell shell and loading the module into a cold one (after a long wait), easily exceeded for 40 seconds (at this time, the user indicated a password change too sits and waits), and at immodest timeouts of 30 seconds, the call to the handler was interrupted forcibly.

So, the work of PasswdHk is over here, now we will figure out how to get the password on our side. PasswdHk passes the username and password pair in the last two command line arguments.

Processing in PowerShell

For our lab work, I chose PowerShell (as a fashion phenomenon, you can do the processing on anything).
Import-Module ActiveDirectory
$csv_file = "d:\passwd.csv"
$domain=(Get-ADDomain -Server localhost).DNSRoot
if(Get-ADUser -Filter {sAMAccountName -eq $user } -SearchScope 2 -Server localhost){
	$csv = @()
	$csv_new = New-Object System.Object
	$csv_new | Add-Member -MemberType NoteProperty -Name "DOMAIN" -Value $domain
	$csv_new | Add-Member -MemberType NoteProperty -Name "USER" -Value $user
	$csv_new | Add-Member -MemberType NoteProperty -Name "PASSWD" -Value $passwd
	$csv += $csv_new
	if (Test-Path  $csv_file){
	$csv | ConvertTo-Csv -NoTypeInformation -delimiter "`t" `
		| select -Skip 1 `
		| Out-File -Encoding UTF8 -Append $csv_file
		$csv | ConvertTo-Csv -NoTypeInformation -delimiter "`t" `
			| Out-File -Encoding UTF8 -Append $csv_file

Here we get a couple of usernames and passwords from the command line variables, in $ domain we put the DNS name of the domain in which the password was changed, and we put all this into a CSV with three columns (DOMAIN, USER, PASSWD), if the user search returned is not empty result (excludes cases with names not in Latin, you can still filter objects that correspond only to users, otherwise you will also find machines regularly updating their passwords in this file).


By changing this example a bit, you can send these passwords to any environment you use, I used this method to generate CRAM-MD5 hashes for the Dovecot mail system.

Also popular now: