Продолжая разговор об аутентификации в семействе Windows NT/2000/XP/Server 2003, начавшийся с обсуждения GINA.DLL, на этот раз я расскажу о средствах, позволяющих контролировать процесс создания и изменения паролей пользователей. Проблема неустойчивых к взлому паролей, наверное, набила оскомину уже всем системным администраторам, заботящимся о безопасности информации как в сети, так и на отдельных компьютерах. Но, действительно, как объяснить пользователю, что пароли, совпадающие с именем учетной записи, а также 1, secret, sex и еще несколько всем известных вариантов, подвергают риску корпоративную информацию?

В состав операционных систем входят базовые средства для решения этой проблемы. Оснастка «Локальные политики безопасности» в MMC отчасти предоставляет необходимые инструменты. Рекомендуется разрешить политику Passwords must meet complexity requirements (см. Экран 1).

Это приведет к тому, что:

  1. пароли не будут содержать имя или любую часть полного имени пользователя;
  2. пароли пользователей должны будут иметь длину не менее шести символов;
  3. в составе паролей обязательны символы трех из четырех групп.
Тип символов Пример
Английские заглавные буквы A, B, C, ... Z
Английские прописные буквы a, b, c, ... z
Цифры 0, 1, 2, ... 9
Специальные символы $,!,%,^

Администратору может показаться, что требования к паролям, предъявляемые Windows, недостаточны. В этом случае можно воспользоваться API Password Filters, позволяющим расширять и изменять требования к политике безопасности, а также контролировать процесс изменения паролей. Кроме того, по тем или иным причинам может потребоваться отслеживать момент смены пароля пользователем. Впервые возможность применить это расширение появилась в Windows NT SP2.

В Platform SDK входит пример, реализующий модуль Password Filter (...SamplesWinBaseSecurityWinNTPwdFilt), который выполняет как стандартные ограничения, так и описанные выше. Когда приходит запрос на изменение пароля, LSA вызывает модули Password Filters, зарегистрированные в системе. Эти модули представляют собой dll, экспортирующие три функции.

Нужно иметь в виду, что механизм действует только по отношению к локальной базе SAM, т. е. если меняется пароль доменной учетной записи, то будут вызываться функции на модуле, работающем на соответствующем DC. Если же администратор меняет локальную учетную запись, то и модуль должен выполняться на локальном компьютере. По этой причине в функции модулей Password Filter не передается имя домена, в котором находится учетная запись.

В процессе изменения пароля каждый модуль вызывается дважды. Первый раз — для проверки подлинности идентификационной информации. Это делается вызовом функции PasswordFilter. А второй раз, после того как все фильтры подтвердили корректность этой информации, для извещения модуля фильтра об успешной операции вызывается функция PasswordChangeNotify (см. Рисунок 1).

Рисунок 1. Взаимодействие Password Filter и LSA.

Описание модуля Password Filter

Модуль Password Filter устроен несложно. Он представляет собой стандартную библиотеку dll. Библиотека Password Filter, как уже отмечалось выше, экспортирует три функции с прототипами, приведенными в Листинге 1.

Листинг 1. Прототипы библиотеки Password Filter.

BOOLEAN NTAPI InitializeChangeNotify();
NTSTATUS NTAPI PasswordChangeNotify(
 PUNICODE_STRING UserName,
 ULONG RelativeId,
 PUNICODE_STRING NewPassword
);
BOOLEAN NTAPI PasswordFilter(
 PUNICODE_STRING AccountName,
 PUNICODE_STRING FullName,
 PUNICODE_STRING Password,
 BOOLEAN SetOperation
 );

Процесс блокируется до тех пор, пока эти функции не вернут управление. Строковые параметры передаются в функции в виде указателей на структуру UNICODE_STRING. В заголовочном файле Ntsecapi.h она определяется следующим образом:

typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING;
typedef LSA_UNICODE_STRING UNICODE_STRING, *PUNICODE_STRING;

И назначение этих функций, и смысл передаваемых им параметров достаточно очевидны.

InitializeChangeNotify. Подсистема LSA вызывает эту функцию, чтобы, с одной стороны, дать библиотеке возможность провести процесс инициализации, а с другой, получить от нее подтверждение о готовности к работе. Если данная функция возвращает FALSE, в ходе дальнейшей работы она вызываться не будет.

