7 min czytania

Porządki w Active Directory: nieaktywne konta, puste grupy i stare uprawnienia

Katalog, w którym nikt nie sprząta, zbiera konta osób, które odeszły, komputery, których już nie ma, i uprawnienia nadane na chwilę kilka lat temu. Zapytania, które to pokazują, i procedura usuwania, którą można cofnąć.

Rys. 1. Konto przechodzi przez wyłączenie i 30 dni kwarantanny, zanim zostanie usunięte; do tego momentu zmianę cofa się jednym poleceniem, a później jeszcze przez kosz AD.

Przegląd przed audytem pokazuje, że katalog ma wyraźnie więcej włączonych kont użytkowników niż firma ma pracowników. Są wśród nich konta osób, które odeszły kilka lat temu, konta testowe z hasłem, które nigdy nie wygasa, i komputery, które zniknęły z sieci, ale nadal mają konto i ważne hasło maszyny. Każde z nich jest wejściem, którego nikt nie pilnuje, i pozycją, którą trzeba wyjaśnić audytorowi.

Porządki w katalogu nie wymagają narzędzi firm trzecich. Wystarczy kilka zapytań PowerShell uruchamianych raz w miesiącu i procedura, w której każdą zmianę da się cofnąć przez co najmniej 30 dni.

Jak katalog mierzy aktywność

Active Directory przechowuje datę ostatniego logowania w dwóch atrybutach. lastLogon jest dokładny, ale nie jest replikowany: każdy kontroler domeny zna tylko logowania, które sam obsłużył. lastLogonTimestamp jest replikowany, ale aktualizowany tylko wtedy, gdy zapisana wartość jest starsza niż 14 dni pomniejszone o losowe do 5 dni. W efekcie jego wartość może spóźniać się o 9 do 14 dni. Właściwość LastLogonDate w PowerShell to właśnie przeliczony lastLogonTimestamp.

Komputery mają dodatkowy wskaźnik: domyślnie zmieniają hasło konta maszyny co 30 dni. Komputer, którego PasswordLastSet jest starszy niż 90 dni, od dawna nie rozmawiał z kontrolerem domeny.

Mapa problemów

ProblemZapytanieCo zrobić
Nieaktywne konta użytkownikówSearch-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnlypotwierdzić z przełożonym lub kadrami, wyłączyć, przenieść do kwarantanny
Nieaktywne komputerySearch-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -ComputersOnlywyłączyć, przenieść, usunąć po 30 dniach
Hasło bez wygasaniaSearch-ADAccount -PasswordNeverExpires -UsersOnlykonta osobiste: zdjąć flagę; konta usług: gMSA albo właściciel i plan zmiany hasła
Konta usług z SPNGet-ADUser -LDAPFilter '(servicePrincipalName=*)'hasło co najmniej 25 znaków, właściciel w opisie, gMSA tam, gdzie się da
Puste grupyGet-ADGroup -LDAPFilter '(!(member=*))'sprawdzić użycie w uprawnieniach i GPO, przenieść, usunąć
Pozostałości adminCountGet-ADUser -LDAPFilter '(adminCount=1)'wyczyścić atrybut, przywrócić dziedziczenie uprawnień
Delegacja nieograniczonaGet-ADComputer -Filter 'TrustedForDelegation -eq $true'zamienić na delegację ograniczoną albo usunąć; dopuszczalna tylko na kontrolerach domeny

Pełne wersje zapytań, z filtrami i kolumnami do raportu, są w sekcji „Sprawdź u siebie”.

Nieaktywne konta użytkowników i komputerów

Konto użytkownika nieaktywne od 90 dni nie zawsze jest porzucone. Rzadko logują się konta awaryjne, konta usług uruchamiane raz na kwartał i konta osób na długim urlopie. Dlatego wyłączenie konta użytkownika poprzedza potwierdzenie z przełożonym albo z działem kadr, a wyjątki są oznaczone tak, żeby skrypt je pomijał.

Wyłączenie konta synchronizowanego z Entra ID blokuje także logowanie w chmurze, po najbliższym cyklu synchronizacji. Skrzynka pocztowa i licencja zostają do czasu usunięcia konta, więc decyzja o przekierowaniu poczty zapada przed tym krokiem.

