Address Copilot re-review: %SystemRoot%, Entra SIDs, WOW64 gate, reg-unload check, subcategory-failure, help

- Machine file targets now use %SystemRoot% and are expanded at runtime, so a non-C: system
  drive no longer skips every file target.
- Get-WelaUserProfiles now also matches Entra/Azure AD user SIDs (S-1-12-1-*), not only S-1-5-21-*.
- WOW64 (Wow6432Node) registry targets are skipped/not provisioned on 32-bit Windows.
- reg unload is now checked (retry once, then error) so a failed unload no longer leaves the
  user's NTUSER.DAT mounted under the temp alias while reporting success.
- A failed auditpol subcategory is tracked; the final message warns (instead of claiming success)
  that SACLs for that class will not produce events.
- configure-sacl help text updated: per-user HKCU/AppData ARE covered and absent ASEP keys are provisioned.

Registry SACLs continue to use the .NET RegistryKey API (GetAccessControl/SetAccessControl with
SeSecurityPrivilege enabled), which was verified live to read/write the SACL and emit 4657.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MVUmXZBkr5FnZ2hwFkDhx7
This commit is contained in:
Shirofune-SecurityandClaude Opus 4.8 committed 2026-09-17 22:49:42 +09:00
1 parent 570b9565d1
commit 10c1bcaac7
2 files changed
+34 -19

No files matched your search

+27 -12
View File
@@ -1417,16 +1417,18 @@ function Set-AuditSacl {
@{Name="Registry"; GUID="0CCE921E-69AE-11D9-BED3-505054503030"},
@{Name="Handle Manipulation"; GUID="0CCE9223-69AE-11D9-BED3-505054503030"}
)
$subFailed = $false
foreach ($s in $subs) {
$p = Start-Process -FilePath "auditpol.exe" -ArgumentList "/set /subcategory:{$($s.GUID)} /success:enable /failure:enable" -Wait -PassThru -NoNewWindow -RedirectStandardOutput "NUL"
if ($p.ExitCode -eq 0) { Write-Host "[OK] subcategory: $($s.Name)" -ForegroundColor Green }
else { Write-Host "[ERROR] subcategory: $($s.Name) (ExitCode $($p.ExitCode))" -ForegroundColor Red }
else { $subFailed = $true; Write-Host "[ERROR] subcategory: $($s.Name) (ExitCode $($p.ExitCode)) -- its SACLs will NOT produce events" -ForegroundColor Red }
}
Write-Host ""
# 2) Machine registry SACLs (absent keys are provisioned so future writes are audited)
Write-Host "Applying targeted machine REGISTRY audit SACLs..."
foreach ($t in $targets.registry) {
if ($t.path -match 'Wow6432Node' -and -not [System.Environment]::Is64BitOperatingSystem) { continue } # WOW64 view absent on 32-bit
$rights = [System.Security.AccessControl.RegistryRights]($t.rights -join ",")
$inh = if ($t.inherit) { [System.Security.AccessControl.InheritanceFlags]"ContainerInherit" } else { [System.Security.AccessControl.InheritanceFlags]"None" }
$sub = $t.path -replace '^HKLM:\\', ''
@@ -1437,17 +1439,18 @@ function Set-AuditSacl {
# 3) Machine file / directory SACLs (absent sensitive files are skipped, never created)
Write-Host "Applying targeted machine FILE audit SACLs..."
foreach ($t in $targets.files) {
$path = [System.Environment]::ExpandEnvironmentVariables($t.path) # e.g. %SystemRoot% -> the real system drive
try {
if (-not (Test-Path -LiteralPath $t.path)) { Write-Host "[SKIPPED] $($t.path) : not present on this host" -ForegroundColor DarkYellow; continue }
$isDir = (Get-Item -LiteralPath $t.path -Force).PSIsContainer
if (-not (Test-Path -LiteralPath $path)) { Write-Host "[SKIPPED] $path : not present on this host" -ForegroundColor DarkYellow; continue }
$isDir = (Get-Item -LiteralPath $path -Force).PSIsContainer
$rights = [System.Security.AccessControl.FileSystemRights]($t.rights -join ",")
$inh = if ($isDir -and $t.inherit) { [System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit" } else { [System.Security.AccessControl.InheritanceFlags]"None" }
$acl = Get-Acl -LiteralPath $t.path -Audit
if (Test-WelaAuditRulePresent $acl.Audit $everyone.Value ([int]$rights) 'FileSystemRights' $inh $auditFlags) { Write-Host "[SKIPPED] $($t.path) : SACL already present ($($t.note))" -ForegroundColor Yellow; continue }
$acl = Get-Acl -LiteralPath $path -Audit
if (Test-WelaAuditRulePresent $acl.Audit $everyone.Value ([int]$rights) 'FileSystemRights' $inh $auditFlags) { Write-Host "[SKIPPED] $path : SACL already present ($($t.note))" -ForegroundColor Yellow; continue }
$rule = New-Object System.Security.AccessControl.FileSystemAuditRule($everyone, $rights, $inh, "None", $auditFlags)
$acl.AddAuditRule($rule); Set-Acl -LiteralPath $t.path -AclObject $acl
Write-Host "[OK] $($t.path) ($($t.note))" -ForegroundColor Green
} catch { Write-Host "[ERROR] $($t.path) : $_" -ForegroundColor Red }
$acl.AddAuditRule($rule); Set-Acl -LiteralPath $path -AclObject $acl
Write-Host "[OK] $path ($($t.note))" -ForegroundColor Green
} catch { Write-Host "[ERROR] $path : $_" -ForegroundColor Red }
}
Write-Host ""
@@ -1500,8 +1503,14 @@ function Set-AuditSacl {
}
finally {
if ($loadedHere) {
# release .NET handles before unloading, or 'reg unload' fails and the hive stays mounted
[gc]::Collect(); [gc]::WaitForPendingFinalizers()
reg unload "HKU\$mount" 2>&1 | Out-Null
$u = reg unload "HKU\$mount" 2>&1
if ($LASTEXITCODE -ne 0) {
Start-Sleep -Milliseconds 500; [gc]::Collect(); [gc]::WaitForPendingFinalizers()
$u = reg unload "HKU\$mount" 2>&1
if ($LASTEXITCODE -ne 0) { Write-Host "[ERROR] could not unload hive HKU\$mount (it remains mounted!): $u" -ForegroundColor Red }
}
}
}
}
@@ -1509,7 +1518,11 @@ function Set-AuditSacl {
Write-Host ""
}
Write-Host "Done. Targeted object-access auditing is enabled without global file/registry auditing." -ForegroundColor Cyan
if ($subFailed) {
Write-Host "WARNING: one or more Object Access subcategories failed to enable -- SACLs on the affected class will NOT produce events. Fix the auditpol error above and re-run." -ForegroundColor Red
} else {
Write-Host "Done. Targeted object-access auditing is enabled without global file/registry auditing." -ForegroundColor Cyan
}
Write-Host "Per-user objects were applied to existing profiles and the Default profile (future users)." -ForegroundColor DarkCyan
Write-Host "Not covered: folder-redirected AppData on network shares, and mandatory profiles." -ForegroundColor DarkCyan
}
@@ -1521,7 +1534,7 @@ function Get-WelaUserProfiles {
$pl = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
foreach ($k in (Get-ChildItem -LiteralPath $pl -ErrorAction SilentlyContinue)) {
$sid = $k.PSChildName
if ($sid -notmatch '^S-1-5-21-') { continue }
if ($sid -notmatch '^S-1-(5-21|12-1)-') { continue } # local/domain (S-1-5-21) + Entra/Azure AD (S-1-12-1) users; skip system SIDs
$p = (Get-ItemProperty -LiteralPath $k.PSPath -Name ProfileImagePath -ErrorAction SilentlyContinue).ProfileImagePath
if (-not $p -or -not (Test-Path -LiteralPath $p)) { continue }
$loaded = Test-Path -LiteralPath "Microsoft.PowerShell.Core\Registry::HKEY_USERS\$sid"
@@ -1623,7 +1636,9 @@ switch ($Cmd.ToLower()) {
Write-Host " -Auto Apply without the confirmation prompt"
Write-Host ""
Write-Host "Targets are defined in config/audit_sacl_targets.json (edit to customize; 'update-rules' refreshes it)."
Write-Host "Objects absent on the host are skipped; per-user HKCU/AppData objects are out of scope."
Write-Host "Per-user HKCU keys and profile AppData ARE covered: applied across every user profile and the"
Write-Host "Default profile (so future users inherit). Absent registry ASEP keys are provisioned; absent files"
Write-Host "are skipped. Not covered: folder-redirected AppData on network shares, and mandatory profiles."
Write-Host ""
return
}
+7 -7
View File
@@ -406,7 +406,7 @@
],
"files": [
{
"path": "C:\\Windows\\NTDS",
"path": "%SystemRoot%\\NTDS",
"inherit": true,
"rights": [
"ReadData",
@@ -418,7 +418,7 @@
"note": "AD database dir - ntds.dit theft (T1003.003)"
},
{
"path": "C:\\Windows\\System32\\config\\SAM",
"path": "%SystemRoot%\\System32\\config\\SAM",
"inherit": false,
"rights": [
"ReadData",
@@ -429,7 +429,7 @@
"note": "SAM hive theft (T1003.002)"
},
{
"path": "C:\\Windows\\System32\\config\\SECURITY",
"path": "%SystemRoot%\\System32\\config\\SECURITY",
"inherit": false,
"rights": [
"ReadData",
@@ -440,7 +440,7 @@
"note": "SECURITY hive (T1003.004 LSA secrets)"
},
{
"path": "C:\\Windows\\System32\\config\\SYSTEM",
"path": "%SystemRoot%\\System32\\config\\SYSTEM",
"inherit": false,
"rights": [
"ReadData",
@@ -451,7 +451,7 @@
"note": "SYSTEM hive (boot key for offline SAM)"
},
{
"path": "C:\\Windows\\System32\\lsass.exe",
"path": "%SystemRoot%\\System32\\lsass.exe",
"inherit": false,
"rights": [
"ReadData",
@@ -461,7 +461,7 @@
"note": "LSASS binary read/replace. NOTE: live LSASS memory/handle access (credential dumping) is better detected via Sysmon EID 10, not a file SACL."
},
{
"path": "C:\\Windows\\System32\\ntdsutil.exe",
"path": "%SystemRoot%\\System32\\ntdsutil.exe",
"inherit": false,
"rights": [
"ExecuteFile"
@@ -469,7 +469,7 @@
"note": "ntdsutil execution (IFM/ntds.dit dump)"
},
{
"path": "C:\\Windows\\System32\\vssadmin.exe",
"path": "%SystemRoot%\\System32\\vssadmin.exe",
"inherit": false,
"rights": [
"ExecuteFile"