O heslech v Active Direcotry (AD DS) jsem už psal třeba tady a tady a tady. Ale dnes jsem dostal mailem otázku, jak zjistím všechny účty, které si nezměnily svoje výchozí heslo.
Takový skript bude o něco složitější, takže můžeme začít nejprve jednoduše. Zjistíme všechny uživatelské účty, které si už dlouho nezměnili heslo. Déle v mém skriptu znamená déle než třicet dnů:
dsquery * domainRoot -filter "(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(pwdLastSet<=$(([DateTime]::Now.AddDays(-30) - [DateTime]::Parse('1601-01-01')).Ticks))(pwdLastSet>=1))" -attr sAMAccountName,userPrincipalName
Používám k tomu PowerShell a v něm utilitu dsquery, protože ActiveDirectory module každý nemá. Používám standardní LDAP search string. Uvnitř začínám s filterm na objectCategory=person, protože tohle je na všech verzích schematu indexované a tím pádem to bude maximálně rychlé. Vyhledají se tím user a inetOrgPerson účty a také contact objekty, které ale nechceme. Dále tedy potřebujeme filter na objectClass=user, abychom vyhodili kontakty.
Následně tam mám filter na userAccountControl, který nesmí obsahovat bit 2 - tedy účet nesmí být zakázaný (disabled), technickými slovy mít příznak ACCOUNTDISABLE. V tomhle filtříku se používá bitový AND (bitwise and) vyhledávací MS OID modifikátor 1.2.840.113556.1.4.803.
A dále už k tomu nejdůležitějšímu - tedy pwdLastSet. Atribut pwdLastSet obvykle obsahuje datum poslední modifikace hesla. Píšu obvykle, protože to není vždy. Pokud zaškrtnete na účtu, že si uživatel musí změnit heslo při příštím přihlášení (user must change password at next logon), tak to ve skutečnosti pouze vynuluje pwdLastSet atribut. Pokud tohle zaškrtávátko někdy později vypnete, tak do pwdLastSet nastaví natvrdo aktuální datum.
Takže tomuhle atributu je potřeba věřit jenom tak nějak opatrně :-) Proto testuju, jestli je sice starší, než 30 dnů, ale současně musí být hodnota větší než 0. V LDAP filterech nemůžete používat ostrou nerovnost, takže nezbývá než porovnávat na větší rovno 1.
Co tímhle vyhledávacím filtrem tedy najdete? Účty, které si už opravdu dlouho nezměnily heslo. Ale nenajdete účty, kterým bylo manipulováno s příznakem user must change password at next logon.
Ale už i tohle by mohlo někomu stačit.
Lepší vyhledání všech nezměněných hesel pomocí replikačních metadat
Možná vám nestačí nedokonalý atribut pwdLastSet. Máme jinou metodu? Ano. Musíme se podívat na vnitřní replikační metadata (replication metadata) každého jednotlivého uživatele a zjistit, kdy se naposledy skutečně změnilo heslo v atributu unicodePwd. Bez téhle informace nemůže fungovat replikace, takže to je naprosto přesná a jistá hodnota. Akorát to bude trvat déle, protože replikační metadata (přesněji řečeno žádný computed/constructed atribute) nelze vyhledat rovnou pomocí LDAP search.
Musíme si načíst každý účet, z něho vyndat constructed atribut msDS-ReplAttributeMetaData a z něho to vyčíst. Upozorňuji, že to funguje až od DC řadičích verze Windows 2003. Na řadičích Windows 2000 tento atribut nebyl k dispozici.
function Get-UnchangedPasswords ([int] $sincePastDays)
{
$root = [ADSI] 'LDAP://RootDSE'
$searcher = [ADSISearcher] ([ADSI] ('LDAP://{0}' -f $root.defaultNamingContext.Value))
$searcher.Filter = '(&(objectCategory=person)(objectClass=user))'
$searcher.SearchScope = 'subTree'
[Collections.ArrayList] $nonChangedPasswords = @()
$pastDate = [DateTime]::Now.AddDays(- $sincePastDays)
foreach ($oneResult in $searcher.FindAll()) {
$user = [ADSI] $oneResult.Path
$user.RefreshCache('replPropertyMetadata')
$user.RefreshCache('msDS-ReplAttributeMetaData')
$replBin = $user.Get('replPropertyMetadata')
$replBinText = [BitConverter]::ToString($replBin)
$replTxt = $user.Get('msDS-ReplAttributeMetaData')
$unicodePwdReplMeta = $replTxt | % { [xml] $_ } | ? { $_.DS_REPL_ATTR_META_DATA.pszAttributeName -eq 'unicodePwd' } | select -Expand DS_REPL_ATTR_META_DATA
$theDate = [DateTime]::Parse($unicodePwdReplMeta.ftimeLastOriginatingChange)
if ($theDate -le $pastDate) {
$nonChangedPwd = New-Object PSCustomObject
Add-Member -Input $nonChangedPwd -MemberType NoteProperty -Name sam -Value $user.sAMAccountName.Value
Add-Member -Input $nonChangedPwd -MemberType NoteProperty -Name upn -Value $user.userPrincipalName.Value
Add-Member -Input $nonChangedPwd -MemberType NoteProperty -Name lastPwdChange -Value $theDate
Add-Member -Input $nonChangedPwd -MemberType NoteProperty -Name isDisabled -Value ((([int] $user.userAccountControl.Value) -band 2) -eq 2)
Add-Member -Input $nonChangedPwd -MemberType NoteProperty -Name created -Value $user.whenCreated.Value
[void] $nonChangedPasswords.Add($nonChangedPwd)
}
}
return $nonChangedPasswords
}
No a to je celá věda :-) Další třídění a filtrování už zvládnete sami.