Komputery są prostsze: jeśli LastLogonDate i PasswordLastSet są starsze niż 90 dni, maszyna nie pracuje w sieci. Często to sprzęt wycofany bez usunięcia konta albo maszyna przeinstalowana pod nową nazwą.

Hasła bez wygasania i konta usług

Flaga PasswordNeverExpires na koncie osobistym zwykle oznacza tymczasowe rozwiązanie sprzed lat. Na kontach usług jest częstsza i ma uzasadnienie, ale wymaga właściciela i planu zmiany hasła.

Szczególnie ważne są konta użytkowników z atrybutem servicePrincipalName. Każdy uwierzytelniony użytkownik domeny może poprosić o bilet Kerberos do takiej usługi i próbować złamać jej hasło poza siecią, bez śladu prób logowania. Długie, losowe hasło czyni to niepraktycznym, a konto gMSA z hasłem zmienianym automatycznie przez katalog rozwiązuje problem całkowicie. Konto krbtgt też ma SPN, ale jest kontem systemowym i nie podlega tym porządkom.

Puste grupy, adminCount i delegacja

Puste grupy. Grupa bez członków nie daje nikomu dostępu, ale zaśmieca katalog i może zostać przypadkowo użyta ponownie. Przed usunięciem trzeba sprawdzić, czy nie jest używana w uprawnieniach do udziałów, w filtrach zabezpieczeń GPO albo jako grupa odmowy, która celowo jest pusta. Grupy Domain Users i Domain Computers wyglądają na puste, bo członkostwo przez grupę podstawową nie jest zapisane w atrybucie member; zapytanie w sekcji „Sprawdź u siebie” pomija je przez isCriticalSystemObject.

adminCount. Konto usunięte z grupy uprzywilejowanej zachowuje adminCount=1 i wyłączone dziedziczenie uprawnień. Porządek polega na wyczyszczeniu atrybutu poleceniem Set-ADUser -Identity <konto> -Clear adminCount i włączeniu dziedziczenia w zakładce Zabezpieczenia › Zaawansowane. Więcej o grupach uprzywilejowanych w modelu warstw.

Delegacja nieograniczona. Komputer z flagą TrustedForDelegation dostaje bilety TGT wszystkich użytkowników, którzy się z nim uwierzytelniają. Przejęcie takiego serwera daje napastnikowi bilety każdego administratora, który się z nim połączył. Flagę mają z założenia kontrolery domeny; na innych maszynach zastępuje się ją delegacją ograniczoną do konkretnych usług.

Procedura: wyłącz, przenieś, usuń po 30 dniach

Każda zmiana zaczyna się od raportu zapisanego do pliku i uruchomienia z -WhatIf, które pokazuje, co polecenie zrobi, nie zmieniając niczego. Poniższy przykład dotyczy komputerów; dla użytkowników kroki są takie same, z Get-ADUser i Set-ADUser.

$cutoff = (Get-Date).AddDays(-90)
$stale = Get-ADComputer -Filter 'Enabled -eq $true' -Properties LastLogonDate, PasswordLastSet, whenCreated |
    Where-Object { (-not $_.LastLogonDate -or $_.LastLogonDate -lt $cutoff) -and $_.PasswordLastSet -lt $cutoff -and $_.whenCreated -lt $cutoff }
$stale | Select-Object Name, LastLogonDate, PasswordLastSet, DistinguishedName |
    Export-Csv -Path .\nieaktywne-komputery.csv -NoTypeInformation -Encoding UTF8
# Najpierw z -WhatIf; po sprawdzeniu wyniku uruchom ponownie bez tego parametru
$quarantine = 'OU=Do usuniecia,DC=corp,DC=example,DC=com'
$stamp = Get-Date -Format 'yyyy-MM-dd'
foreach ($c in $stale) {
    Disable-ADAccount -Identity $c.DistinguishedName -WhatIf
    Set-ADComputer -Identity $c.DistinguishedName -Description "Wyłączone $stamp, brak logowania od 90 dni" -WhatIf
    Move-ADObject -Identity $c.DistinguishedName -TargetPath $quarantine -WhatIf
}

