devxlogo

Enforce Custom Password Policies in Windows

Enforce Custom Password Policies in Windows

icrosoft Windows allows you to define various password policy rules. Specifically, it allows you to enable the “Password must meet complexity requirements” setting using the Policy Editor. This validates user passwords against password filter(s) (system DLL(s)). Usually, people use the default filter. However, many admins say they’d prefer a Linux-style validation, which would allow them to install various pluggable authentication modules (Linux-PAM modules) to filter user passwords (authentication tokens). You can easily adapt these modules to reflect your organization’s security policy with help of Linux configuration text files. The ability to add-on such modules creates more flexibility in composing password policies. With help of such custom modules (of course, these modules should be developed by a Linux programmers), Linux administrators may even author a regular expression for matching user passwords. Go to www.kernel.org/pub/linux/libs/pam/ for more detailed information about Linux-PAM and the available modules.

The Linux model described above may be employed on Windows machines as well.

What You Need: Windows NT/2000/XP

In this article, learn how to create a Custom Password Filter (DLL in C++) that validates passwords against a configurable regular expression. The RegEx functionality is implemented based on the Boost open source library because it has wide support for regular expressions.

Let’s start with an overview of the Windows Security system.

Windows Security
Windows Security is a policy-based system with a set of rules that compose security settings for a local machine or domain. The work of policy-based systems usually has three major stages:

  1. Creating rules to compose a policy.
  2. Searching for evidences.
  3. Enforcing policy based on the evidences.

There is a parallel between the above stages and real-life legal systems. Most countries have an authority (usually parliament or senate) that makes laws. This corresponds to the first stage?composing the policy). Police departments are the guards of the legal system, responsible for collecting evidence (e.g. measuring car speed on highways) and enforcing the existing laws based on evidences (e.g. canceling driving license in case of exceeding the speed limit). So, a police force corresponds to the second and third stages.

In Windows security, system administrators play the role of parliament. They dictate the policy for an organization domain. In some cases, regular users also design security policy (e.g. when choosing their own passwords). The police uniform is given to the local security authority (LSA) Windows sub-system. LSA collects evidences for decision-making and enforces the policies (laws). The LSA sub-system is represented by the lsass.exe Windows process and several system DLLs.

Configuring Security Policy
System Administrators are usually responsible for configuring Security Policy. Since this article is about password filters, I’ll use configuring Password Policy as the example.

Figure 1. The “Local Security Policy” Management Console: This shows the list of security settings that compose your password policy on the local machine.

As mentioned previously, regular users are involved in composing security settings when they choose their own log-on passwords. However, because a weak password can create vulnerable system and compromise organization security, system administrators need more control over this issue and disallow the use of too simple, short and vulnerable to dictionary attacks passwords. In other words, you need to compose a password policy that meets your organization’s security requirements.

To edit security policies, you can use either the secedit.exe command line utility or the “Domain Security Policy” graphical console available from Control Panel -> Administrative Tools on the domain controller machine. With this tool, you will govern the security policy for all the computers in the Windows domain. Note that in case of workstation machine, only the “Local Security Policy” console is installed (shown in Figure 1). Local policy affects settings on the local machines and it doesn’t override domain policy. Thus, the security settings will be effective for local machine users, but not for domain users. This article uses the graphical tool to alter security settings on the local machine.

Figure 2. Editing Password Policy Rules: Double-click the “Minimum password length” item to display the dialog window.

The left pane of the management console contains an Explorer-like tree. Each node represents a different Security Policy. In this example, you’ll make modifications to the Password Policy to require users to choose long enough passwords (at least 10 characters). Here’s how to do it:

Expand the “Account Policies” node and select “Password Policy.” On the right pane of the management console, you should see a list of security settings (rules) that compose the password policy as shown in Figure 1.Double-click the “Minimum password length” item to display the dialog window (Figure 2).Edit the text field, setting the minimum password length to 10 characters, and click OK.

Congratulations! The new rule is ready. From now on, LSA will not allow your users to choose passwords shorter than 10 characters.

An interesting rule from the Password Policy set is “Password must meet complexity requirements.” This rule may be either Disabled or Enabled. In the Disabled state it has no effect. Enabling this rule instructs LSA to validate each password against Password Filters. If you don’t provide any filter, the default is used (which is considered relatively strong). However, the default allows simple passwords, such as Paris123. You definitely want more powerful filters and this is where Custom Password Filters can be helpful.

