Active Directory (AD) セキュリティの管理は、特にパスワードを共有するアカウントを識別する場合や弱い認証手法を使用する場合に困難です。 パワフルで軽量な PowerShell ツールで、共有パスワードの使用を迅速に明らかにし、組織の AD セキュリティ姿勢を改善します。 LazyAdminFinder LazyAdminFinderとは? LazyAdminFinder は、DSInternals モジュールを活用する PowerShell スクリプトです。 共有 NTLM パスワードハッシュを検出し、報告します。 過去のハッシュデータをアーカイブして比較してパスワードの再利用を識別します。 リスクのあるアカウント行動を特定する包括的なレポートを提供する。 Active Directory 管理者向けに特別に設計された LazyAdminFinder は、パスワードセキュリティに関する迅速かつ操作可能な洞察を提供します。 なぜLazyAdminFinderを使うのか? Identify weak or compromised passwords. Security Improvement: Maintain AD integrity for audit and compliance purposes. Compliance: Quickly audit accounts without manually reviewing data. Efficiency: LazyAdminFinder はどのように機能しますか? Fetch and Filter User アカウント スクリプトは、ADユーザーアカウントを収集し、無効、機械、およびサービスアカウントをフィルタリングして、アクティブなユーザーにのみ焦点を当てます。 NTLM ハッシュ抽出とアーカイブ LazyAdminFinder はアカウントから NTLM ハッシュを抽出し、歴史的な比較のために SHA-256 ハッシュを安全にアーカイブします。 パスワード再利用検出 現在の実行を以前のアーカイブと比較することで、このツールはパスワードの再使用を検出し、潜在的なセキュリティリスクを伴うアカウントをマークします。 管理者または特権ユーザーが標準および管理アカウント間のパスワードを同期します。 ユーザー、特に管理者は、以前使用した値にパスワードを繰り返しリセットします。 共有パスワードグループ スクリプトは、同一の NTLM ハッシュを共有するアカウントを識別し、グループ化し、意図的なパスワード共有または複数のユーザーが独立して同じパスワードを選択することを示すことができます - しばしば弱いパスワードの習慣のために。 使用頻度推奨 LazyAdminFinder を組織のパスワードリセットポリシーに基づいて定期的に実行することを検討してください. たとえば、90 日ごとにパスワードリセットを要求するポリシーがある場合は、スクリプトを約 100 日ごとに実行するようにスケジュールしてください. このタイミングは、管理者や特権ユーザーが以前使用していたパスワードにパスワードをリセットする可能性のあるインスタンスをキャプチャすることを保証します. もし組織がパスワード再使用ポリシーを適用しない場合、定期的に LazyAdminFinder を実行することは、潜在的なセキュリティリスクを特定し、軽減するためにさらに重要になります。 LazyAdminFinderのインストール ステップ1:準備 PowerShell がシステムにインストールされていることを確認します。 必要なモジュールをインストール: Install-Module DSInternals STEP 2: Script を設定する 提供されたスクリプト内でこれらの重要なパラメータをカスタマイズする: ドメインコントローラー: $dc = "your-domain-controller.example.com" 名前の文脈: $nc = "DC=yourdomain,DC=com" 出力レポートコース: $outputReport = "C:\Path\To\SharedPasswordsReport.csv" アーカイブ ディレクトリ: $archiveDir = "C:\Path\To\HashArchives" ステップ3: Run the Script Execute the script as a Domain admin: powershell.exe -ExecutionPolicy Bypass -File "C:\Path\To\LazyAdminFinder.ps1" 結果をレビュー 詳細な CSV レポートが生成され、アカウントがパスワードを共有するか、弱いパスワードを独立して選択することを明確に強調します。 歴史的アーカイブは将来の比較のために安全に保存され、強力なパスワード管理を確保します。 LazyAdminFinderについて セキュリティ監査:共有されたパスワード、弱いパスワード、または妥協されたパスワードを定期的に特定し、修正します。 インシデント対応:セキュリティ インシデントの影響を受けたアカウントを迅速に検出します。 ルーチンコンプライアンスチェック:強力なアカウントセキュリティ慣行を維持することによって継続的なコンプライアンスを確保します。 特権アカウント管理:複数のアカウント間でパスワードを再利用または同期できる管理者または特権ユーザーを対象にします。 カスタマイズ LazyAdminFinder あなたの環境とニーズに応じてスクリプトを調整する自由に感じてください: 特定のアカウントタイプを含む/除外するためにフィルタリングロジックを変更します。 ストレージの設定に合わせてアーカイブおよびレポートパスを調整します。 必要に応じて、追加のレポート能力を統合します。 LazyAdminFinderを使用する利点 Automates complex AD security checks. Rapid Security Assessments: Clearly identifies potential security risks within AD. Enhanced Visibility: Enables administrators to swiftly address vulnerabilities before exploitation. Proactive Defense: LazyAdminFinder は、Active Directory のパスワード監査の重要なタスクを簡素化し、管理者が安全、コンプライアンス、効率的な IT 環境を維持できるようにします。 共有されたパスワードを発見し、セキュリティのリスクを修正し、LazyAdminFinderで容易にアドセンスのセキュリティを強化します。 #Another /\_[]_/\ # fine |] _||_ [| # ___ \/ || \/ # /___\ || # (|0 0|) || # __/{\U/}\_ ___/vvv # / \ {~} / _|_P| # | /\ ~ /_/ [] # |_| (____) # \_]/______\ Barberion # _\_||_/_ Production # (_,_||_,_) # Import-Module DSInternals # === Parameters === $dc = "your-domain-controller.example.com" $nc = "DC=yourdomain,DC=com" $outputReport = "C:\Path\To\SharedPasswordsReport.csv" $archiveDir = "C:\Path\To\HashArchives" # Ensure archive directory exists if (-not (Test-Path $archiveDir)) { New-Item -ItemType Directory -Path $archiveDir | Out-Null } $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss") # === 1. Fetch & filter AD user accounts === Write-Host "[+] Fetching AD user accounts (skipping machines, service & disabled)..." $filteredAccounts = Get-ADReplAccount -All -Server $dc -NamingContext $nc | Where-Object { $_.NTHash -and $_.SamAccountName } | Where-Object { $_.SamAccountName -notmatch '\$$' } | Where-Object { $_.SamAccountName -notmatch '^(?i)(svc|service)' } | Where-Object { if ($_.PSObject.Properties.Match('IsDisabled').Count -gt 0) { -not $_.IsDisabled } else { # bit 2 = disabled ( ($_.UserAccountControl -band 2) -eq 0 ) } } if (-not $filteredAccounts) { Write-Host "[-] No matching user accounts found. Exiting." exit } # === 2. Convert NTLM byte array to hex string === $filteredAccounts | ForEach-Object { $hex = [BitConverter]::ToString($_.NTHash) -replace '-', '' $_ | Add-Member -MemberType NoteProperty -Name NTLMHashString -Value $hex -Force } # === 3. Archive SHA-256 of each NTLM hash === Write-Host "[+] Archiving SHA-256 of each NTLM hash..." $sha256 = [System.Security.Cryptography.SHA256]::Create() $currentArchive = $filteredAccounts | ForEach-Object { $shaBytes = $sha256.ComputeHash($_.NTHash) $shaHex = [BitConverter]::ToString($shaBytes) -replace '-', '' [PSCustomObject]@{ SamAccountName = $_.SamAccountName HashSha256 = $shaHex } } $sha256.Dispose() $archiveFile = Join-Path $archiveDir "UserHashArchive_$timestamp.csv" $currentArchive | Export-Csv -Path $archiveFile -NoTypeInformation # === 4. Compare to previous archive === $allArchives = Get-ChildItem -Path $archiveDir -Filter "UserHashArchive_*.csv" | Sort-Object LastWriteTime -Descending if ($allArchives.Count -gt 1) { $previousFile = $allArchives[1].FullName Write-Host "[+] Comparing this run to previous archive: $previousFile" $prevData = Import-Csv $previousFile $reused = Compare-Object ` -ReferenceObject $prevData ` -DifferenceObject $currentArchive ` -Property SamAccountName, HashSha256 ` -IncludeEqual | Where-Object { $_.SideIndicator -eq '==' } | Select-Object SamAccountName, HashSha256 if ($reused) { $reuseReport = Join-Path $archiveDir "PasswordReuse_$timestamp.csv" Write-Host "[!] $($reused.Count) accounts reused their old hash; exporting to $reuseReport" $reused | Export-Csv -Path $reuseReport -NoTypeInformation } else { Write-Host "[+] No password‑reuse detected this run." } } else { Write-Host "[+] Only one archive present; skipping reuse comparison." } # === 5. Shared‑password grouping & report === Write-Host "[+] Grouping accounts by shared NTLM hash..." $sharedGroups = $filteredAccounts | Group-Object -Property NTLMHashString | Where-Object { $_.Count -gt 1 } if (-not $sharedGroups) { Write-Host "[-] No shared hashes found." exit } $reportResults = @() Write-Host "[+] Generating shared‑password report:" foreach ($g in $sharedGroups) { $h = $g.Name $users = $g.Group | ForEach-Object { $_.SamAccountName } $reportResults += [PSCustomObject]@{ SharedHash = $h UserCount = $users.Count Users = $users -join ', ' } Write-Host '----------------------------------------------' Write-Host "Shared Hash: $h" Write-Host "User Count : $($users.Count)" Write-Host "Users : $($users -join ', ')" Write-Host '----------------------------------------------`n' } Write-Host "[+] Saving shared‑password report to $outputReport" $reportResults | Export-Csv -Path $outputReport -NoTypeInformation Write-Host "[Summary] Shared groups: $($reportResults.Count); Total archives: $($allArchives.Count)"