From db9a966a8b81465309e67821cd06d474dce13220 Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Thu, 17 Sep 2026 21:50:25 +0900 Subject: [PATCH 1/9] Add 'configure-sacl': targeted File System/Registry audit SACLs for detection 'configure' already enables Object Access subcategories such as File Share, SAM and Certification Services, but File System (4663), Registry (4657) and Handle Manipulation (4656) auditing produce no events without SACLs on the audited objects - and enabling them globally floods the log. This adds targeted SACLs on only the autostart/persistence registry keys (ASEPs) and sensitive files that the Hayabusa/Sigma Security-channel rules actually watch, so those rules can fire without global object auditing. - config/audit_sacl_targets.json: curated, commented list of 30 registry keys (Run/RunOnce, Winlogon, IFEO, AppInit, Explorer shell extensions, Active Setup, Command Processor AutoRun, Session Manager, LSA packages, Winsock LSP, protocol handlers, logon scripts, Defender exclusions, service create/delete, ...) and 7 files (NTDS dir, SAM/SECURITY/SYSTEM hives, lsass.exe, ntdsutil, vssadmin), each tagged with the ATT&CK technique / rule class it serves. - WELA.ps1: new 'configure-sacl' command. Enables the File System / Registry / Handle Manipulation subcategories (by GUID) and applies the SACLs from the config (principal Everyone, Success+Failure, ContainerInherit on registry keys), idempotently, honoring -Auto / -WhatIf / -Confirm. Enables SeSecurityPrivilege first; skips objects absent on the host. Per-user objects (HKCU / profile AppData) and live LSASS memory/handle access are intentionally out of scope (need a per-user mechanism / Sysmon EID 10) and are documented as such. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MVUmXZBkr5FnZ2hwFkDhx7 --- README.md | 2 +- WELA.ps1 | 127 +++++++++++++++++++++++++++++++++ config/audit_sacl_targets.json | 44 ++++++++++++ 3 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 config/audit_sacl_targets.json diff --git a/README.md b/README.md index 866a704e..2547b290 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ All documentation now lives on a dedicated, searchable, multi-language site: | Section | | | --- | --- | | 🚀 [Getting Started](https://yamato-security.github.io/WELA/getting-started/) | Prerequisites, downloads and running WELA | -| ⌨️ [Command Reference](https://yamato-security.github.io/WELA/commands/) | `audit-settings`, `audit-filesize`, `configure`, `update-rules` | +| ⌨️ [Command Reference](https://yamato-security.github.io/WELA/commands/) | `audit-settings`, `audit-filesize`, `configure`, `configure-sacl`, `update-rules` | | ✨ [Features](https://yamato-security.github.io/WELA/overview/features/) | What WELA can do | | 📦 [Resources](https://yamato-security.github.io/WELA/resources/companion-projects/) | Companion projects, changelog, contributing | diff --git a/WELA.ps1 b/WELA.ps1 index 1690b119..2abb4c85 100644 --- a/WELA.ps1 +++ b/WELA.ps1 @@ -16,6 +16,7 @@ $BaselineConfigPath = Join-Path $ScriptRoot "config/baselines.json" $SecurityRulesPath = Join-Path $ScriptRoot "config/security_rules.json" $EidMappingPath = Join-Path $ScriptRoot "config/eid_subcategory_mapping.csv" $AuditpolTxtPath = Join-Path $ScriptRoot "auditpol.txt" +$SaclTargetsPath = Join-Path $ScriptRoot "config/audit_sacl_targets.json" # 64bit の PowerShell と GPO が読むのは Wow6432Node の無いパス。32bit 用に両方を扱う。 $PowerShellPolicyRoots = @( @@ -1286,6 +1287,111 @@ $logo = @" by Yamato Security "@ +function Enable-WelaPrivilege { + # Enable SeSecurityPrivilege (required to read/write SACLs) + backup/restore, in the current token. + param([string[]] $Privileges = @("SeSecurityPrivilege","SeBackupPrivilege","SeRestorePrivilege")) + if (-not ("WELA.PrivHelper" -as [type])) { + Add-Type -Namespace WELA -Name PrivHelper -MemberDefinition @" +[DllImport("advapi32.dll", SetLastError=true)] public static extern bool OpenProcessToken(IntPtr h, uint acc, out IntPtr tok); +[DllImport("advapi32.dll", SetLastError=true)] public static extern bool LookupPrivilegeValue(string host, string name, out long luid); +[DllImport("advapi32.dll", SetLastError=true)] public static extern bool AdjustTokenPrivileges(IntPtr tok, bool dis, ref TOKEN_PRIVILEGES np, uint len, IntPtr prev, IntPtr rl); +[DllImport("kernel32.dll")] public static extern IntPtr GetCurrentProcess(); +[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] +public struct TOKEN_PRIVILEGES { public uint Count; public long Luid; public uint Attr; } +public static bool Enable(string priv) { + IntPtr tok; if(!OpenProcessToken(GetCurrentProcess(), 0x20, out tok)) return false; + long luid; if(!LookupPrivilegeValue(null, priv, out luid)) return false; + TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES(); tp.Count=1; tp.Luid=luid; tp.Attr=0x2; + return AdjustTokenPrivileges(tok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); +} +"@ + } + foreach ($p in $Privileges) { [void][WELA.PrivHelper]::Enable($p) } +} + +function Set-AuditSacl { + # Apply TARGETED audit SACLs (from config/audit_sacl_targets.json) so that File System (4663), + # Registry (4657) and Handle Manipulation (4656) auditing fires only on the specific ASEP keys + # and sensitive files the detection rules watch - never globally. + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param([switch] $Auto) + + if (-not (TestWindows)) { + Write-Host "[ERROR] 'configure-sacl' changes Windows settings and can only run on Windows." -ForegroundColor Red; return + } + if (-not (TestAdministrator)) { Write-Error "This command requires Administrator privileges"; return } + if (-not (Test-Path $script:SaclTargetsPath)) { + Write-Host "[ERROR] Missing config: $script:SaclTargetsPath (run 'update-rules' or reinstall WELA)." -ForegroundColor Red; return + } + Enable-WelaPrivilege + $targets = Get-Content -Path $script:SaclTargetsPath -Raw | ConvertFrom-Json + + # 1) Enable ONLY the object-access subcategories these SACLs need (by GUID, locale-independent). + # With no SACLs beyond the targeted ones below, these subcategories stay effectively silent. + Write-Host "Enabling Object Access subcategories (File System, Registry, Handle Manipulation)..." + $subs = @( + @{Name="File System"; GUID="0CCE921D-69AE-11D9-BED3-505054503030"}, + @{Name="Registry"; GUID="0CCE921E-69AE-11D9-BED3-505054503030"}, + @{Name="Handle Manipulation"; GUID="0CCE9223-69AE-11D9-BED3-505054503030"} + ) + foreach ($s in $subs) { + if ($Auto -or $PSCmdlet.ShouldProcess($s.Name, "auditpol enable Success+Failure")) { + $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 } + } + } + Write-Host "" + + $everyone = New-Object System.Security.Principal.SecurityIdentifier("S-1-1-0") + $auditFlags = [System.Security.AccessControl.AuditFlags]"Success,Failure" + + # 2) Registry SACLs + Write-Host "Applying targeted REGISTRY audit SACLs..." + foreach ($t in $targets.registry) { + try { + if (-not (Test-Path -LiteralPath $t.path)) { + Write-Host "[SKIPPED] $($t.path) : key not present on this host" -ForegroundColor DarkYellow; continue + } + $rights = [System.Security.AccessControl.RegistryRights]($t.rights -join ",") + $inh = if ($t.inherit) { [System.Security.AccessControl.InheritanceFlags]"ContainerInherit" } else { [System.Security.AccessControl.InheritanceFlags]"None" } + $acl = Get-Acl -LiteralPath $t.path -Audit + $already = $acl.Audit | Where-Object { $_.IdentityReference.Value -eq $everyone.Value -and (($_.RegistryRights -band $rights) -eq $rights) -and ($_.AuditFlags -eq $auditFlags) } + if ($already) { Write-Host "[SKIPPED] $($t.path) : SACL already present ($($t.note))" -ForegroundColor Yellow; continue } + if ($Auto -or $PSCmdlet.ShouldProcess($t.path, "add audit SACL [$($t.rights -join ',')] Everyone Success+Failure")) { + $rule = New-Object System.Security.AccessControl.RegistryAuditRule($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 } + } + Write-Host "" + + # 3) File / directory SACLs + Write-Host "Applying targeted FILE audit SACLs..." + foreach ($t in $targets.files) { + try { + if (-not (Test-Path -LiteralPath $t.path)) { + Write-Host "[SKIPPED] $($t.path) : path not present on this host" -ForegroundColor DarkYellow; continue + } + $isDir = (Get-Item -LiteralPath $t.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 + $already = $acl.Audit | Where-Object { $_.IdentityReference.Value -eq $everyone.Value -and (($_.FileSystemRights -band $rights) -eq $rights) -and ($_.AuditFlags -eq $auditFlags) } + if ($already) { Write-Host "[SKIPPED] $($t.path) : SACL already present ($($t.note))" -ForegroundColor Yellow; continue } + if ($Auto -or $PSCmdlet.ShouldProcess($t.path, "add audit SACL [$($t.rights -join ',')] Everyone Success+Failure")) { + $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 } + } + Write-Host "" + Write-Host "Done. Targeted object-access auditing is enabled without global file/registry auditing." -ForegroundColor Cyan + Write-Host "Note: per-user (HKCU / profile AppData) objects are out of scope for a machine-wide SACL policy." -ForegroundColor DarkCyan +} + $usage = @" Usage: ./WELA.ps1 audit-settings -Baseline YamatoSecurity # Audit current setting and show in stdout, save to csv @@ -1293,6 +1399,8 @@ Usage: ./WELA.ps1 audit-filesize -Baseline YamatoSecurity # Audit current file size and show in stdout, save to csv ./WELA.ps1 configure -Baseline YamatoSecurity # Configure audit settings based on the specified baseline ./WELA.ps1 configure -Baseline YamatoSecurity -Auto # Configure audit settings automatically without prompts + ./WELA.ps1 configure-sacl # Add targeted File System/Registry audit SACLs (ASEP keys + sensitive files) needed by the rules, without global auditing + ./WELA.ps1 configure-sacl -Auto # ...automatically without prompts ./WELA.ps1 update-rules # Update rule config files from https://github.com/Yamato-Security/WELA ./WELA.ps1 version # Show the WELA version ./WELA.ps1 help # Show this help @@ -1363,6 +1471,25 @@ switch ($Cmd.ToLower()) { ConfigureAuditSettings -Auto:$Auto -Debug:$Debug } + "configure-sacl" { + if ($Help){ + Write-Host "Add TARGETED object-access audit SACLs so File System (4663) / Registry (4657) /" + Write-Host "Handle Manipulation (4656) auditing fires only on the specific autostart/persistence" + Write-Host "registry keys and sensitive files the Hayabusa/Sigma rules watch - never globally." + Write-Host "" + Write-Host "Usage: ./WELA.ps1 configure-sacl [-Auto]" + Write-Host "" + Write-Host "Options:" + Write-Host " -Auto Apply without per-object prompts" + Write-Host "" + Write-Host "Targets are defined in config/audit_sacl_targets.json (edit to customize)." + Write-Host "Objects absent on the host are skipped; per-user HKCU/AppData objects are out of scope." + Write-Host "" + return + } + Set-AuditSacl -Auto:$Auto + } + "update-rules" { if ($Help) { Write-Host "Update detection rule configuration files from GitHub repository" diff --git a/config/audit_sacl_targets.json b/config/audit_sacl_targets.json new file mode 100644 index 00000000..f5547509 --- /dev/null +++ b/config/audit_sacl_targets.json @@ -0,0 +1,44 @@ +{ + "description": "Targeted System Access Control Lists (SACLs) that make Object Access File System (4663), Registry (4657) and Handle Manipulation (4656/4658) auditing produce the events the Hayabusa/Sigma Security-channel rules rely on - WITHOUT enabling global file/registry auditing (which would flood). Only the specific autostart/persistence registry keys (ASEPs) and sensitive files referenced by the ruleset are audited. Principal is 'Everyone'. Registry keys use ContainerInherit so subkeys are covered. Objects that are absent on a given host (e.g. Wow6432Node on 32-bit, NTDS on non-DCs) are skipped. Per-user objects (HKCU / user-profile AppData) cannot be set machine-wide here and are intentionally out of scope - apply those via logon script / GPP.", + "registry": [ + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP Run (T1547.001)"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunOnce"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunOnceEx"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunServices"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServicesOnce", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunServicesOnce"}, + {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP Run (WOW64)"}, + {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunOnce (WOW64)"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP Policies Explorer Run"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "Winlogon Shell/Userinit/Notify (T1547.004)"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "IFEO debugger/GlobalFlag (T1546.012)"}, + {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "IFEO (WOW64)"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", "inherit": false, "rights": ["SetValue"], "note": "AppInit_DLLs / Load / Run (T1546.010)"}, + {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Windows", "inherit": false, "rights": ["SetValue"], "note": "AppInit_DLLs (WOW64)"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellServiceObjectDelayLoad", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "SSODL (T1547.005)"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SharedTaskScheduler", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "SharedTaskScheduler"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellIconOverlayIdentifiers", "inherit": true, "rights": ["CreateSubKey","Delete"], "note": "ShellIconOverlayIdentifiers"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects", "inherit": true, "rights": ["CreateSubKey","Delete"], "note": "BHO (T1176)"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Active Setup\\Installed Components", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "Active Setup (T1547.014)"}, + {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Active Setup\\Installed Components", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "Active Setup (WOW64)"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Command Processor", "inherit": false, "rights": ["SetValue"], "note": "cmd AutoRun (T1546.011)"}, + {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Command Processor", "inherit": false, "rights": ["SetValue"], "note": "cmd AutoRun (WOW64)"}, + {"path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager", "inherit": false, "rights": ["SetValue"], "note": "BootExecute/AppCertDlls/SubSystems (T1546.009)"}, + {"path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa", "inherit": false, "rights": ["SetValue"], "note": "LSA Security/Authentication/Notification Packages - SSP (T1547.005/T1556)"}, + {"path": "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters", "inherit": true, "rights": ["SetValue","CreateSubKey"], "note": "Winsock LSP (T1546.015)"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Ctf\\LangBarAddin", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "LangBarAddin"}, + {"path": "HKLM:\\SOFTWARE\\Classes\\Protocols\\Handler", "inherit": true, "rights": ["CreateSubKey","Delete"], "note": "Protocol handler hijack"}, + {"path": "HKLM:\\SOFTWARE\\Classes\\Protocols\\Filter", "inherit": true, "rights": ["CreateSubKey","Delete"], "note": "Protocol filter hijack"}, + {"path": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\System\\Scripts", "inherit": true, "rights": ["SetValue","CreateSubKey"], "note": "Logon/Logoff/Startup scripts (T1037)"}, + {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows Defender\\Exclusions", "inherit": true, "rights": ["SetValue","CreateSubKey"], "note": "Defender exclusion tampering (T1562.001)"}, + {"path": "HKLM:\\SYSTEM\\CurrentControlSet\\Services", "inherit": false, "rights": ["CreateSubKey","Delete"], "note": "Service create/delete (T1543.003). Subkey add/remove only, to limit noise - service value edits are captured by 4697/7045."} + ], + "files": [ + {"path": "C:\\Windows\\NTDS", "inherit": true, "rights": ["ReadData","WriteData","Delete","ChangePermissions","TakeOwnership"], "note": "AD database dir - ntds.dit theft (T1003.003)"}, + {"path": "C:\\Windows\\System32\\config\\SAM", "inherit": false, "rights": ["ReadData","WriteData","Delete","TakeOwnership"], "note": "SAM hive theft (T1003.002)"}, + {"path": "C:\\Windows\\System32\\config\\SECURITY", "inherit": false, "rights": ["ReadData","WriteData","Delete","TakeOwnership"], "note": "SECURITY hive (T1003.004 LSA secrets)"}, + {"path": "C:\\Windows\\System32\\config\\SYSTEM", "inherit": false, "rights": ["ReadData","WriteData","Delete","TakeOwnership"], "note": "SYSTEM hive (boot key for offline SAM)"}, + {"path": "C:\\Windows\\System32\\lsass.exe", "inherit": false, "rights": ["ReadData","WriteData","TakeOwnership"], "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", "inherit": false, "rights": ["ExecuteFile"], "note": "ntdsutil execution (IFM/ntds.dit dump)"}, + {"path": "C:\\Windows\\System32\\vssadmin.exe", "inherit": false, "rights": ["ExecuteFile"], "note": "Shadow copy tooling"} + ] +} From 6aef4c0f7bcc1575f31d98419e28c3de61ccef1b Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Thu, 17 Sep 2026 21:51:14 +0900 Subject: [PATCH 2/9] Add CHANGELOG entry for configure-sacl (#361) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MVUmXZBkr5FnZ2hwFkDhx7 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5f70642..802fd15a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ **Improvements:** +- Added a `configure-sacl` command that sets targeted audit SACLs on the autostart/persistence registry keys and sensitive files the detection rules watch, so File System (4663), Registry (4657) and Handle Manipulation (4656) auditing produce useful events without enabling global object auditing. Targets live in `config/audit_sacl_targets.json`. (#361) (@YamatoSecurity) - Baseline definitions were moved out of `WELA.ps1` into a `config/baselines.json` config file, so adding or changing a baseline is now a JSON-only edit. (#358) (@fukusuket) - The `Microsoft-Windows-DFSN-Server/Admin` channel is now checked by `audit-settings` and `audit-filesize`. (#358) (@fukusuket) From 31aeeda768b76c12263b2e0df251af9167f144de Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:03:24 +0900 Subject: [PATCH 3/9] configure-sacl: cover per-user objects across all profiles + Default Enumerate every user profile from ProfileList (plus C:\Users\Default so future users inherit the SACL) and apply per-user SACLs: - user_files: file SACL under each profile dir (Startup folder, Signal AppData). - user_registry: registry SACL on each user hive - loaded hives via HKEY_USERS\ directly, offline/Default hives by reg-load/unload of NTUSER.DAT (HKCU Run/RunOnce, User Shell Folders, StartupApproved, Load/Run, Command Processor AutoRun, Control Panel\Desktop screensaver, Environment logon script, LangBarAddin, Outlook Addins). Handles are released ([gc]) before reg unload; objects/hives absent on the host are skipped. Not covered: folder-redirected AppData on network shares, mandatory profiles. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MVUmXZBkr5FnZ2hwFkDhx7 --- WELA.ps1 | 100 ++++++++++++++++++++++++++++++++- config/audit_sacl_targets.json | 19 ++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/WELA.ps1 b/WELA.ps1 index 2abb4c85..82d1634b 100644 --- a/WELA.ps1 +++ b/WELA.ps1 @@ -1388,8 +1388,106 @@ function Set-AuditSacl { } catch { Write-Host "[ERROR] $($t.path) : $_" -ForegroundColor Red } } Write-Host "" + + # 4) Per-user objects: enumerate every profile (+ Default, for future users) and apply SACLs. + $hasUserTargets = ($targets.PSObject.Properties.Name -contains 'user_files' -and $targets.user_files) -or ` + ($targets.PSObject.Properties.Name -contains 'user_registry' -and $targets.user_registry) + if ($hasUserTargets) { + Write-Host "Enumerating user profiles for per-user SACLs..." + $profiles = Get-WelaUserProfiles + Write-Host "Found $($profiles.Count) profile(s) (incl. Default template)." + Write-Host "" + + # 4a) Per-user FILE SACLs (each existing profile + Default) + if ($targets.user_files) { + foreach ($prof in $profiles) { + foreach ($t in $targets.user_files) { + $path = Join-Path $prof.Path $t.relpath + try { + if (-not (Test-Path -LiteralPath $path)) { continue } # not installed for this user + $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 $path -Audit + $already = $acl.Audit | Where-Object { $_.IdentityReference.Value -eq $everyone.Value -and (($_.FileSystemRights -band $rights) -eq $rights) -and ($_.AuditFlags -eq $auditFlags) } + if ($already) { continue } + if ($Auto -or $PSCmdlet.ShouldProcess("$path [$($prof.Sid)]", "add audit SACL")) { + $rule = New-Object System.Security.AccessControl.FileSystemAuditRule($everyone, $rights, $inh, "None", $auditFlags) + $acl.AddAuditRule($rule); Set-Acl -LiteralPath $path -AclObject $acl + Write-Host "[OK] $path ($($t.note))" -ForegroundColor Green + } + } catch { Write-Host "[ERROR] $path : $_" -ForegroundColor Red } + } + } + } + + # 4b) Per-user REGISTRY SACLs. Loaded hives -> HKEY_USERS\ directly; + # offline / Default hives -> reg load NTUSER.DAT, apply, reg unload. + if ($targets.user_registry) { + foreach ($prof in $profiles) { + $loadedHere = $false + if ($prof.Loaded) { + $base = "Microsoft.PowerShell.Core\Registry::HKEY_USERS\$($prof.Sid)" + } else { + $hive = Join-Path $prof.Path "NTUSER.DAT" + if (-not (Test-Path -LiteralPath $hive)) { continue } + $mount = "WELA_$($prof.Sid)" + $out = reg load "HKU\$mount" "$hive" 2>&1 + if ($LASTEXITCODE -ne 0) { Write-Host "[SKIPPED] hive $($prof.Sid) : cannot load ($out)" -ForegroundColor DarkYellow; continue } + $loadedHere = $true + $base = "Microsoft.PowerShell.Core\Registry::HKEY_USERS\$mount" + } + try { + foreach ($t in $targets.user_registry) { + $key = "$base\$($t.key)" + try { + if (-not (Test-Path -LiteralPath $key)) { continue } + $rights = [System.Security.AccessControl.RegistryRights]($t.rights -join ",") + $inh = if ($t.inherit) { [System.Security.AccessControl.InheritanceFlags]"ContainerInherit" } else { [System.Security.AccessControl.InheritanceFlags]"None" } + $acl = Get-Acl -LiteralPath $key -Audit + $already = $acl.Audit | Where-Object { $_.IdentityReference.Value -eq $everyone.Value -and (($_.RegistryRights -band $rights) -eq $rights) -and ($_.AuditFlags -eq $auditFlags) } + if ($already) { continue } + if ($Auto -or $PSCmdlet.ShouldProcess("$($t.key) [$($prof.Sid)]", "add audit SACL")) { + $rule = New-Object System.Security.AccessControl.RegistryAuditRule($everyone, $rights, $inh, "None", $auditFlags) + $acl.AddAuditRule($rule); Set-Acl -LiteralPath $key -AclObject $acl + Write-Host "[OK] HKU\$($prof.Sid)\$($t.key) ($($t.note))" -ForegroundColor Green + } + } catch { Write-Host "[ERROR] $($t.key) [$($prof.Sid)] : $_" -ForegroundColor Red } + } + } + finally { + if ($loadedHere) { + # release handles before unloading, or 'reg unload' fails + [gc]::Collect(); [gc]::WaitForPendingFinalizers() + reg unload "HKU\$mount" 2>&1 | Out-Null + } + } + } + } + Write-Host "" + } + Write-Host "Done. Targeted object-access auditing is enabled without global file/registry auditing." -ForegroundColor Cyan - Write-Host "Note: per-user (HKCU / profile AppData) objects are out of scope for a machine-wide SACL policy." -ForegroundColor DarkCyan + 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 +} + +function Get-WelaUserProfiles { + # Enumerate real user profiles from ProfileList (SID + path + whether the hive is loaded), + # plus the Default profile template so SACLs propagate to future users. + $result = @() + $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 } # skip system/service SIDs (S-1-5-18/19/20) + $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" + $result += [pscustomobject]@{ Sid = $sid; Path = $p; Loaded = $loaded } + } + $def = Join-Path $env:SystemDrive "Users\Default" + if (Test-Path -LiteralPath $def) { $result += [pscustomobject]@{ Sid = "DEFAULT"; Path = $def; Loaded = $false } } + return $result } $usage = @" diff --git a/config/audit_sacl_targets.json b/config/audit_sacl_targets.json index f5547509..96555a24 100644 --- a/config/audit_sacl_targets.json +++ b/config/audit_sacl_targets.json @@ -1,5 +1,5 @@ { - "description": "Targeted System Access Control Lists (SACLs) that make Object Access File System (4663), Registry (4657) and Handle Manipulation (4656/4658) auditing produce the events the Hayabusa/Sigma Security-channel rules rely on - WITHOUT enabling global file/registry auditing (which would flood). Only the specific autostart/persistence registry keys (ASEPs) and sensitive files referenced by the ruleset are audited. Principal is 'Everyone'. Registry keys use ContainerInherit so subkeys are covered. Objects that are absent on a given host (e.g. Wow6432Node on 32-bit, NTDS on non-DCs) are skipped. Per-user objects (HKCU / user-profile AppData) cannot be set machine-wide here and are intentionally out of scope - apply those via logon script / GPP.", + "description": "Targeted System Access Control Lists (SACLs) that make Object Access File System (4663), Registry (4657) and Handle Manipulation (4656/4658) auditing produce the events the Hayabusa/Sigma Security-channel rules rely on - WITHOUT enabling global file/registry auditing (which would flood). Only the specific autostart/persistence registry keys (ASEPs) and sensitive files referenced by the ruleset are audited. Principal is 'Everyone'. Registry keys use ContainerInherit so subkeys are covered. Objects that are absent on a given host (e.g. Wow6432Node on 32-bit, NTDS on non-DCs) are skipped. Per-user objects are handled via 'user_registry' and 'user_files': WELA enumerates every profile from ProfileList (plus C:\\Users\\Default so future users inherit the SACL), sets file SACLs under each profile, and sets registry SACLs on each user hive (loaded hives directly via HKU:\\, offline/Default hives by reg-load/unload of NTUSER.DAT). 'user_registry' keys are relative to the user hive root; 'user_files' paths are relative to the profile directory. Remaining edge cases (folder-redirected AppData on network shares, mandatory profiles) are not covered.", "registry": [ {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP Run (T1547.001)"}, {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunOnce"}, @@ -32,6 +32,23 @@ {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows Defender\\Exclusions", "inherit": true, "rights": ["SetValue","CreateSubKey"], "note": "Defender exclusion tampering (T1562.001)"}, {"path": "HKLM:\\SYSTEM\\CurrentControlSet\\Services", "inherit": false, "rights": ["CreateSubKey","Delete"], "note": "Service create/delete (T1543.003). Subkey add/remove only, to limit noise - service value edits are captured by 4697/7045."} ], + "user_registry": [ + {"key": "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "HKCU ASEP Run (T1547.001)"}, + {"key": "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "HKCU ASEP RunOnce"}, + {"key": "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "HKCU Policies Explorer Run"}, + {"key": "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", "inherit": false, "rights": ["SetValue"], "note": "Startup folder redirection (T1547.001)"}, + {"key": "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run", "inherit": false, "rights": ["SetValue","Delete"], "note": "StartupApproved (enable/disable autorun)"}, + {"key": "Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", "inherit": false, "rights": ["SetValue"], "note": "HKCU Load/Run (T1546.010)"}, + {"key": "Software\\Microsoft\\Command Processor", "inherit": false, "rights": ["SetValue"], "note": "HKCU cmd AutoRun (T1546.011)"}, + {"key": "Control Panel\\Desktop", "inherit": false, "rights": ["SetValue"], "note": "Scrnsave.exe screensaver hijack (T1546.002)"}, + {"key": "Environment", "inherit": false, "rights": ["SetValue"], "note": "UserInitMprLogonScript logon-script persistence (T1037.001)"}, + {"key": "Software\\Microsoft\\Ctf\\LangBarAddin", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "HKCU LangBarAddin"}, + {"key": "Software\\Microsoft\\Office\\Outlook\\Addins", "inherit": true, "rights": ["CreateSubKey","Delete","SetValue"], "note": "Outlook add-in persistence (T1137.006)"} + ], + "user_files": [ + {"relpath": "AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", "inherit": true, "rights": ["WriteData","CreateFiles","Delete"], "note": "Startup folder persistence (T1547.001)"}, + {"relpath": "AppData\\Roaming\\Signal", "inherit": true, "rights": ["ReadData"], "note": "Signal Desktop sensitive data access (T1005)"} + ], "files": [ {"path": "C:\\Windows\\NTDS", "inherit": true, "rights": ["ReadData","WriteData","Delete","ChangePermissions","TakeOwnership"], "note": "AD database dir - ntds.dit theft (T1003.003)"}, {"path": "C:\\Windows\\System32\\config\\SAM", "inherit": false, "rights": ["ReadData","WriteData","Delete","TakeOwnership"], "note": "SAM hive theft (T1003.002)"}, From 917037a67974e35b2366541e4b7cd081e726079b Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:03:47 +0900 Subject: [PATCH 4/9] CHANGELOG: note per-user coverage for configure-sacl (#361) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MVUmXZBkr5FnZ2hwFkDhx7 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 802fd15a..2093e942 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ **Improvements:** -- Added a `configure-sacl` command that sets targeted audit SACLs on the autostart/persistence registry keys and sensitive files the detection rules watch, so File System (4663), Registry (4657) and Handle Manipulation (4656) auditing produce useful events without enabling global object auditing. Targets live in `config/audit_sacl_targets.json`. (#361) (@YamatoSecurity) +- Added a `configure-sacl` command that sets targeted audit SACLs on the autostart/persistence registry keys and sensitive files the detection rules watch, so File System (4663), Registry (4657) and Handle Manipulation (4656) auditing produce useful events without enabling global object auditing. It covers machine-wide objects plus per-user HKCU keys and profile AppData across all user profiles and the Default profile (so future users inherit the SACL). Targets live in `config/audit_sacl_targets.json`. (#361) (@YamatoSecurity) - Baseline definitions were moved out of `WELA.ps1` into a `config/baselines.json` config file, so adding or changing a baseline is now a JSON-only edit. (#358) (@fukusuket) - The `Microsoft-Windows-DFSN-Server/Admin` channel is now checked by `audit-settings` and `audit-filesize`. (#358) (@fukusuket) From 1308bc003d2b2b4b85e087944a399cff2faddb1a Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:11:03 +0900 Subject: [PATCH 5/9] Address Copilot review: privilege check, idempotency, ASEP provisioning, service inheritance, update-rules, CLI - Enable-WelaPrivilege now validates ERROR_NOT_ALL_ASSIGNED per privilege; Set-AuditSacl aborts if SeSecurityPrivilege cannot be enabled (was silently proceeding). - Idempotency (Test-WelaAuditRulePresent) translates IdentityReference to SID before comparing (Get-Acl returns NTAccount, not S-1-1-0) and also compares InheritanceFlags, so reruns no longer re-add rules and a non-inheriting rule no longer satisfies an inheriting target. - Absent registry ASEP keys (RunOnceEx, Policies\Explorer\Run, ...) are now provisioned (created) before the SACL is applied, so a later attacker write is audited via the inheritable ACE instead of being missed. - Services SACL is now inherited (SetValue,CreateSubKey,Delete) so 4657 on child-service ImagePath/ServiceDLL/Start edits and service deletion are captured (4697/7045 only cover install). - update-rules now downloads config/audit_sacl_targets.json, matching the recovery message. - Dropped the non-functional -WhatIf/-Confirm advertising (the script param block has a custom -Debug that precludes CmdletBinding); configure-sacl now uses a single -Auto-skippable confirmation prompt, consistent with 'configure'. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MVUmXZBkr5FnZ2hwFkDhx7 --- WELA.ps1 | 169 ++++++----- config/audit_sacl_targets.json | 521 +++++++++++++++++++++++++++++---- 2 files changed, 561 insertions(+), 129 deletions(-) diff --git a/WELA.ps1 b/WELA.ps1 index 82d1634b..08452c27 100644 --- a/WELA.ps1 +++ b/WELA.ps1 @@ -780,7 +780,8 @@ function UpdateRules { $baseUrl = "https://raw.githubusercontent.com/Yamato-Security/WELA/main/config" $downloads = @( @{ Url = "$baseUrl/eid_subcategory_mapping.csv"; Path = $script:EidMappingPath }, - @{ Url = "$baseUrl/security_rules.json"; Path = $script:SecurityRulesPath } + @{ Url = "$baseUrl/security_rules.json"; Path = $script:SecurityRulesPath }, + @{ Url = "$baseUrl/audit_sacl_targets.json"; Path = $script:SaclTargetsPath } ) $failed = 0 @@ -1288,7 +1289,9 @@ $logo = @" "@ function Enable-WelaPrivilege { - # Enable SeSecurityPrivilege (required to read/write SACLs) + backup/restore, in the current token. + # Enable SeSecurityPrivilege (required to read/write SACLs) + backup/restore (for reg load/unload). + # Returns @{ PrivName = $true/$false } - AdjustTokenPrivileges returns true even when a privilege is + # NOT held (it sets ERROR_NOT_ALL_ASSIGNED=1300), so the last Win32 error is validated per privilege. param([string[]] $Privileges = @("SeSecurityPrivilege","SeBackupPrivilege","SeRestorePrivilege")) if (-not ("WELA.PrivHelper" -as [type])) { Add-Type -Namespace WELA -Name PrivHelper -MemberDefinition @" @@ -1299,21 +1302,53 @@ function Enable-WelaPrivilege { [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct TOKEN_PRIVILEGES { public uint Count; public long Luid; public uint Attr; } public static bool Enable(string priv) { - IntPtr tok; if(!OpenProcessToken(GetCurrentProcess(), 0x20, out tok)) return false; + IntPtr tok; if(!OpenProcessToken(GetCurrentProcess(), 0x28, out tok)) return false; long luid; if(!LookupPrivilegeValue(null, priv, out luid)) return false; TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES(); tp.Count=1; tp.Luid=luid; tp.Attr=0x2; - return AdjustTokenPrivileges(tok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); + bool ok = AdjustTokenPrivileges(tok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); + return ok && System.Runtime.InteropServices.Marshal.GetLastWin32Error() == 0; } "@ } - foreach ($p in $Privileges) { [void][WELA.PrivHelper]::Enable($p) } + $res = @{} + foreach ($p in $Privileges) { $res[$p] = [WELA.PrivHelper]::Enable($p) } + return $res +} + +function Test-WelaAuditRulePresent { + # Idempotency: is an audit rule for $Sid already present that covers $RightsValue with matching + # inheritance and audit flags? Compares translated SIDs (Get-Acl returns NTAccount by default). + param($AuditRules, [string]$Sid, [int]$RightsValue, [string]$RightsProp, $Inh, $AuditFlags) + foreach ($r in $AuditRules) { + $rsid = try { $r.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value } catch { $r.IdentityReference.Value } + if ($rsid -ne $Sid) { continue } + $rr = [int]($r.$RightsProp) + if ((($rr -band $RightsValue) -eq $RightsValue) -and ($r.InheritanceFlags -eq $Inh) -and (($r.AuditFlags -band $AuditFlags) -eq $AuditFlags)) { return $true } + } + return $false +} + +function Set-RegistryAuditSacl { + # Provision the key if absent (ASEPs are often created only when first used - a missing key must + # still carry an inheritable SACL so a later attacker write is audited), then add the audit ACE. + param([string]$Path, $Rights, $Inh, $Sid, $AuditFlags, [string]$Note) + if (-not (Test-Path -LiteralPath $Path)) { + New-Item -Path $Path -Force | Out-Null + Write-Host "[NEW] provisioned absent key $Path" -ForegroundColor DarkGreen + } + $acl = Get-Acl -LiteralPath $Path -Audit + if (Test-WelaAuditRulePresent $acl.Audit $Sid ([int]$Rights) 'RegistryRights' $Inh $AuditFlags) { + Write-Host "[SKIPPED] $Path : SACL already present ($Note)" -ForegroundColor Yellow; return + } + $rule = New-Object System.Security.AccessControl.RegistryAuditRule((New-Object System.Security.Principal.SecurityIdentifier($Sid)), $Rights, $Inh, "None", $AuditFlags) + $acl.AddAuditRule($rule); Set-Acl -LiteralPath $Path -AclObject $acl + Write-Host "[OK] $Path ($Note)" -ForegroundColor Green } function Set-AuditSacl { - # Apply TARGETED audit SACLs (from config/audit_sacl_targets.json) so that File System (4663), - # Registry (4657) and Handle Manipulation (4656) auditing fires only on the specific ASEP keys - # and sensitive files the detection rules watch - never globally. - [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + # Apply TARGETED audit SACLs (config/audit_sacl_targets.json) so File System (4663) / Registry (4657) + # / Handle Manipulation (4656) auditing fires only on the specific ASEP keys and sensitive files the + # detection rules watch - never globally. -Auto skips the confirmation prompt. param([switch] $Auto) if (-not (TestWindows)) { @@ -1321,13 +1356,28 @@ function Set-AuditSacl { } if (-not (TestAdministrator)) { Write-Error "This command requires Administrator privileges"; return } if (-not (Test-Path $script:SaclTargetsPath)) { - Write-Host "[ERROR] Missing config: $script:SaclTargetsPath (run 'update-rules' or reinstall WELA)." -ForegroundColor Red; return + Write-Host "[ERROR] Missing config: $script:SaclTargetsPath" -ForegroundColor Red + Write-Host " Run './WELA.ps1 update-rules' to download it, or reinstall WELA." -ForegroundColor Red; return + } + $priv = Enable-WelaPrivilege + if (-not $priv['SeSecurityPrivilege']) { + Write-Host "[ERROR] SeSecurityPrivilege could not be enabled (removed by policy?). Cannot set SACLs - aborting." -ForegroundColor Red; return + } + if (-not $priv['SeBackupPrivilege'] -or -not $priv['SeRestorePrivilege']) { + Write-Host "[WARN] Backup/Restore privilege not fully enabled; offline per-user hives (reg load) may be skipped." -ForegroundColor DarkYellow } - Enable-WelaPrivilege $targets = Get-Content -Path $script:SaclTargetsPath -Raw | ConvertFrom-Json + $regN = @($targets.registry).Count; $fileN = @($targets.files).Count + + if (-not $Auto) { + $resp = Read-Host "This enables File System/Registry/Handle auditing and sets targeted SACLs on $regN registry keys, $fileN files, plus per-user objects across all profiles (and Default). Proceed? (Y/n)" + if ($resp -notin @('','Y','y')) { Write-Host "Aborted." -ForegroundColor Yellow; return } + } + + $everyone = New-Object System.Security.Principal.SecurityIdentifier("S-1-1-0") + $auditFlags = [System.Security.AccessControl.AuditFlags]"Success,Failure" # 1) Enable ONLY the object-access subcategories these SACLs need (by GUID, locale-independent). - # With no SACLs beyond the targeted ones below, these subcategories stay effectively silent. Write-Host "Enabling Object Access subcategories (File System, Registry, Handle Manipulation)..." $subs = @( @{Name="File System"; GUID="0CCE921D-69AE-11D9-BED3-505054503030"}, @@ -1335,97 +1385,69 @@ function Set-AuditSacl { @{Name="Handle Manipulation"; GUID="0CCE9223-69AE-11D9-BED3-505054503030"} ) foreach ($s in $subs) { - if ($Auto -or $PSCmdlet.ShouldProcess($s.Name, "auditpol enable Success+Failure")) { - $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 } - } + $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 } } Write-Host "" - $everyone = New-Object System.Security.Principal.SecurityIdentifier("S-1-1-0") - $auditFlags = [System.Security.AccessControl.AuditFlags]"Success,Failure" - - # 2) Registry SACLs - Write-Host "Applying targeted REGISTRY audit SACLs..." + # 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) { try { - if (-not (Test-Path -LiteralPath $t.path)) { - Write-Host "[SKIPPED] $($t.path) : key not present on this host" -ForegroundColor DarkYellow; continue - } $rights = [System.Security.AccessControl.RegistryRights]($t.rights -join ",") $inh = if ($t.inherit) { [System.Security.AccessControl.InheritanceFlags]"ContainerInherit" } else { [System.Security.AccessControl.InheritanceFlags]"None" } - $acl = Get-Acl -LiteralPath $t.path -Audit - $already = $acl.Audit | Where-Object { $_.IdentityReference.Value -eq $everyone.Value -and (($_.RegistryRights -band $rights) -eq $rights) -and ($_.AuditFlags -eq $auditFlags) } - if ($already) { Write-Host "[SKIPPED] $($t.path) : SACL already present ($($t.note))" -ForegroundColor Yellow; continue } - if ($Auto -or $PSCmdlet.ShouldProcess($t.path, "add audit SACL [$($t.rights -join ',')] Everyone Success+Failure")) { - $rule = New-Object System.Security.AccessControl.RegistryAuditRule($everyone, $rights, $inh, "None", $auditFlags) - $acl.AddAuditRule($rule); Set-Acl -LiteralPath $t.path -AclObject $acl - Write-Host "[OK] $($t.path) ($($t.note))" -ForegroundColor Green - } + Set-RegistryAuditSacl -Path $t.path -Rights $rights -Inh $inh -Sid $everyone.Value -AuditFlags $auditFlags -Note $t.note } catch { Write-Host "[ERROR] $($t.path) : $_" -ForegroundColor Red } } Write-Host "" - # 3) File / directory SACLs - Write-Host "Applying targeted FILE audit SACLs..." + # 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) { try { - if (-not (Test-Path -LiteralPath $t.path)) { - Write-Host "[SKIPPED] $($t.path) : path not present on this host" -ForegroundColor DarkYellow; continue - } + 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 $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 - $already = $acl.Audit | Where-Object { $_.IdentityReference.Value -eq $everyone.Value -and (($_.FileSystemRights -band $rights) -eq $rights) -and ($_.AuditFlags -eq $auditFlags) } - if ($already) { Write-Host "[SKIPPED] $($t.path) : SACL already present ($($t.note))" -ForegroundColor Yellow; continue } - if ($Auto -or $PSCmdlet.ShouldProcess($t.path, "add audit SACL [$($t.rights -join ',')] Everyone Success+Failure")) { - $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 - } + 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 } + $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 } } Write-Host "" - # 4) Per-user objects: enumerate every profile (+ Default, for future users) and apply SACLs. - $hasUserTargets = ($targets.PSObject.Properties.Name -contains 'user_files' -and $targets.user_files) -or ` - ($targets.PSObject.Properties.Name -contains 'user_registry' -and $targets.user_registry) - if ($hasUserTargets) { + # 4) Per-user objects across every profile (+ Default template, so future users inherit the SACL) + $hasUser = (@($targets.user_files).Count -gt 0) -or (@($targets.user_registry).Count -gt 0) + if ($hasUser) { Write-Host "Enumerating user profiles for per-user SACLs..." $profiles = Get-WelaUserProfiles - Write-Host "Found $($profiles.Count) profile(s) (incl. Default template)." - Write-Host "" + Write-Host "Found $($profiles.Count) profile(s) (incl. Default template)."; Write-Host "" - # 4a) Per-user FILE SACLs (each existing profile + Default) - if ($targets.user_files) { + if (@($targets.user_files).Count -gt 0) { foreach ($prof in $profiles) { foreach ($t in $targets.user_files) { $path = Join-Path $prof.Path $t.relpath try { - if (-not (Test-Path -LiteralPath $path)) { continue } # not installed for this user + if (-not (Test-Path -LiteralPath $path)) { 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 $path -Audit - $already = $acl.Audit | Where-Object { $_.IdentityReference.Value -eq $everyone.Value -and (($_.FileSystemRights -band $rights) -eq $rights) -and ($_.AuditFlags -eq $auditFlags) } - if ($already) { continue } - if ($Auto -or $PSCmdlet.ShouldProcess("$path [$($prof.Sid)]", "add audit SACL")) { - $rule = New-Object System.Security.AccessControl.FileSystemAuditRule($everyone, $rights, $inh, "None", $auditFlags) - $acl.AddAuditRule($rule); Set-Acl -LiteralPath $path -AclObject $acl - Write-Host "[OK] $path ($($t.note))" -ForegroundColor Green - } + if (Test-WelaAuditRulePresent $acl.Audit $everyone.Value ([int]$rights) 'FileSystemRights' $inh $auditFlags) { continue } + $rule = New-Object System.Security.AccessControl.FileSystemAuditRule($everyone, $rights, $inh, "None", $auditFlags) + $acl.AddAuditRule($rule); Set-Acl -LiteralPath $path -AclObject $acl + Write-Host "[OK] $path ($($t.note))" -ForegroundColor Green } catch { Write-Host "[ERROR] $path : $_" -ForegroundColor Red } } } } - # 4b) Per-user REGISTRY SACLs. Loaded hives -> HKEY_USERS\ directly; - # offline / Default hives -> reg load NTUSER.DAT, apply, reg unload. - if ($targets.user_registry) { + if (@($targets.user_registry).Count -gt 0) { foreach ($prof in $profiles) { - $loadedHere = $false + $loadedHere = $false; $mount = $null if ($prof.Loaded) { $base = "Microsoft.PowerShell.Core\Registry::HKEY_USERS\$($prof.Sid)" } else { @@ -1441,23 +1463,14 @@ function Set-AuditSacl { foreach ($t in $targets.user_registry) { $key = "$base\$($t.key)" try { - if (-not (Test-Path -LiteralPath $key)) { continue } $rights = [System.Security.AccessControl.RegistryRights]($t.rights -join ",") $inh = if ($t.inherit) { [System.Security.AccessControl.InheritanceFlags]"ContainerInherit" } else { [System.Security.AccessControl.InheritanceFlags]"None" } - $acl = Get-Acl -LiteralPath $key -Audit - $already = $acl.Audit | Where-Object { $_.IdentityReference.Value -eq $everyone.Value -and (($_.RegistryRights -band $rights) -eq $rights) -and ($_.AuditFlags -eq $auditFlags) } - if ($already) { continue } - if ($Auto -or $PSCmdlet.ShouldProcess("$($t.key) [$($prof.Sid)]", "add audit SACL")) { - $rule = New-Object System.Security.AccessControl.RegistryAuditRule($everyone, $rights, $inh, "None", $auditFlags) - $acl.AddAuditRule($rule); Set-Acl -LiteralPath $key -AclObject $acl - Write-Host "[OK] HKU\$($prof.Sid)\$($t.key) ($($t.note))" -ForegroundColor Green - } + Set-RegistryAuditSacl -Path $key -Rights $rights -Inh $inh -Sid $everyone.Value -AuditFlags $auditFlags -Note "$($t.note) [$($prof.Sid)]" } catch { Write-Host "[ERROR] $($t.key) [$($prof.Sid)] : $_" -ForegroundColor Red } } } finally { if ($loadedHere) { - # release handles before unloading, or 'reg unload' fails [gc]::Collect(); [gc]::WaitForPendingFinalizers() reg unload "HKU\$mount" 2>&1 | Out-Null } @@ -1479,7 +1492,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 } # skip system/service SIDs (S-1-5-18/19/20) + if ($sid -notmatch '^S-1-5-21-') { continue } $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" @@ -1578,9 +1591,9 @@ switch ($Cmd.ToLower()) { Write-Host "Usage: ./WELA.ps1 configure-sacl [-Auto]" Write-Host "" Write-Host "Options:" - Write-Host " -Auto Apply without per-object prompts" + Write-Host " -Auto Apply without the confirmation prompt" Write-Host "" - Write-Host "Targets are defined in config/audit_sacl_targets.json (edit to customize)." + 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 "" return diff --git a/config/audit_sacl_targets.json b/config/audit_sacl_targets.json index 96555a24..2bf0b894 100644 --- a/config/audit_sacl_targets.json +++ b/config/audit_sacl_targets.json @@ -1,61 +1,480 @@ { "description": "Targeted System Access Control Lists (SACLs) that make Object Access File System (4663), Registry (4657) and Handle Manipulation (4656/4658) auditing produce the events the Hayabusa/Sigma Security-channel rules rely on - WITHOUT enabling global file/registry auditing (which would flood). Only the specific autostart/persistence registry keys (ASEPs) and sensitive files referenced by the ruleset are audited. Principal is 'Everyone'. Registry keys use ContainerInherit so subkeys are covered. Objects that are absent on a given host (e.g. Wow6432Node on 32-bit, NTDS on non-DCs) are skipped. Per-user objects are handled via 'user_registry' and 'user_files': WELA enumerates every profile from ProfileList (plus C:\\Users\\Default so future users inherit the SACL), sets file SACLs under each profile, and sets registry SACLs on each user hive (loaded hives directly via HKU:\\, offline/Default hives by reg-load/unload of NTUSER.DAT). 'user_registry' keys are relative to the user hive root; 'user_files' paths are relative to the profile directory. Remaining edge cases (folder-redirected AppData on network shares, mandatory profiles) are not covered.", "registry": [ - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP Run (T1547.001)"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunOnce"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunOnceEx"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunServices"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServicesOnce", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunServicesOnce"}, - {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP Run (WOW64)"}, - {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP RunOnce (WOW64)"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "ASEP Policies Explorer Run"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "Winlogon Shell/Userinit/Notify (T1547.004)"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "IFEO debugger/GlobalFlag (T1546.012)"}, - {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "IFEO (WOW64)"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", "inherit": false, "rights": ["SetValue"], "note": "AppInit_DLLs / Load / Run (T1546.010)"}, - {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Windows", "inherit": false, "rights": ["SetValue"], "note": "AppInit_DLLs (WOW64)"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellServiceObjectDelayLoad", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "SSODL (T1547.005)"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SharedTaskScheduler", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "SharedTaskScheduler"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellIconOverlayIdentifiers", "inherit": true, "rights": ["CreateSubKey","Delete"], "note": "ShellIconOverlayIdentifiers"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects", "inherit": true, "rights": ["CreateSubKey","Delete"], "note": "BHO (T1176)"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Active Setup\\Installed Components", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "Active Setup (T1547.014)"}, - {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Active Setup\\Installed Components", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "Active Setup (WOW64)"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Command Processor", "inherit": false, "rights": ["SetValue"], "note": "cmd AutoRun (T1546.011)"}, - {"path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Command Processor", "inherit": false, "rights": ["SetValue"], "note": "cmd AutoRun (WOW64)"}, - {"path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager", "inherit": false, "rights": ["SetValue"], "note": "BootExecute/AppCertDlls/SubSystems (T1546.009)"}, - {"path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa", "inherit": false, "rights": ["SetValue"], "note": "LSA Security/Authentication/Notification Packages - SSP (T1547.005/T1556)"}, - {"path": "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters", "inherit": true, "rights": ["SetValue","CreateSubKey"], "note": "Winsock LSP (T1546.015)"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Ctf\\LangBarAddin", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "LangBarAddin"}, - {"path": "HKLM:\\SOFTWARE\\Classes\\Protocols\\Handler", "inherit": true, "rights": ["CreateSubKey","Delete"], "note": "Protocol handler hijack"}, - {"path": "HKLM:\\SOFTWARE\\Classes\\Protocols\\Filter", "inherit": true, "rights": ["CreateSubKey","Delete"], "note": "Protocol filter hijack"}, - {"path": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\System\\Scripts", "inherit": true, "rights": ["SetValue","CreateSubKey"], "note": "Logon/Logoff/Startup scripts (T1037)"}, - {"path": "HKLM:\\SOFTWARE\\Microsoft\\Windows Defender\\Exclusions", "inherit": true, "rights": ["SetValue","CreateSubKey"], "note": "Defender exclusion tampering (T1562.001)"}, - {"path": "HKLM:\\SYSTEM\\CurrentControlSet\\Services", "inherit": false, "rights": ["CreateSubKey","Delete"], "note": "Service create/delete (T1543.003). Subkey add/remove only, to limit noise - service value edits are captured by 4697/7045."} + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "ASEP Run (T1547.001)" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "ASEP RunOnce" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "ASEP RunOnceEx" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "ASEP RunServices" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServicesOnce", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "ASEP RunServicesOnce" + }, + { + "path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "ASEP Run (WOW64)" + }, + { + "path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "ASEP RunOnce (WOW64)" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "ASEP Policies Explorer Run" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "Winlogon Shell/Userinit/Notify (T1547.004)" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "IFEO debugger/GlobalFlag (T1546.012)" + }, + { + "path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "IFEO (WOW64)" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "AppInit_DLLs / Load / Run (T1546.010)" + }, + { + "path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Windows", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "AppInit_DLLs (WOW64)" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellServiceObjectDelayLoad", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "SSODL (T1547.005)" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SharedTaskScheduler", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "SharedTaskScheduler" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellIconOverlayIdentifiers", + "inherit": true, + "rights": [ + "CreateSubKey", + "Delete" + ], + "note": "ShellIconOverlayIdentifiers" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects", + "inherit": true, + "rights": [ + "CreateSubKey", + "Delete" + ], + "note": "BHO (T1176)" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Active Setup\\Installed Components", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "Active Setup (T1547.014)" + }, + { + "path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Active Setup\\Installed Components", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "Active Setup (WOW64)" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Command Processor", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "cmd AutoRun (T1546.011)" + }, + { + "path": "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Command Processor", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "cmd AutoRun (WOW64)" + }, + { + "path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "BootExecute/AppCertDlls/SubSystems (T1546.009)" + }, + { + "path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "LSA Security/Authentication/Notification Packages - SSP (T1547.005/T1556)" + }, + { + "path": "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey" + ], + "note": "Winsock LSP (T1546.015)" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Ctf\\LangBarAddin", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "LangBarAddin" + }, + { + "path": "HKLM:\\SOFTWARE\\Classes\\Protocols\\Handler", + "inherit": true, + "rights": [ + "CreateSubKey", + "Delete" + ], + "note": "Protocol handler hijack" + }, + { + "path": "HKLM:\\SOFTWARE\\Classes\\Protocols\\Filter", + "inherit": true, + "rights": [ + "CreateSubKey", + "Delete" + ], + "note": "Protocol filter hijack" + }, + { + "path": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\System\\Scripts", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey" + ], + "note": "Logon/Logoff/Startup scripts (T1037)" + }, + { + "path": "HKLM:\\SOFTWARE\\Microsoft\\Windows Defender\\Exclusions", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey" + ], + "note": "Defender exclusion tampering (T1562.001)" + }, + { + "path": "HKLM:\\SYSTEM\\CurrentControlSet\\Services", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "Service create/delete + ImagePath/ServiceDLL/Start edits on child keys (T1543.003). Inherited so 4657 on service value changes is captured (4697/7045 cover install only)." + } ], "user_registry": [ - {"key": "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "HKCU ASEP Run (T1547.001)"}, - {"key": "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "HKCU ASEP RunOnce"}, - {"key": "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "HKCU Policies Explorer Run"}, - {"key": "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", "inherit": false, "rights": ["SetValue"], "note": "Startup folder redirection (T1547.001)"}, - {"key": "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run", "inherit": false, "rights": ["SetValue","Delete"], "note": "StartupApproved (enable/disable autorun)"}, - {"key": "Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", "inherit": false, "rights": ["SetValue"], "note": "HKCU Load/Run (T1546.010)"}, - {"key": "Software\\Microsoft\\Command Processor", "inherit": false, "rights": ["SetValue"], "note": "HKCU cmd AutoRun (T1546.011)"}, - {"key": "Control Panel\\Desktop", "inherit": false, "rights": ["SetValue"], "note": "Scrnsave.exe screensaver hijack (T1546.002)"}, - {"key": "Environment", "inherit": false, "rights": ["SetValue"], "note": "UserInitMprLogonScript logon-script persistence (T1037.001)"}, - {"key": "Software\\Microsoft\\Ctf\\LangBarAddin", "inherit": true, "rights": ["SetValue","CreateSubKey","Delete"], "note": "HKCU LangBarAddin"}, - {"key": "Software\\Microsoft\\Office\\Outlook\\Addins", "inherit": true, "rights": ["CreateSubKey","Delete","SetValue"], "note": "Outlook add-in persistence (T1137.006)"} + { + "key": "Software\\Microsoft\\Windows\\CurrentVersion\\Run", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "HKCU ASEP Run (T1547.001)" + }, + { + "key": "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "HKCU ASEP RunOnce" + }, + { + "key": "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "HKCU Policies Explorer Run" + }, + { + "key": "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "Startup folder redirection (T1547.001)" + }, + { + "key": "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run", + "inherit": false, + "rights": [ + "SetValue", + "Delete" + ], + "note": "StartupApproved (enable/disable autorun)" + }, + { + "key": "Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "HKCU Load/Run (T1546.010)" + }, + { + "key": "Software\\Microsoft\\Command Processor", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "HKCU cmd AutoRun (T1546.011)" + }, + { + "key": "Control Panel\\Desktop", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "Scrnsave.exe screensaver hijack (T1546.002)" + }, + { + "key": "Environment", + "inherit": false, + "rights": [ + "SetValue" + ], + "note": "UserInitMprLogonScript logon-script persistence (T1037.001)" + }, + { + "key": "Software\\Microsoft\\Ctf\\LangBarAddin", + "inherit": true, + "rights": [ + "SetValue", + "CreateSubKey", + "Delete" + ], + "note": "HKCU LangBarAddin" + }, + { + "key": "Software\\Microsoft\\Office\\Outlook\\Addins", + "inherit": true, + "rights": [ + "CreateSubKey", + "Delete", + "SetValue" + ], + "note": "Outlook add-in persistence (T1137.006)" + } ], "user_files": [ - {"relpath": "AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", "inherit": true, "rights": ["WriteData","CreateFiles","Delete"], "note": "Startup folder persistence (T1547.001)"}, - {"relpath": "AppData\\Roaming\\Signal", "inherit": true, "rights": ["ReadData"], "note": "Signal Desktop sensitive data access (T1005)"} + { + "relpath": "AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", + "inherit": true, + "rights": [ + "WriteData", + "CreateFiles", + "Delete" + ], + "note": "Startup folder persistence (T1547.001)" + }, + { + "relpath": "AppData\\Roaming\\Signal", + "inherit": true, + "rights": [ + "ReadData" + ], + "note": "Signal Desktop sensitive data access (T1005)" + } ], "files": [ - {"path": "C:\\Windows\\NTDS", "inherit": true, "rights": ["ReadData","WriteData","Delete","ChangePermissions","TakeOwnership"], "note": "AD database dir - ntds.dit theft (T1003.003)"}, - {"path": "C:\\Windows\\System32\\config\\SAM", "inherit": false, "rights": ["ReadData","WriteData","Delete","TakeOwnership"], "note": "SAM hive theft (T1003.002)"}, - {"path": "C:\\Windows\\System32\\config\\SECURITY", "inherit": false, "rights": ["ReadData","WriteData","Delete","TakeOwnership"], "note": "SECURITY hive (T1003.004 LSA secrets)"}, - {"path": "C:\\Windows\\System32\\config\\SYSTEM", "inherit": false, "rights": ["ReadData","WriteData","Delete","TakeOwnership"], "note": "SYSTEM hive (boot key for offline SAM)"}, - {"path": "C:\\Windows\\System32\\lsass.exe", "inherit": false, "rights": ["ReadData","WriteData","TakeOwnership"], "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", "inherit": false, "rights": ["ExecuteFile"], "note": "ntdsutil execution (IFM/ntds.dit dump)"}, - {"path": "C:\\Windows\\System32\\vssadmin.exe", "inherit": false, "rights": ["ExecuteFile"], "note": "Shadow copy tooling"} + { + "path": "C:\\Windows\\NTDS", + "inherit": true, + "rights": [ + "ReadData", + "WriteData", + "Delete", + "ChangePermissions", + "TakeOwnership" + ], + "note": "AD database dir - ntds.dit theft (T1003.003)" + }, + { + "path": "C:\\Windows\\System32\\config\\SAM", + "inherit": false, + "rights": [ + "ReadData", + "WriteData", + "Delete", + "TakeOwnership" + ], + "note": "SAM hive theft (T1003.002)" + }, + { + "path": "C:\\Windows\\System32\\config\\SECURITY", + "inherit": false, + "rights": [ + "ReadData", + "WriteData", + "Delete", + "TakeOwnership" + ], + "note": "SECURITY hive (T1003.004 LSA secrets)" + }, + { + "path": "C:\\Windows\\System32\\config\\SYSTEM", + "inherit": false, + "rights": [ + "ReadData", + "WriteData", + "Delete", + "TakeOwnership" + ], + "note": "SYSTEM hive (boot key for offline SAM)" + }, + { + "path": "C:\\Windows\\System32\\lsass.exe", + "inherit": false, + "rights": [ + "ReadData", + "WriteData", + "TakeOwnership" + ], + "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", + "inherit": false, + "rights": [ + "ExecuteFile" + ], + "note": "ntdsutil execution (IFM/ntds.dit dump)" + }, + { + "path": "C:\\Windows\\System32\\vssadmin.exe", + "inherit": false, + "rights": [ + "ExecuteFile" + ], + "note": "Shadow copy tooling" + } ] -} +} \ No newline at end of file From d9bef96e0d3ed620e1c8e3aabd6a27dbfff777ad Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:24:27 +0900 Subject: [PATCH 6/9] configure-sacl: use .NET RegistryKey API for SACLs (Get-Acl -Audit is unreliable on the registry) Live-tested on Windows Server 2019; three bugs fixed found during testing: - TOKEN_PRIVILEGES had a `long Luid` after a `uint Count`, which is 8-byte aligned on x64 and inserted padding, so AdjustTokenPrivileges failed with ERROR_NOT_ALL_ASSIGNED and the new privilege guard aborted. Split the LUID into LuidLow(uint)+LuidHigh(int) to match the native layout. - Get-Acl/Set-Acl -Audit is unreliable on the registry provider (returns/throws "path does not exist" and null). Registry SACLs now use the .NET RegistryKey API (OpenSubKey with ReadPermissions,ChangePermissions -> GetAccessControl(Audit) -> AddAuditRule -> SetAccessControl), which honors the enabled SeSecurityPrivilege. Absent ASEP keys are provisioned via CreateSubKey then reopened. - Tamper-protected keys (e.g. Defender Exclusions) that deny even admin are reported as SKIPPED, not ERROR. Verified: File System/Registry/Handle subcategories enabled; HKLM Run carries the Everyone Success+Failure ContainerInherit SACL; a test autorun write produced EventID 4657 - with no global registry auditing. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MVUmXZBkr5FnZ2hwFkDhx7 --- WELA.ps1 | 70 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/WELA.ps1 b/WELA.ps1 index 08452c27..c65e626b 100644 --- a/WELA.ps1 +++ b/WELA.ps1 @@ -1299,12 +1299,15 @@ function Enable-WelaPrivilege { [DllImport("advapi32.dll", SetLastError=true)] public static extern bool LookupPrivilegeValue(string host, string name, out long luid); [DllImport("advapi32.dll", SetLastError=true)] public static extern bool AdjustTokenPrivileges(IntPtr tok, bool dis, ref TOKEN_PRIVILEGES np, uint len, IntPtr prev, IntPtr rl); [DllImport("kernel32.dll")] public static extern IntPtr GetCurrentProcess(); +// Layout must match native TOKEN_PRIVILEGES exactly: DWORD Count; LUID(LowPart DWORD, HighPart LONG); DWORD Attr. +// (A single 8-byte 'long' Luid would be 8-byte aligned on x64 and insert padding, misaligning the struct.) [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] -public struct TOKEN_PRIVILEGES { public uint Count; public long Luid; public uint Attr; } +public struct TOKEN_PRIVILEGES { public uint Count; public uint LuidLow; public int LuidHigh; public uint Attr; } public static bool Enable(string priv) { IntPtr tok; if(!OpenProcessToken(GetCurrentProcess(), 0x28, out tok)) return false; long luid; if(!LookupPrivilegeValue(null, priv, out luid)) return false; - TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES(); tp.Count=1; tp.Luid=luid; tp.Attr=0x2; + TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES(); + tp.Count=1; tp.LuidLow=(uint)(luid & 0xFFFFFFFF); tp.LuidHigh=(int)(luid >> 32); tp.Attr=0x2; bool ok = AdjustTokenPrivileges(tok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); return ok && System.Runtime.InteropServices.Marshal.GetLastWin32Error() == 0; } @@ -1329,20 +1332,37 @@ function Test-WelaAuditRulePresent { } function Set-RegistryAuditSacl { - # Provision the key if absent (ASEPs are often created only when first used - a missing key must - # still carry an inheritable SACL so a later attacker write is audited), then add the audit ACE. - param([string]$Path, $Rights, $Inh, $Sid, $AuditFlags, [string]$Note) - if (-not (Test-Path -LiteralPath $Path)) { - New-Item -Path $Path -Force | Out-Null - Write-Host "[NEW] provisioned absent key $Path" -ForegroundColor DarkGreen + # Add an audit SACL to a registry key using the .NET RegistryKey API. Get-Acl/Set-Acl -Audit is + # unreliable on the registry provider, so we open the key with the .NET API (which honors the + # enabled SeSecurityPrivilege for SACL read/write). CreateSubKey is idempotent - it opens the key + # if it exists and provisions it if absent, so an ASEP created later still inherits the audit ACE. + param($BaseKey, [string]$SubPath, $Rights, $Inh, [string]$Sid, $AuditFlags, [string]$Label, [string]$Note) + $rk = $null + try { + # OpenSubKey has the RegistryRights overload (CreateSubKey does not); provision absent keys first. + $rights2 = [System.Security.AccessControl.RegistryRights]"ReadPermissions,ChangePermissions" + $rk = $BaseKey.OpenSubKey($SubPath, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, $rights2) + if (-not $rk) { + $created = $BaseKey.CreateSubKey($SubPath) # provision absent ASEP so a later write inherits the SACL + if ($created) { $created.Close() } + $rk = $BaseKey.OpenSubKey($SubPath, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, $rights2) + } + if (-not $rk) { Write-Host "[ERROR] $Label : cannot open/create key" -ForegroundColor Red; return } + $sec = $rk.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Audit) + $existing = $sec.GetAuditRules($true, $true, [System.Security.Principal.SecurityIdentifier]) | Where-Object { + $_.IdentityReference.Value -eq $Sid -and (([int]$_.RegistryRights -band [int]$Rights) -eq [int]$Rights) -and ($_.InheritanceFlags -eq $Inh) -and (($_.AuditFlags -band $AuditFlags) -eq $AuditFlags) } + if ($existing) { Write-Host "[SKIPPED] $Label : SACL already present ($Note)" -ForegroundColor Yellow; return } + $rule = New-Object System.Security.AccessControl.RegistryAuditRule((New-Object System.Security.Principal.SecurityIdentifier($Sid)), $Rights, $Inh, "None", $AuditFlags) + $sec.AddAuditRule($rule) + $rk.SetAccessControl($sec) + Write-Host "[OK] $Label ($Note)" -ForegroundColor Green } - $acl = Get-Acl -LiteralPath $Path -Audit - if (Test-WelaAuditRulePresent $acl.Audit $Sid ([int]$Rights) 'RegistryRights' $Inh $AuditFlags) { - Write-Host "[SKIPPED] $Path : SACL already present ($Note)" -ForegroundColor Yellow; return + catch { + if ("$_" -match 'access is not allowed|Requested registry access') { + Write-Host "[SKIPPED] $Label : access denied (tamper-protected key, e.g. Defender Exclusions)" -ForegroundColor DarkYellow + } else { Write-Host "[ERROR] $Label : $_" -ForegroundColor Red } } - $rule = New-Object System.Security.AccessControl.RegistryAuditRule((New-Object System.Security.Principal.SecurityIdentifier($Sid)), $Rights, $Inh, "None", $AuditFlags) - $acl.AddAuditRule($rule); Set-Acl -LiteralPath $Path -AclObject $acl - Write-Host "[OK] $Path ($Note)" -ForegroundColor Green + finally { if ($rk) { $rk.Close() } } } function Set-AuditSacl { @@ -1394,11 +1414,10 @@ function Set-AuditSacl { # 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) { - try { - $rights = [System.Security.AccessControl.RegistryRights]($t.rights -join ",") - $inh = if ($t.inherit) { [System.Security.AccessControl.InheritanceFlags]"ContainerInherit" } else { [System.Security.AccessControl.InheritanceFlags]"None" } - Set-RegistryAuditSacl -Path $t.path -Rights $rights -Inh $inh -Sid $everyone.Value -AuditFlags $auditFlags -Note $t.note - } catch { Write-Host "[ERROR] $($t.path) : $_" -ForegroundColor Red } + $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:\\', '' + Set-RegistryAuditSacl -BaseKey ([Microsoft.Win32.Registry]::LocalMachine) -SubPath $sub -Rights $rights -Inh $inh -Sid $everyone.Value -AuditFlags $auditFlags -Label $t.path -Note $t.note } Write-Host "" @@ -1449,7 +1468,7 @@ function Set-AuditSacl { foreach ($prof in $profiles) { $loadedHere = $false; $mount = $null if ($prof.Loaded) { - $base = "Microsoft.PowerShell.Core\Registry::HKEY_USERS\$($prof.Sid)" + $userRoot = $prof.Sid } else { $hive = Join-Path $prof.Path "NTUSER.DAT" if (-not (Test-Path -LiteralPath $hive)) { continue } @@ -1457,16 +1476,13 @@ function Set-AuditSacl { $out = reg load "HKU\$mount" "$hive" 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host "[SKIPPED] hive $($prof.Sid) : cannot load ($out)" -ForegroundColor DarkYellow; continue } $loadedHere = $true - $base = "Microsoft.PowerShell.Core\Registry::HKEY_USERS\$mount" + $userRoot = $mount } try { foreach ($t in $targets.user_registry) { - $key = "$base\$($t.key)" - try { - $rights = [System.Security.AccessControl.RegistryRights]($t.rights -join ",") - $inh = if ($t.inherit) { [System.Security.AccessControl.InheritanceFlags]"ContainerInherit" } else { [System.Security.AccessControl.InheritanceFlags]"None" } - Set-RegistryAuditSacl -Path $key -Rights $rights -Inh $inh -Sid $everyone.Value -AuditFlags $auditFlags -Note "$($t.note) [$($prof.Sid)]" - } catch { Write-Host "[ERROR] $($t.key) [$($prof.Sid)] : $_" -ForegroundColor Red } + $rights = [System.Security.AccessControl.RegistryRights]($t.rights -join ",") + $inh = if ($t.inherit) { [System.Security.AccessControl.InheritanceFlags]"ContainerInherit" } else { [System.Security.AccessControl.InheritanceFlags]"None" } + Set-RegistryAuditSacl -BaseKey ([Microsoft.Win32.Registry]::Users) -SubPath "$userRoot\$($t.key)" -Rights $rights -Inh $inh -Sid $everyone.Value -AuditFlags $auditFlags -Label "HKU\$userRoot\$($t.key)" -Note "$($t.note) [$($prof.Sid)]" } } finally { From 35e5329f74225e1012217e34c88591af27184553 Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:31:54 +0900 Subject: [PATCH 7/9] configure: add Process Termination, Detailed File Share, and DC LDAP 1644 logging Fills the remaining gaps so 'configure' + 'configure-sacl' cover a full detection baseline out of the box (no manual auditpol/registry needed downstream): - Detailed Tracking > Process Termination (4689) - Object Access > Detailed File Share (5145) - Directory Service LDAP query logging (1644) via NTDS "15 Field Engineering"=5, applied only on domain controllers (BloodHound/LDAP recon). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MVUmXZBkr5FnZ2hwFkDhx7 --- WELA.ps1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/WELA.ps1 b/WELA.ps1 index c65e626b..cd9234a4 100644 --- a/WELA.ps1 +++ b/WELA.ps1 @@ -1132,6 +1132,17 @@ function ConfigureAuditSettings { ) Set-RegistryConfig -RegPaths $regPaths -Auto:$Auto + # LDAP query logging (Directory Service EventID 1644) - domain controllers only. + # "15 Field Engineering" = 5 makes expensive / inefficient LDAP searches log as 1644, which surfaces + # BloodHound / SharpHound-style directory reconnaissance. Only applied where the NTDS role is present. + if (Test-Path "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters") { + Write-Host "Configuring LDAP query logging (1644) on this domain controller..." + Write-Host "" + Set-RegistryConfig -RegPaths @( + @{Path = "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics"; Name = "15 Field Engineering"; Value = 5} + ) -Auto:$Auto + } + # 監査ポリシーの設定 Write-Host "Configuring Audit Policies..." Write-Host "" @@ -1146,6 +1157,7 @@ function ConfigureAuditSettings { @{Category = "Account Management"; Name = "User Account Management"; GUID = "0CCE9235-69AE-11D9-BED3-505054503030"}, @{Category = "Detailed Tracking"; Name = "Plug and Play"; GUID = "0cce9248-69ae-11d9-bed3-505054503030"}, @{Category = "Detailed Tracking"; Name = "Process Creation"; GUID = "0CCE922B-69AE-11D9-BED3-505054503030"}, + @{Category = "Detailed Tracking"; Name = "Process Termination"; GUID = "0CCE922C-69AE-11D9-BED3-505054503030"}, @{Category = "Detailed Tracking"; Name = "RPC Events"; GUID = "0CCE922E-69AE-11D9-BED3-505054503030"}, @{Category = "DS Access"; Name = "Directory Service Access"; GUID = "0CCE923B-69AE-11D9-BED3-505054503030"}, @{Category = "DS Access"; Name = "Directory Service Changes"; GUID = "0CCE923C-69AE-11D9-BED3-505054503030"}, @@ -1156,6 +1168,7 @@ function ConfigureAuditSettings { @{Category = "Logon/Logoff"; Name = "Special Logon"; GUID = "0CCE921B-69AE-11D9-BED3-505054503030"}, @{Category = "Object Access"; Name = "Certification Services"; GUID = "0CCE9221-69AE-11D9-BED3-505054503030"}, @{Category = "Object Access"; Name = "File Share"; GUID = "0CCE9224-69AE-11D9-BED3-505054503030"}, + @{Category = "Object Access"; Name = "Detailed File Share"; GUID = "0CCE9244-69AE-11D9-BED3-505054503030"}, @{Category = "Object Access"; Name = "Filtering Platform Connection"; GUID = "0CCE9226-69AE-11D9-BED3-505054503030"}, @{Category = "Object Access"; Name = "Other Object Access Events"; GUID = "0CCE9227-69AE-11D9-BED3-505054503030"}, @{Category = "Object Access"; Name = "Removable Storage"; GUID = "0CCE9245-69AE-11D9-BED3-505054503030"}, From 570b9565d1b93b3a9ea4c308e23d8967183a7730 Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:32:51 +0900 Subject: [PATCH 8/9] CHANGELOG: note configure additions (#361) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MVUmXZBkr5FnZ2hwFkDhx7 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2093e942..5f9b4bfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Improvements:** - Added a `configure-sacl` command that sets targeted audit SACLs on the autostart/persistence registry keys and sensitive files the detection rules watch, so File System (4663), Registry (4657) and Handle Manipulation (4656) auditing produce useful events without enabling global object auditing. It covers machine-wide objects plus per-user HKCU keys and profile AppData across all user profiles and the Default profile (so future users inherit the SACL). Targets live in `config/audit_sacl_targets.json`. (#361) (@YamatoSecurity) +- `configure` now also enables Detailed Tracking > Process Termination (4689), Object Access > Detailed File Share (5145), and (on domain controllers) LDAP query logging (Directory Service 1644 via NTDS `15 Field Engineering`), so a full detection baseline is applied without any manual `auditpol`/registry steps. (#361) (@YamatoSecurity) - Baseline definitions were moved out of `WELA.ps1` into a `config/baselines.json` config file, so adding or changing a baseline is now a JSON-only edit. (#358) (@fukusuket) - The `Microsoft-Windows-DFSN-Server/Admin` channel is now checked by `audit-settings` and `audit-filesize`. (#358) (@fukusuket) From 10c1bcaac745e9114d1fef17b836b6dac6dfd2f0 Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:49:42 +0900 Subject: [PATCH 9/9] 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 Claude-Session: https://claude.ai/code/session_01MVUmXZBkr5FnZ2hwFkDhx7 --- WELA.ps1 | 39 +++++++++++++++++++++++----------- config/audit_sacl_targets.json | 14 ++++++------ 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/WELA.ps1 b/WELA.ps1 index cd9234a4..076979a6 100644 --- a/WELA.ps1 +++ b/WELA.ps1 @@ -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 } diff --git a/config/audit_sacl_targets.json b/config/audit_sacl_targets.json index 2bf0b894..210997f1 100644 --- a/config/audit_sacl_targets.json +++ b/config/audit_sacl_targets.json @@ -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"