What Is a Password Filter?
A Password Filter plays a primary role in decision-making regarding user passwords. By definition, a Password Filter is a system DLL that exports three functions with the following prototypes (note the __stdcall calling convention):

BOOLEAN __stdcall InitializeChangeNotify(void);	         // (1)BOOLEAN __stdcall PasswordFilter(                         // (2)  PUNICODE_STRING AccountName,  PUNICODE_STRING FullName,  PUNICODE_STRING Password,  BOOLEAN SetOperation);NTSTATUS __stdcall PasswordChangeNotify(	          // (3)  PUNICODE_STRING UserName,  ULONG RelativeId,  PUNICODE_STRING NewPassword); 

How does LSA interact with Custom Password Filters by means of the above interface? First, assume that the “Password must meet complexity requirements” rule is Enabled.On the system startup, LSA loads all the available Password Filters and calls the InitializeChangeNotify() function. When LSA receives TRUE as a return value, this means that the Password Filter loaded successfully and functions properly. Upon this call, LSA also builds a chain of available Password Filters (those that returned TRUE).

When you’re giving a password to a new user or modifying an existing user’s password, LSA assures that every link in Password Filters Chain is satisfied with a new password. LSA invokes the PasswordFilter() function of each filter in the chain. If one filter in a chain returned FALSE, LSA does NOT continue calling the next filter. Instead, it asks the user to provide another password. If every call to PasswordFilter on every filter returns a TRUE value, a new password is approved and each filter is notified about it through the PasswordChangeNotify() function.

As you can see, the Password Filter is a handy tool for LSA (or, the Windows Police), acting as a speed trap for highway patrol, helping to collect evidence from the “field.” These evidences are useful in the third stage, where policies are enforced.

Before You Implement…
Consider the following issues before you start coding your own Password Filters:

  • Treat sensitive data carefully. The PasswordFilter and PasswordChangeNotify functions receive passwords in clear-text format. These passwords should be processed fast and shouldn’t leave any trails in your memory for malicious applications to capture. Introduced in Windows 2003, the SecureZeroMemory Win32 API cleans specified memory. Traditional ZeroMemory may be not enough, since “smart” compilers will optimize your code and remove calls to this API. To make sure there are no such “useful” optimizations, read a random byte from a password string after it was filled with zeros.
  • Make your filters fast and efficient. When LSA calls into the Password Filter function, most Windows processing stops, so make sure you don’t perform any lengthy operations.
  • Expect the unexpected. Because LSA loads password filters during start-up, if something goes wrong, your system may become inoperable or go into deadlock. To avoid this, develop and test your DLLs on machines that have at least two operating systems installed. I have Linux and XP on my box and I found it highly useful when preparing this article. When I encountered problems, I booted from Linux and deleted the Password Filter DLL.
  • Log your actions. Password Filters run in the context of the lsass.exe process. I don’t recommend debugging this process, because after you close the debugger and end the process, your system will shutdown. The best way to debug your already-running filter is to write the log files to disk and follow them to fix the bugs.
  • Pre-debug your DLL. While lsass.exe debugging is not recommended, you may test your fresh Password Filter by writing a small unit-test program. In this program, load your DLL with a call to LoadLibrary Win32 API and invoke exported functions (after getting their addresses within GetProcAddress Win 32 API calls). This way, you may check that your filter doesn’t crash and functions properly.

The RegEx Password Filter Sample
Now that you’re aware of all the possible pitfalls, it’s high time for code action. This section will walk you through the sample provided with this article. I’ve created a VS7 solution with the PasswordFilterRegEx VC project.

As the Password Filter definition requires, you export three functions. Here’s the code for the DEF file included within the sample project:

LIBRARY PasswordFilterRegExEXPORTS	InitializeChangeNotify	PasswordChangeNotify	PasswordFilter

The PasswordFilterRegEx.cpp contains source code for the exported functions. The implementations of InitializeChangeNotify and PasswordChangeNotify are quite simple:

// Initialization of Password filter.// This implementation just returns TRUE// to let LSA know everything is fineBOOLEAN __stdcall InitializeChangeNotify(void){	WriteToLog("InitializeChangeNotify()");	return TRUE;}// This function is called by LSA when password// was successfully changed.//// This implementation just returns 0 (Success)NTSTATUS __stdcall PasswordChangeNotify(  PUNICODE_STRING UserName,  ULONG RelativeId,  PUNICODE_STRING NewPassword){	WriteToLog("PasswordChangeNotify()");	return 0;}

The bulk of the work is done in the PasswordFilter function (shown in Listing 1). First, create a zero-terminating copy of a password string and assign it to an STL wstring object (STL is used in conjunction with the boost regex library):

wszPassword = new wchar_t[Password->Length + 1];if (NULL == wszPassword){	throw E_OUTOFMEMORY;}wcsncpy(wszPassword, Password->Buffer, Password->Length);wszPassword[Password->Length] = 0;WriteToLog("Going to check password");// Initialize STL stringwstrPassword = wszPassword; 

Next, the regular expression is instantiated. The sample Password Filter reads the regular expression from the RegEx value of the following registry key:

HKEY_LOCAL_MACHINE\Software\DevX\PasswordFilter 

If the value is not found in registry, the dummy default regular expression (“^(A)$”) is used.

Finally, validate the password against the regular expression and return the results to the caller (LSA):

WriteToLog("Going to run match");// Prepare iteratorswstring::const_iterator start = wstrPassword.begin();wstring::const_iterator end = wstrPassword.end();		match_results what;unsigned int flags = match_default;bMatch = regex_match(start, end, what, wrePassword);if (bMatch){	WriteToLog("Password matches specified RegEx");}else{	WriteToLog("Password does NOT match specified RegEx");}. . .return bMatch; 

Just before you return the results to LSA, perform memory clean-up:

// Erase all temporary password data// for security reasonswstrPassword.replace(0, wstrPassword.length(), wstrPassword.length(), 						(wchar_t)'?');wstrPassword.erase();if (NULL != wszPassword){	ZeroMemory(wszPassword, Password->Length);	// Assure that there is no compiler optimizations and read random byte	// from cleaned password string	srand(time(NULL));	wchar_t wch = wszPassword[rand() % Password->Length];	delete [] wszPassword;	wszPassword = NULL;}return bMatch; 

Installing the Password Filter

Figure 3. Editing “Notification Packages”: Add the PasswordFilterRegEx string.

Note: In order to filter passwords for domain users, you should use the “Domain Security Policy” console on domain controller machine and install there your password filter. In this example, the entire configuration is done on the local machine. Hence, Password Filter will validate passwords for my local machine accounts.Follow this procedure to activate your fresh Password Filter (the same procedure is applicable for the domain controller):

  • Enable the “Password must meet complexity requirements” rule of the Password Policy.
  • Copy the Password Filter DLL to the %SystemRoot%system32 folder on your machine.
  • Open the Registry Editor (regedit.exe) and locate the following registry key: HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlLsa
  • Modify the “Notification Packages” multi-string value of the above key and add your Password Filter file name without the “.dll” extension. Add the PasswordFilterRegEx string as shown in Figure 3.
  • Close Registry Editor and restart your machine.

Your Password Filter in Action
After you’ve installed Password Filter and restarted your machine, you’re ready for testing. The source code includes a simple regular expression for testing purposes. Find it in the RegEx value of the HKLMSoftwareDevXPasswordFilter key (the PasswordFilter.reg file is provided with the code for your convenience):

^([a-zA-Z]+)(d+)([a-zA-Z]+)$ 

In other words, start with letters, have some digits in the middle and end up with letters again. This regular expression is not recommended as a strong Password Regular expression, but it is useful for assessing whether your Password Filter does its job.

Figure 4. Creating a New User: Select Expand Local Users and Groups, right-click on the Users node, and choose the New User menu item.

Remember that this filter stands after the default Windows filter in the chain. So, in order to have any effect, you’ll need tougher requirements than the default. The Paris2003 password will validate against the default filter, but the test regular expression won’t match it. To check this, create a new user. If you use Domain Controller, create a user with Active Directory. On the stand-alone Workstation machine, right-click on My Computer and choose the Manage item from the context menu. Select Expand Local Users and Groups, right-click on the Users node, and choose the New User menu item as shown in Figure 4.

Fill-in the new user’s details and assign a password. Try a simple one (e.g.: Paris2003) and you will get an error message from LSA (Figure 5). Try a different, more complex password (e.g.: Paris2003A) and it will be accepted.

The Secret Is Out
While there are several commercial products that implement Password Filters, it isn’t really all that difficult. Now, that you understand how they work, you can provide your own, customized solution.

Figure 5. Error!: This password doesn’t meet the complexity requirements.
devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist