Перейти к содержимому

Хранение истории паролей LAPS

01.10.2018

LAPS (Local Administrator Password Solution) от Microsoft широко применяется как средство защиты административных учетных записей на доменных компьютерах. LAPS предоставляет механизм централизованного назначения уникальных паролей, их регулярной смены и не дает возможному злоумышленнику воспользоваться локальными учетными записями для организации атак типа Pass-the-Hash с целью получения контроля над все большим числом устройств в сети организации. Немаловажно и то, что LAPS является штатным, и к тому же бесплатным средством обеспечения безопасности.

В процессе эксплуатации большого парка компьютеров на рабочих местах редко, но с неизменной регулярностью, приходится сталкиваться с ситуацией, когда пароль LAPS не подходит. Причем не подходит, когда в нем есть потребность: компьютер (именно клиентский компьютер, с серверами такое не наблюдал) теряет связь с доменом, и чтобы повторно ввести компьютер в домен, необходимо войти в систему с учетной записью локального администратора.

Казалось бы, применение LAPS дает только преимущество, ведь для каждого компьютера мы всегда можем посмотреть последний актуальный пароль локального администратора. Однако именно этот актуальный и сохраненный в Active Directory пароль не подходит.

Сам факт потери компьютером регистрации в домене не зависит от того, внедрена ли в организации технология LAPS или нет. Клиентский компьютер все равно приходится повторно вводить в домен. Другой факт, который удалось выяснить опытным путем — войти в систему удается по предыдущему, ранее установленному паролю LAPS. Хотя нет полной уверенности в последующем описании, но можно предположить, что проблема возникает в результате целой последовательности событий и проявляется на клиентах LAPS именно по причине частой смены пароля. Возможно, следующая последовательность действий приводит к описываемой, повторюсь, достаточно редкой ошибке.

  1. Клиентский компьютер инициирует очередную установку обновлений Windows Update
  2. Служба System Restore выполняет создание теневой копии системных файлов
  3. Выполняется установка обновлений, и ожидается перезагрузка
  4. Примерно в этот период происходит смена пароля LAPS
  5. Примерно в тот же период происходит автоматическая смена пароля компьютера в домене
  6. Установка обновлений не завершается удачно
  7. Операционная система предпринимает откат к предыдущей конфигурации (до установки обновлений)
  8. Компьютер не может установить связь с доменом, поскольку конфигурация операционной системы была восстановлена со старым паролем учетной записи в домене, который уже не актуален
  9. Пароль LAPS не подходит, поскольку после восстановления действует не последний, а предыдущий пароль LAPS.

Как видим, последовательность событий, которые должны иметь место, чтобы проблема проявилась, довольно нетривиальная, но это действительно иногда происходит. Сообщения в форумах, см. например, это обсуждение тоже подтверждают, что в некоторых случаях при потере компьютером связи с доменом зарегистрироваться в системе удается не с текущим, а с предыдущим паролем LAPS, при условии, конечно, если пароль сохранен. Таким образом, приходим к выводу, что, по крайней мере, для каждого клиентского компьютера необходимо хранить не только текущий пароль LAPS, но и историю паролей или хотя бы предыдущий пароль.

Прежде чем переходить к описанию решения, рассмотрим, в каком виде можно хранить историю паролей LAPS. Напомню, что текущие пароли LAPS сохраняются в объектах компьютеров в Active Directory в “скрытом” атрибуте ms-Mcs-AdmPwd. Разрешение на чтение объектов AD недостаточно для доступа к этому атрибуту, и компьютеру или пользователю необходимо еще обладать особым разрешением Control Access, даже чтобы прочитать пароль. Правда, хранится пароль в открытом виде.

Отмечу заодно, что атрибут ms-Mcs-AdmPwd не реплицируется на RODC, и если у вас есть в инфраструктуре это чудо, будьте внимательны, обращаясь к указанному атрибуту из своих скриптов: лучше явно укажите в параметре -Server writable домен-контроллер.

Чем привлекательно хранение пароля в атрибуте объекта компьютера в Active Directory? Он всегда доступен из любого расположения сети организации. Разумеется, при наличии достаточных разрешений. Хранение предыдущих паролей можно было бы организовать по аналогии, в каком-либо другом атрибуте AD, но в итоге отказался от такого варианта. Да, далеко не все атрибуты из существующих задействованы, но у каждого есть свое назначение. И хотелось сделать так, чтобы решение по дополнительному хранению паролей не оставляло следов, если в нем больше не будет надобности

