воскресенье, 31 июля 2011 г.

Windows: теория и практика синхронизации паролей

28 июня этого года компания Microsoft запустила новый облачный сервис Office 365. Ожидается, что его основными потребителями станут предприятия, желающие сэкономить время и средства на поддержании собственной инфраструктуры офисного ПО. Поэтому, одной из важнейших задач, возникающей в подобном контексте использования, является синхронизация с облаком службы каталогов Active Directory предприятия. Для её решения подписчикам предоставляется специальное приложение, автоматизирующее данный процесс. Но оно имеет очень смешное ограничение, а именно: оно не умеет синхронизировать пароли пользователей. Компания Parallels, в которой я имею честь работать, имеет аналогичное решение в составе продукта Parallels Operations Automation уже более полутора лет. Вот только наше приложение Customer Directory Integration, в отличие от Microsoft Directory Sync, достаточно легко справляется с задачей синхронизации паролей. В этой заметке я хочу приоткрыть завесу тайны над тем, как оно это делает.

Теория

У данной задачи может быть и более приземлённое практическое применение, нежели перенос пользователей локальной Active Directory в облако. Например, это может быть автоматическая синхронизация паролей между Active Directory и используемыми в вашей компании системами контроля версий и отслеживания ошибок. Удобно, не правда ли, когда все применяемые в работе компоненты имеют единые атрибуты безопасности?

Однажды я уже касался темы хранения паролей в ОС Windows. Из той заметки вполне очевидно следует, что восстановить пароль из хэша, сохранённого в недрах операционной системы, весьма трудно. И начиная с определённых длин пароля, эта задача становится практически неразрешимой. Поэтому о таком методе синхронизации можно смело забыть.

Точно также можно смело забыть о попытках перехватить пароль в момент его ввода пользователем при включении компьютера. Операционная система гарантирует, что единственный компонент, имеющий к нему доступ, - это процесс WinLogon и его расширители. Но даже если вы напишете свой Credential Provider (расширитель WinLogon), который сможет перехватывать пароли, толку от него будет маловато. Чтобы этот метод работал, ваш расширитель должен быть установлен на всех компьютерах всех пользователей доменов. А что, если кто-то принципиально пользуется только своим лэптопом и считает неприемлемым устанавливать какие-то сомнительные утилиты, влияющие на процесс входа в систему? Более того, все расширители должны уметь связываться с неким единым центром и передавать ему данные.

В процедуре управления паролем есть один момент времени, когда пароль вполне легально доступен контроллеру доменов в открытом виде. При создании нового пользователя или изменении пароля у существующего, контроллер доменов должен иметь явный доступ к паролю для того, чтобы проверить пароль на соответствие политикам управления паролями (минимальная длинна, набор используемых символов и так далее), вычислить хэш и сохранить его во внутреннее хранилище. Было бы здорово получить доступ к паролю в этот самый момент. И к счастью такая возможность существует. Она реализуется с помощью Фильтров Паролей (Password Filters).

Фильтры паролей предназначены в основном для реализации собственных политик управления паролями. Более того, стандартные политики реализованы через эту же самую технологию. Мы же в Parallels воспользовались этой возможностью для решения задачи синхронизации паролей. При таком подходе необходимо всего лишь установить приложение фильтра на каждый контроллер домена, а не на каждую рабочую станцию каждого сотрудника.

Практика
Так что же представляет из себя фильтр паролей? По сути, это всего лишь динамически линкуемая библиотека (DLL), реализующая и экспортирующая наружу функции со следующими сигнатурами:
BOOLEAN NTAPI
InitializeChangeNotify();

BOOLEAN NTAPI
PasswordFilter(
    __in PUNICODE_STRING AccountName,
    __in PUNICODE_STRING FullName,
    __in PUNICODE_STRING Password,
    __in BOOLEAN SetOperation
);

NTSTATUS NTAPI
PasswordChangeNotify(
    __in PUNICODE_STRING UserName,
    __in ULONG RelativeId,
    __in PUNICODE_STRING NewPassword
);

Достаточно просто поместить такую библиотеку в каталог %SystemRoot%\System32 контроллера доменов, а её имя (без разрешения .dll) добавить к списку фильтров в под-ключе Notification Packages ключа реестра HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa и перезагрузить систему (естественно эти операции нужно повторить на всех контроллерах доменов сети предприятия). При следующей загрузке данная библиотека будет подгружена в контекст процесса LSA (Local Security Authority), о чём она будет уведомлена вызовом функции InitializeChangeNotify.

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

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

Аналогичным образом обрабатывается и пароль любого вновь создаваемого пользователя домена. Отличие заключается лишь в том, что в этом случае пароль вводится непосредственно на контроллере домена и попадает в его LSA напрямую.

Замечательно, а что же делать с теми пользователями, которые существуют давно и пароль менять не собираются? Мы попросим их это сделать принудительно. Если у соответствующего объекта-пользователя в Active Directory обнулить свойство pwdLastSet, то при следующем входе под этой учётной записью, система сообщит, что пароль устарел, и попросит ввести новый. Собственно это именно то, чего мы и добивались.

Легко в учении - тяжело в бою
В заключении хочу обратить внимание на некоторые подводные камни, сопровождающие реализацию своего парольного фильтра.

Во-первых, имейте в виду: LSA подгружает парольные фильтры на достаточно ранних стадиях загрузки системы. Поэтому когда ваш фильтр получаете уведомление о загрузке - InitializeChangeNotify - огромная часть системных компонентов и сервисов ещё не доступна, что может приводить к весьма неожиданным побочным эффектам. Например, в виде сервиса реализован Windows Event Log. Поэтому любые попытки отправлять в него сообщения на этапе инициализации фильтра в лучшем случае будут завершаться ничем, в худшем приводить к падениям.

Во-вторых, фильтр исполняется в контексте процесса критичного для функционирования всей системы. Если одна из функций PasswordFilter или PasswordChangeNotify завершится падением, это приведёт к остановке процесса LSA, в след за которым последует перезагрузка всей системы. К счастью последствия от падения функции InitializeChangeNotify будут не столь ужасны: ваш фильтр всего лишь не будет загружен.

В-третьих, фильтр, как и весь процесс LSA, исполняется под учётной записью локальной системы. Это означает, что удалённые операции в пределах домена, инициированные вашим фильтром, будут исполняться под доменной учётной записью компьютера.

Ну и в завершении: всё сказанное справедливо не только для контроллеров доменов, но и для прочих серверов и даже для клиентских систем. Парольные фильтры могут быть установлены на любой машине, и будут задействованы любой LSA.

Комментариев нет:

Отправить комментарий