mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-24 15:08:08 +02:00
Preserve configured user-file suffixes in SACL plans
This commit is contained in:
1 parent
1acfec66a3
commit
521fe3b826
7 files changed
+60
-10
No files matched your search
@@ -52,7 +52,7 @@
|
||||
|
||||
**新機能:**
|
||||
|
||||
- プロファイルの plan/audit/configure に対象を限定した SACL の読み取り専用計画を追加しました。オブジェクト監査ポリシー、ユーザーハイブ・フォルダーリダイレクトの未確認箇所、WEF Run/RunOnce の監査エントリを表示し、`-SaclMode Skip` による省略も明示します。SACL の書き込みや未検証の検知率向上は行いません。 (#398) (@Shirofune-Security)
|
||||
- プロファイルの plan/audit/configure に対象を限定した SACL の読み取り専用計画を追加しました。オブジェクト監査ポリシー、ユーザーハイブ・フォルダーリダイレクトの未確認箇所、WEF Run/RunOnce の監査エントリを表示し、`-SaclMode Skip` による省略も明示します。ユーザーファイルの対象は、そのユーザーの AppData または Startup 既知フォルダー配下の相対パスを保持し、未対応・曖昧なパスは未解決として扱います。SACL の書き込みや未検証の検知率向上は行いません。 (#398) (@Shirofune-Security)
|
||||
|
||||
|
||||
- MITRE ATT&CK Navigatorヒートマップに対応した。 (#11) (@fukusuket)
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@
|
||||
|
||||
**New Features:**
|
||||
|
||||
- Profile plan/audit/configure now include read-only targeted SACL prerequisites with object policy masks, per-user hive and redirected-folder gaps, exact WEF Run/RunOnce audit entries, and an explicit `-SaclMode Skip`. No SACL writes or unverified detection uplift are implied. (#398) (@Shirofune-Security)
|
||||
- Profile plan/audit/configure now include read-only targeted SACL prerequisites with object policy masks, per-user hive and redirected-folder gaps, exact WEF Run/RunOnce audit entries, and an explicit `-SaclMode Skip`. User-file targets retain their configured suffix under the user's AppData or Startup known folder; unsupported or ambiguous paths remain unresolved. No SACL writes or unverified detection uplift are implied. (#398) (@Shirofune-Security)
|
||||
|
||||
|
||||
- Support for MITRE ATT&CK Navigator heatmaps. (#11) (@fukusuket)
|
||||
|
||||
@@ -17,6 +17,8 @@ No SACL is written by a profile command. Audit policy configuration retains its
|
||||
|
||||
On a matching local Windows role/build, the plan inventories ProfileList, Default and loaded HKU hives. Unloaded hives remain unresolved; it never silently substitutes the operator's user environment for another user's paths. Loaded-user Startup/AppData locations come from that user's unexpanded `User Shell Folders` values. Redirected, remote, missing, inaccessible and unresolved paths are explicit; UNC destinations are not contacted. Mapped network drives and reparse points in any path component are flagged before descendant or SACL inspection. These are point-in-time observations, not an atomic guard against concurrent path replacement. Enumeration failures, per-profile path errors and unmatched loaded hives make the inventory incomplete and remain visible as diagnostics. ProfileList paths expand only known machine variables; operator user variables are never substituted. An offline plan (or plan for a different role/build) inspects no host targets. `Skip` performs no user/target inspection.
|
||||
|
||||
Configured user-file paths beneath `AppData\Roaming` retain their complete relative suffix under the user's AppData known folder. The exact `AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup` root and its descendants use the separate Startup known folder, including its redirection; an unrelated directory named `Startup` does not. Unsupported roots, dot segments, unresolved variables and ambiguous path components remain unresolved before any known-folder read. This resolution adds no targets and does not enumerate other application folders.
|
||||
|
||||
`Exists` and `SaclReadState=Readable` mean only that the object and its SACL could be read. They do **not** prove the necessary audit ACE is present, inheritance reaches every descendant, mandatory/temporary profiles are covered, or a Security event was generated. All rows remain `GenerationReadiness=Conditional`, with zero usable-rule credit. A failure to read SACLs is reported separately from a missing path; elevated privileges may be necessary for those reads.
|
||||
|
||||
## Source-specific distinctions
|
||||
|
||||
@@ -58,7 +58,22 @@ function Resolve-WelaSaclUserFile {
|
||||
if (-not $User.HiveLoaded) { return [pscustomobject]@{ Path = $null; State = 'UnloadedHive'; Diagnostic = 'Known-folder redirection cannot be read without loading the user hive; no hive was loaded.' } }
|
||||
if (-not $User.ProfilePath) { return [pscustomobject]@{ Path = $null; State = 'UnresolvedUserPath'; Diagnostic = 'Profile path is unavailable.' } }
|
||||
try {
|
||||
$folder = if ($RelativePath -like '*\Startup') { 'Startup' } else { 'AppData' }
|
||||
# Match complete known-folder roots, not an arbitrary directory named
|
||||
# Startup. Keep the configured suffix rather than substituting an app.
|
||||
$parts = @($RelativePath -split '\\')
|
||||
if ($RelativePath -match '[<>:"/|?*\x00-\x1F]' -or $RelativePath -match '%[^%]+%' -or
|
||||
@($parts | Where-Object { -not $_ -or $_ -in @('.', '..') -or $_ -match '[ .]$' }).Count) {
|
||||
throw 'User target contains unsupported or ambiguous path components.'
|
||||
}
|
||||
$startupRoot = 'AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup'
|
||||
$appDataRoot = 'AppData\Roaming'
|
||||
if ($RelativePath -ieq $startupRoot) {
|
||||
$folder = 'Startup'; $suffix = ''
|
||||
} elseif ($RelativePath.StartsWith($startupRoot + '\', [StringComparison]::OrdinalIgnoreCase)) {
|
||||
$folder = 'Startup'; $suffix = $RelativePath.Substring($startupRoot.Length + 1)
|
||||
} elseif ($RelativePath.StartsWith($appDataRoot + '\', [StringComparison]::OrdinalIgnoreCase)) {
|
||||
$folder = 'AppData'; $suffix = $RelativePath.Substring($appDataRoot.Length + 1)
|
||||
} else { throw 'User target must be the Startup known folder or a child of the supported AppData\Roaming known-folder root.' }
|
||||
$keyPath = "Registry::HKEY_USERS\$($User.Sid)\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders"
|
||||
$key = Get-Item -LiteralPath $keyPath -ErrorAction Stop
|
||||
# DoNotExpandEnvironmentNames is essential when reading another user's hive.
|
||||
@@ -66,7 +81,7 @@ function Resolve-WelaSaclUserFile {
|
||||
if (-not $raw) { throw "Known-folder value '$folder' is absent." }
|
||||
$resolved = [regex]::Replace($raw, '(?i)%USERPROFILE%', [System.Text.RegularExpressions.MatchEvaluator]{ param($match) $User.ProfilePath })
|
||||
if ($resolved -match '%[^%]+%' -or $resolved -notmatch '^(?:[A-Za-z]:\\|\\\\)') { throw 'Known-folder path contains unresolved user variables or is not absolute.' }
|
||||
if ($folder -eq 'AppData') { $resolved = $resolved.TrimEnd('\') + '\Signal' }
|
||||
if ($suffix) { $resolved = $resolved.TrimEnd('\') + '\' + $suffix }
|
||||
$expected = $User.ProfilePath.TrimEnd('\') + '\' + $RelativePath
|
||||
$state = if ($resolved -ine $expected) { 'Redirected' } else { 'Resolved' }
|
||||
[pscustomobject]@{ Path = $resolved; State = $state; Diagnostic = 'Resolved from this user hive; remote paths are reported without network access.' }
|
||||
|
||||
@@ -64,18 +64,51 @@ $unloaded = Resolve-WelaSaclUserFile -User ([pscustomobject]@{HiveLoaded=$false}
|
||||
Assert ($unloaded.State -eq 'UnloadedHive') 'Unloaded hive must not fall back to operator APPDATA.'
|
||||
# Verify actual resolver against real catalog escaping, not a pre-normalized fixture.
|
||||
$script:knownFolder = '%USERPROFILE%\AppData\Roaming'
|
||||
$script:knownFolderName = $null; $script:knownFolderReads = 0
|
||||
$script:key = [pscustomobject]@{}
|
||||
$script:key | Add-Member ScriptMethod GetValue { param($Name,$Default,$Options) if ($Options -ne [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) { throw 'Unsafe variable expansion mode' }; return $script:knownFolder }
|
||||
function Get-Item { param($LiteralPath,[switch]$Force,$ErrorAction) return $script:key }
|
||||
$script:key | Add-Member ScriptMethod GetValue { param($Name,$Default,$Options) if ($Options -ne [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) { throw 'Unsafe variable expansion mode' }; $script:knownFolderName = $Name; return $script:knownFolder }
|
||||
function Get-Item {
|
||||
param($LiteralPath,[switch]$Force,$ErrorAction)
|
||||
if ($LiteralPath -ne 'Registry::HKEY_USERS\S-1-5-21-1\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders') { throw 'Unexpected known-folder read scope.' }
|
||||
$script:knownFolderReads++; return $script:key
|
||||
}
|
||||
$definitions = Get-Content -LiteralPath (Join-Path $PSScriptRoot '../config/audit_sacl_targets.json') -Raw | ConvertFrom-Json
|
||||
$user = [pscustomobject]@{Sid='S-1-5-21-1';ProfilePath='C:\Users\One';HiveLoaded=$true}
|
||||
$signal = Resolve-WelaSaclUserFile -User $user -RelativePath $definitions.user_files[1].relpath
|
||||
Assert ($signal.State -eq 'Resolved' -and $signal.Path -eq 'C:\Users\One\AppData\Roaming\Signal') 'Actual doubled-separator catalog path must not mislabel a default known folder as redirected.'
|
||||
# Additional catalog-shaped targets retain their complete suffix under the
|
||||
# selected user's known folder. Resolving a remote root never accesses it.
|
||||
$userFileFixtures = @(
|
||||
[pscustomobject]@{ relpath = 'AppData\\Roaming\\Foo'; suffix = 'Foo' },
|
||||
[pscustomobject]@{ relpath = 'appdata\roaming\Vendor\Cache'; suffix = 'Vendor\Cache' },
|
||||
[pscustomobject]@{ relpath = 'AppData\Roaming\Foo\Startup'; suffix = 'Foo\Startup' }
|
||||
)
|
||||
foreach ($fixture in $userFileFixtures) {
|
||||
$script:knownFolder = '%USERPROFILE%\AppData\Roaming'
|
||||
$result = Resolve-WelaSaclUserFile -User $user -RelativePath $fixture.relpath
|
||||
Assert ($result.State -eq 'Resolved' -and $result.Path -ieq ('C:\Users\One\AppData\Roaming\' + $fixture.suffix) -and $script:knownFolderName -eq 'AppData') 'Additional AppData target must retain its suffix and use the AppData known folder.'
|
||||
$script:knownFolder = '\\server\share\Roaming'
|
||||
$result = Resolve-WelaSaclUserFile -User $user -RelativePath $fixture.relpath
|
||||
Assert ($result.State -eq 'Redirected' -and $result.Path -eq ('\\server\share\Roaming\' + $fixture.suffix) -and $script:knownFolderName -eq 'AppData') 'Redirected AppData target must retain its own suffix.'
|
||||
$readsBefore = $script:knownFolderReads
|
||||
Assert ((Get-WelaSaclTargetObservation -Path $result.Path -Kind FileSystem).PathState -eq 'RemoteNotInspected' -and $script:knownFolderReads -eq $readsBefore) 'Remote AppData target must be reported without target access.'
|
||||
}
|
||||
$script:knownFolder = '%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup'
|
||||
$startup = Resolve-WelaSaclUserFile -User $user -RelativePath $definitions.user_files[0].relpath
|
||||
Assert ($startup.State -eq 'Resolved') 'Actual Startup catalog path normalizes before comparison.'
|
||||
Assert ($startup.State -eq 'Resolved' -and $script:knownFolderName -eq 'Startup') 'Actual Startup catalog path normalizes before comparison and uses its own known folder.'
|
||||
$script:knownFolder = '\\server\share\Startup'
|
||||
Assert ((Resolve-WelaSaclUserFile -User $user -RelativePath $definitions.user_files[0].relpath).State -eq 'Redirected') 'Real redirected Startup remains distinguished.'
|
||||
$startup = Resolve-WelaSaclUserFile -User $user -RelativePath $definitions.user_files[0].relpath
|
||||
Assert ($startup.State -eq 'Redirected' -and $startup.Path -eq $script:knownFolder -and $script:knownFolderName -eq 'Startup') 'Real redirected Startup remains distinguished without appending its catalog suffix twice.'
|
||||
$startupChild = Resolve-WelaSaclUserFile -User $user -RelativePath 'AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\Child'
|
||||
Assert ($startupChild.Path -eq '\\server\share\Startup\Child' -and $script:knownFolderName -eq 'Startup') 'A target below Startup must retain Startup redirection rather than fall back to AppData.'
|
||||
foreach ($invalid in @('Desktop\Startup', 'AppData\Local\Foo', 'AppData\RoamingOther\Foo', 'AppData\Roaming',
|
||||
'AppData\Roaming\..\Local\Foo', 'AppData\Roaming\.\Foo', 'AppData\Roaming\Foo.\Bar', 'AppData\Roaming\Foo \Bar',
|
||||
'AppData\Roaming\Foo:stream', 'AppData/Roaming/Foo', 'AppData\Roaming\*', 'AppData\Roaming\Foo\',
|
||||
'AppData\Roaming\%APPDATA%', 'C:\Users\Other\AppData\Roaming\Foo')) {
|
||||
$readsBefore = $script:knownFolderReads
|
||||
$result = Resolve-WelaSaclUserFile -User $user -RelativePath $invalid
|
||||
Assert ($result.State -eq 'UnresolvedUserPath' -and -not $result.Path -and $script:knownFolderReads -eq $readsBefore) "Unsupported user target must remain unresolved without path reads: $invalid"
|
||||
}
|
||||
Assert (@($live.Targets | Where-Object { $_.Scope -eq 'user_registry' -and $_.Path -match '\\\\' }).Count -eq 0) 'User registry keys normalize catalog separators.'
|
||||
# Failures inside an individual profile must affect global inventory completeness.
|
||||
function Get-ChildItem {
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
**新機能:**
|
||||
|
||||
- プロファイルの plan/audit/configure に対象を限定した SACL の読み取り専用計画を追加しました。オブジェクト監査ポリシー、ユーザーハイブ・フォルダーリダイレクトの未確認箇所、WEF Run/RunOnce の監査エントリを表示し、`-SaclMode Skip` による省略も明示します。SACL の書き込みや未検証の検知率向上は行いません。 (#398) (@Shirofune-Security)
|
||||
- プロファイルの plan/audit/configure に対象を限定した SACL の読み取り専用計画を追加しました。オブジェクト監査ポリシー、ユーザーハイブ・フォルダーリダイレクトの未確認箇所、WEF Run/RunOnce の監査エントリを表示し、`-SaclMode Skip` による省略も明示します。ユーザーファイルの対象は、そのユーザーの AppData または Startup 既知フォルダー配下の相対パスを保持し、未対応・曖昧なパスは未解決として扱います。SACL の書き込みや未検証の検知率向上は行いません。 (#398) (@Shirofune-Security)
|
||||
|
||||
|
||||
- MITRE ATT&CK Navigatorヒートマップに対応した。 (#11) (@fukusuket)
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
**New Features:**
|
||||
|
||||
- Profile plan/audit/configure now include read-only targeted SACL prerequisites with object policy masks, per-user hive and redirected-folder gaps, exact WEF Run/RunOnce audit entries, and an explicit `-SaclMode Skip`. No SACL writes or unverified detection uplift are implied. (#398) (@Shirofune-Security)
|
||||
- Profile plan/audit/configure now include read-only targeted SACL prerequisites with object policy masks, per-user hive and redirected-folder gaps, exact WEF Run/RunOnce audit entries, and an explicit `-SaclMode Skip`. User-file targets retain their configured suffix under the user's AppData or Startup known folder; unsupported or ambiguous paths remain unresolved. No SACL writes or unverified detection uplift are implied. (#398) (@Shirofune-Security)
|
||||
|
||||
|
||||
- Support for MITRE ATT&CK Navigator heatmaps. (#11) (@fukusuket)
|
||||
|
||||
Reference in new issue
Block a user