На всех домен-контроллерах есть реплицируемые разделяемые папки Sysvol для групповых политик и Netlogon для сценариев подключения и много чего другого. Папка Netlogon досталась в наследство еще от Windows NT. Важно то, что содержимое этих папок реплицируется по всем контроллерам в пределах домена, а значит, доступно отовсюду без какой-либо дополнительной настройки. И доступом на чтение в этих папках обладают все доменные пользователи.

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

Решение включает два сценария, написанные на PowerShell (подойдут версии 4.0 и выше). Один сценарий – encrypt.ps1 — предназначен для запуска по расписанию на сервере или на административной рабочей станции. Для шифрования необходимо разместить в папке, путь к которой указывается в переменной $certpath, сертификат, на котором будет выполняться шифрование информации. Сертификат в своей области действия должен предусматривать шифрование ключей. Особых требований нет, например, можно использовать сертификат, который легко получить на шаблоне Шифрующая файловая система (EFS), причем самоподписанный сертификат тоже подойдет. Также сертификат, размещаемый для работы со сценарием encrypt.ps1, не должен содержать закрытый ключ. Хуже, конечно, не будет, но и безопасности тоже не будет.

# encrypt.ps1
# Шифрование паролей LAPS с целью хранения

Function Encrypt-Asymmetric {
    [CmdletBinding()]
    [OutputType([System.String])]
    param(
        [Parameter(Position=0, Mandatory=$true)][ValidateNotNullOrEmpty()][System.String]
        $ClearText,
        [Parameter(Position=1, Mandatory=$true)][ValidateNotNullOrEmpty()][ValidateScript({Test-Path $_ -PathType Leaf})][System.String]
        $PublicCertFilePath
    )
    # Шифрование строки на открытом ключе
    $PublicCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($PublicCertFilePath)
    $ByteArray = [System.Text.Encoding]::UTF8.GetBytes($ClearText)
    $EncryptedByteArray = $PublicCert.PublicKey.Key.Encrypt($ByteArray,$true)
    $Base64String = [Convert]::ToBase64String($EncryptedByteArray)
 
    Return $Base64String
}

function GetBase64Hash {

Param ([string]$strVar)
# Вычисление хэш-функции MD5

$md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
$utf8 = new-object -TypeName System.Text.UTF8Encoding
$strVarWithSalt = $strVar + "yY"
$strBase64 = [System.Convert]::ToBase64String($md5.ComputeHash($utf8.GetBytes($strVarWithSalt)))
Return $strBase64 -replace "==","="

}

$dc =  (Get-ADDomainController -Discover -Writable).hostname[0]

$pwdfile = "\\" + $dc + "\netlogon\laps_pwd.txt"
# Укажите путь к файлу сертификата
$certPath = "C:\cert\usercert.cer"

$txtcrypt = $null
$htCrypt = @{}

If (Test-Path $pwdfile) {
    $txtcrypt = Get-Content -Path $pwdfile -Encoding OEM
}

If (($txtcrypt.count -eq 1) -and ($txtcrypt.length -gt 0)) {

    $arrcrypt = $txtcrypt -split "="
    
    If ($arrcrypt[3].length -gt 0) {
        $pwd2crypt = $arrcrypt[3] + "="
    }
    Else {
        $pwd2crypt = ""
    }
    
    $pwd1crypt = $arrcrypt[2] + "="
    $pwd1hash = $arrcrypt[1] + "="
    $namehash = $arrcrypt[0] + "="

    $htCrypt.Add($namehash,@($pwd1hash, $pwd1crypt, $pwd2crypt))
}

If ($txtcrypt.count -gt 1) {
    foreach ($strcrypt in $txtcrypt) {
        If ($strcrypt.length -gt 0) {
            $arrcrypt = $strcrypt -split "="
    
            If ($arrcrypt[3].length -gt 0) {
                $pwd2crypt = $arrcrypt[3] + "="
            }
            Else {
                $pwd2crypt = ""
            }
    
            $pwd1crypt = $arrcrypt[2] + "="
            $pwd1hash = $arrcrypt[1] + "="
            $namehash = $arrcrypt[0] + "="

            $htCrypt.Add($namehash,@($pwd1hash, $pwd1crypt, $pwd2crypt))
        }
    }
}

$dt = (Get-Date).Adddays(-90)
# В параметре SearchBase укажите домен или OU с объектами типа Компьютер
$colComputers = Get-ADComputer -Filter {(enabled -eq $True) -and (whenChanged -ge $dt) -and (ms-Mcs-AdmPwd -like "*")} -SearchBase "OU=Main,DC=domain,DC=com" -SearchScope Subtree -Properties name,ms-Mcs-AdmPwd -Server $dc | Select-Object name,@{Name="AdmPwd";Expression={$_."ms-Mcs-AdmPwd"}}
# Добавьте другие расположения, если необходимо
# $colComputers += Get-ADComputer -Filter {(enabled -eq $True) -and (whenChanged -ge $dt) -and (ms-Mcs-AdmPwd -like "*")} -SearchBase "OU=Remote,DC=domain,DC=com" -SearchScope Subtree -Properties name,ms-Mcs-AdmPwd -Server $dc | Select-Object name,@{Name="AdmPwd";Expression={$_."ms-Mcs-AdmPwd"}}

$txtcrypt = ""

foreach ($oComputer in $colComputers) {

    $namehash = GetBase64Hash -strVar $oComputer.Name
    # $namehash = $oComputer.Name + "="
    $pwd1 = $oComputer.AdmPwd
    $pwd1hash = GetBase64Hash -strVar $pwd1
    $arrcrypt = $htCrypt[$namehash]
    If ($arrcrypt -eq $null) {
        $pwd1crypt = Encrypt-Asymmetric -ClearText $pwd1 print -PublicCertFilePath $certPath
        $strcrypt = $namehash + $pwd1hash + $pwd1crypt
        $txtcrypt += $strcrypt + "`r`n"
    } else {
        If ($arrcrypt[0] -eq $pwd1hash) {
            If (($arrcrypt[2] -eq "") -Or ($arrcrypt[2] -eq $null)) {
                $strcrypt = $namehash + $arrcrypt[0] + $arrcrypt[1]
            } Else {
                $strcrypt = $namehash + $arrcrypt[0] + $arrcrypt[1] + $arrcrypt[2]
            }
            $txtcrypt += $strcrypt + "`r`n"
        } else {
            $pwd1crypt = Encrypt-Asymmetric -ClearText $pwd1 -PublicCertFilePath $certPath
            $strcrypt = $namehash + $pwd1hash + $pwd1crypt + $arrcrypt[1]
            $txtcrypt += $strcrypt + "`r`n"
        }
    }
}

If ($colComputers.count -gt 0) {
    $txtcrypt | Out-File -FilePath $pwdfile -Encoding OEM -Force
}

Назначенное задание должно запускаться в контексте учетной записи, способной читать атрибуты ms-Mcs-AdmPwd объектов компьютеров. При первом проходе сценарий перечисляет компьютеры, учетные записи которых не заблокированы, имеют непустой атрибут ms-Mcs-AdmPwd и дату последнего изменения менее 90 дней назад. Текущие (на момент выполнения сценария) пароли шифруются и сохраняются в файле, путь к которому содержится в переменной $pwdfile. Хотя файл текстовый, но вся информация в нем зашифрована. Информация в каждой строке соответствует компьютеру и содержит: хэш имени компьютера, хэш текущего (на момент выполнения сценария) пароля, зашифрованный текущий пароль и, если есть, зашифрованный предыдущий пароль. Факт изменения пароля выясняется сравнением хэша текущего пароля с хэшем, сохраненным в файле. При различии сохраненный в файле пароль снова сохраняется в файле, но в поле предыдущего пароля. Благодаря использованию хэшей не требуется расшифровывать пароли для их сравнения.

Для просмотра текущего (на момент последнего запуска назначенного задания) и предыдущего паролей используется сценарий laps_pwd.ps1. Его можно запускать с любого доменного компьютера, однако для работы необходим ранее подготовленный сертификат, но уже содержащий закрытый ключ. Его следует установить в защищенное хранилище в профиле пользователя, который будет работать со сценарием. В переменной $certThumbprint укажите отпечаток (ThumbPrint) установленного сертификата.

# laps_pwd.ps1
# Средство просмотра сохраненных паролей