Przez 30 dni zmianę cofa się poleceniem Enable-ADAccount i przeniesieniem obiektu z powrotem. Po tym czasie obiekty z kwarantanny są usuwane. W jednostce kwarantanny nikt nie modyfikuje obiektów, więc whenChanged odpowiada dacie przeniesienia.

$quarantine = 'OU=Do usuniecia,DC=corp,DC=example,DC=com'
$limit = (Get-Date).AddDays(-30)
Get-ADComputer -SearchBase $quarantine -Filter 'Enabled -eq $false' -Properties whenChanged |
    Where-Object { $_.whenChanged -lt $limit } |
    Remove-ADObject -Recursive -WhatIf

Ostatnią siatką bezpieczeństwa jest kosz Active Directory. Włącza się go poleceniem Enable-ADOptionalFeature -Identity 'Recycle Bin Feature' -Scope ForestOrConfigurationSet -Target 'corp.example.com', przy poziomie funkcjonalnym lasu co najmniej Windows Server 2008 R2. Tej operacji nie da się cofnąć. Usunięty obiekt można wtedy przywrócić z wszystkimi atrybutami i członkostwem w grupach, domyślnie przez 180 dni.

Get-ADObject -Filter 'isDeleted -eq $true -and Name -like "PC-0142*"' -IncludeDeletedObjects |
    Restore-ADObject

Sprawdź u siebie

Wszystkie polecenia poniżej tylko odczytują katalog.

Nieaktywne konta użytkowników i komputerów. Każda pozycja to albo udokumentowany wyjątek, albo kandydat do procedury. Jeśli konto ma puste LastLogonDate, nie logowało się nigdy; sprawdź jego datę utworzenia, zanim uznasz je za porzucone.

Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly |
    Where-Object Enabled |
    Sort-Object LastLogonDate |
    Select-Object SamAccountName, LastLogonDate, DistinguishedName

Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -ComputersOnly |
    Where-Object Enabled |
    Sort-Object LastLogonDate |
    Select-Object Name, LastLogonDate, DistinguishedName

Hasła bez wygasania i konta usług z SPN. Konta osobiste na pierwszej liście są do poprawy. Na drugiej priorytetem są konta z PasswordLastSet sprzed kilku lat i bez właściciela w opisie.

Search-ADAccount -PasswordNeverExpires -UsersOnly |
    Where-Object Enabled |
    Select-Object SamAccountName, LastLogonDate

Get-ADUser -LDAPFilter '(servicePrincipalName=*)' -Properties servicePrincipalName, PasswordLastSet, Description |
    Where-Object SamAccountName -ne 'krbtgt' |
    Select-Object SamAccountName, PasswordLastSet, Description, @{ n = 'SPN'; e = { $_.servicePrincipalName -join '; ' } }

Puste grupy i konta z adminCount=1. Puste grupy wymagają sprawdzenia użycia przed usunięciem. Każde konto z adminCount=1, które dziś nie należy do żadnej grupy uprzywilejowanej, jest pozostałością do wyczyszczenia.

Get-ADGroup -LDAPFilter '(&(!(member=*))(!(isCriticalSystemObject=TRUE)))' -Properties whenChanged, Description |
    Select-Object Name, GroupScope, whenChanged, Description

Get-ADUser -LDAPFilter '(adminCount=1)' -Properties LastLogonDate |
    Select-Object SamAccountName, Enabled, LastLogonDate

Delegacja nieograniczona poza kontrolerami domeny. Dobry wynik to dwie puste listy.

# 524288 = TRUSTED_FOR_DELEGATION, 516 = grupa podstawowa kontrolerów domeny
Get-ADComputer -LDAPFilter '(&(userAccountControl:1.2.840.113556.1.4.803:=524288)(!(primaryGroupID=516)))' |
    Select-Object Name, DistinguishedName

Get-ADUser -LDAPFilter '(userAccountControl:1.2.840.113556.1.4.803:=524288)' |
    Select-Object SamAccountName