PasswordFilter. Данная функция вызывается в тот момент, когда предпринимается попытка изменения пароля. Это происходит в тех случаях, когда создается учетная запись, и пользователь или администратор изменяют пароль для уже существующей учетной записи. Если функция по тем или иным причинам возвращает FALSE, то LSA — значение ERROR_ILL_FORMED_PASSWORD. Если же процесс проверки пароля прошел успешно, то LSA поочередно вызывает остальные зарегистрированные в системе модули Password Filter.

В функцию передаются следующие параметры:

AccountName — [in] имя учетной записи, для которой выполняется проверка;

FullName — [in] полное имя пользователя;

Password — [in] новое значение пароля в виде открытого текста;

SetOperation — [in] TRUE, если пароль изменяется для уже существующей учетной записи;

FALSE — если создается новая учетная запись.

PasswordChangeNotify. Подсистема LSA вызывает эту функцию для каждого из зарегистрированных в системе модулей Password Filter, чтобы уведомить их о том, что проверка пароля прошла успешно, и он изменен. Т. е. названная функция вызывается уже после того, как новый пароль внесен в базу данных SAM.

В функцию передаются следующие параметры:

UserName — [in] имя учетной записи, для которой выполняется проверка;

RelativeId — [in] значение Relative identifier (RID) учетной записи, определенной в UserName. RID представляет собой часть SID, определяющую учетную запись в базе данных SAM;

NewPassword — [in] новое значение пароля в виде открытого текста учетной записи, определенной в UserName.

Что необходимо помнить при написании кода?

В MSDN приводится ряд требований, которые необходимо учитывать при написании модулей PasswordFilter.

  1. Информация передается в виде открытого текста. Передача паролей через сеть в таком виде может привести к перехвату паролей с помощью сетевых сканеров.
  2. Для всех областей динамической памяти перед уничтожением необходимо использовать функцию ZeroMemory, ведь функции освобождения памяти физически не уничтожают ее содержимое, а лишь высвобождают дескрипторы.
  3. Все буферы, которые передаются процедурам, доступны только для чтения.
  4. Все процедуры должны быть рассчитаны на работу в многопоточной среде. Необходимо использовать Critical Sections или другие методы синхронизации для доступа к данным.
  5. Фильтры паролей должны быть установлены как на PDC, так и на BDC Windows NT. Для Windows 2000 на все контроллеры доменов должны быть установлены фильтры паролей.
  6. Модули Password Filter выполняются в контексте учетной записи LocalSystem.
  7. Модули Password Filter поддерживают уведомления об изменении паролей, приходящие только от Windows 95, Windows 98, Windows Me, Windows NT, Windows 2000 и Windows XP, Windows 2003 Server.

Следуя этим правилам, администратор сможет избежать ситуации, при которой модуль станет новой «дырой» в системе безопасности операционной системы, вместо того чтобы послужить ее более надежной защитой.

Среда разработки

Пример, приведенный в статье, разработан и откомпилирован в MSVC 6.0. Для написания модуля PasswordFilter не нужны какие-либо дополнительные библиотеки, помимо входящих в MS Visual C++ 6.0 и старше. В проект необходимо включить заголовочный файл Ntsecapi.h.

Установка модуля Password Filter

Установить созданный фильтр несложно. Процедура установки состоит из трех этапов.

  1. Необходимо скопировать dll-библиотеку в папку Windows %SYSTEMROOT% или %SYSTEMROOT%SYSTEM32.
  2. Далее требуется изменить параметр Notification Packages в разделе реестра HKEY_LOCAL_MACHINESYSTEM CurrentControlSetControlLsa. Если такой параметр отсутствует, его необходимо добавить. Его тип - REG_MULTI_SZ. Если же он есть, то находящиеся в нем строки удалять нельзя, можно лишь добавить новую. Она должна представлять собой имя модуля Password Filter без расширения. Так, если наш модуль называется rtPwdFltr.dll, то в реестр следует добавить rtPwdFltr. Скорее всего, в списке будет одно значение - scecli. Эта библиотека реализует многие функции защиты, и удалять ее нельзя.
  3. Последнее действие состоит в изменении локальной политики безопасности. Необходимо открыть оснастку Local Security Policy, выбрать Account Policy, Password Policy и установить значение Passwords must meet complexity requirements в Enabled.

Eсли написанный модуль работает некорректно и приводит к сбою системы, можно воспользоваться консолью восстановления (потребуется пароль учетной записи администратора) и удалить файл модуля. Затем компьютер следует перезагрузить.

Александр Эпштейн — независимый разработчик и консультант по проблемам низкоуровневого программирования; руководил департаментами разработки программного обеспечения в нескольких крупных российских компаниях. С ним можно связаться по адресу: alex_ep@hotmail.com.