Function Decrypt-Asymmetric
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param(
        [Parameter(Position=0, Mandatory=$true)][ValidateNotNullOrEmpty()][System.String]
        $EncryptedBase64String,
        [Parameter(Position=1, Mandatory=$true)][ValidateNotNullOrEmpty()][System.String]
        $CertThumbprint
    )
    # Дешифрование строки на закрытом ключе
    # Сертификат с закрытым ключом должен быть загружен в хранилище LocalMachine\My (Personal)
    $Cert = Get-ChildItem cert:\CurrentUser\My | where { $_.Thumbprint -eq $CertThumbprint }
    if($Cert) {
        $EncryptedByteArray = [Convert]::FromBase64String($EncryptedBase64String)
        $ClearText = [System.Text.Encoding]::UTF8.GetString($Cert.PrivateKey.Decrypt($EncryptedByteArray,$true))
    }
    Else {Write-Error "Certificate with thumbprint: $CertThumbprint not found!"}
 
    Return $ClearText
}

function GetBase64Hash {

    Param ([string]$strVar)

    $md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
    $utf8 = new-object -TypeName System.Text.UTF8Encoding
    $strVarWithSalt = $strVar + "yY"
    $strBase64 = [System.Convert]::ToBase64String($md5.ComputeHash($utf8.GetBytes($strVarWithSalt)))
    Return $strBase64 -replace "==","="

}

function RetrievePwd () {
    If ($namehash -eq ($arrcrypt[0] + "=")) {
        $script:bFound = $True
        $pwd1crypt = $arrcrypt[2] + "="
        $script:pwd1 = Decrypt-Asymmetric -EncryptedBase64String $pwd1crypt -CertThumbprint $certThumbprint
        If ($arrcrypt[3].length -gt 0) {
            $pwd2crypt = $arrcrypt[3] + "="
            $script:pwd2 = Decrypt-Asymmetric -EncryptedBase64String $pwd2crypt -CertThumbprint $certThumbprint
        }
    }
}

$dc =  (Get-ADDomainController -Discover -Writable).hostname[0]

$pwdfile = "\\" + $dc + "\netlogon\laps_pwd.txt"
# Укажите отпечаток сертификата
$certThumbprint = "3918BEA009C80714BBE678DD02C8F972E3B9F373"

Write-Host
$strComputer = ""
$strComputer = Read-Host -Prompt 'Введите имя компьютера (или нажмите "Enter" для завершения)'
If ($strComputer.length -le 1) {
    break
}

$namehash = GetBase64hash -strVar $strComputer.ToUpper()

$txtcrypt = $null
$htSavedPwd = @{}

If (Test-Path $pwdfile) {
    $txtcrypt = Get-Content -Path $pwdfile -Encoding OEM
}

$bFound = $false
$pwd1 = ""
$pwd2 = ""

If (($txtcrypt.count -eq 1) -and ($txtcrypt.length -gt 0)) {
    $arrcrypt = $txtcrypt -split "="
    RetrievePwd
}

If ($txtcrypt.count -gt 1) {
    foreach ($strcrypt in $txtcrypt) {
        If ($strcrypt.length -gt 0) {
            $arrcrypt = $strcrypt -split "="
            RetrievePwd
        }
        If ($bFound) {break}
    }
}

Write-Host
If ($bFound) {
    Write-Host "Последний сохраненный пароль: " $pwd1
    If ($pwd2.Length -gt 1) {
        Write-Host "Предыдущий сохраненный пароль:" $pwd2
    }
} else {
    Write-Host "Компьютер не найден или не настроен для LAPS"
}

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

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

В заключение расскажу о еще одном способе восстановить старый пароль LAPS, который можно применить даже без предварительного сохранения истории паролей. Но для этого на одном из-контроллеров домена должна быть развернута Корзина AD. Как известно, Корзина AD позволяет сохранять предыдущие состояния Active Directory и обеспечивать их просмотр стандартными средствами типа Active Directory Users and Computers. В указанном случае следует подключиться к теневой копии AD, скажем, месячной давности, включить Advanced Features и в свойствах выбранного объекта компьютера перейти на закладку Attribute Editor. Старый пароль LAPS следует поискать в атрибуте ms-Mcs-AdmPwd. Если политики LAPS предусматривают другой срок смены паролей, отличный от 30 дней, то именно на такой срок давности следует ориентироваться, выбирая для просмотра одну из предыдущих теневых копий AD.

 

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s

%d такие блоггеры, как: