mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-01 03:54:44 +02:00
* Add reviewed configuration for selected native SACL targets * Link selected SACL changelog to PR 422 * Identify native full-descriptor read failures without partial fallback * Fix diagnostic variable scope in native C# helper * Read explicit descriptor sections and retain observation scope
80 lines
6.9 KiB
PowerShell
80 lines
6.9 KiB
PowerShell
param([switch]$AllowDisposableSaclWrite)
|
|
$ErrorActionPreference='Stop'
|
|
if(-not $AllowDisposableSaclWrite -or $env:GITHUB_ACTIONS -ne 'true' -or $env:RUNNER_ENVIRONMENT -ne 'github-hosted'){throw 'This mutating fixture requires explicit opt-in on a disposable GitHub-hosted Windows runner.'}
|
|
$root=Split-Path $PSScriptRoot -Parent
|
|
Import-Module (Join-Path $root 'modules/AuditProfiles.psm1') -Force
|
|
. (Join-Path $root 'scripts/Configuration.ps1')
|
|
. (Join-Path $root 'scripts/TargetedSaclPlanning.ps1')
|
|
. (Join-Path $root 'scripts/SelectedSaclConfiguration.ps1')
|
|
$script:count=0
|
|
function Assert($Condition,$Message){if(-not $Condition){throw $Message};$script:count++}
|
|
function Fingerprint($Map){(@($Map.Keys|Sort-Object|ForEach-Object{"$_=$($Map[$_])"}) -join ';')}
|
|
$beforePolicy=Get-WelaEffectiveAuditPolicy
|
|
$precedencePath='HKLM:\SYSTEM\CurrentControlSet\Control\Lsa'
|
|
$beforePrecedence=Get-WelaRegistryState $precedencePath SCENoApplyLegacyAuditPolicy
|
|
$privilegeBefore=(Invoke-WelaNative whoami.exe @('/priv','/fo','csv')).Diagnostic
|
|
$nonce=[guid]::NewGuid().ToString('N');$temp=Join-Path ([IO.Path]::GetTempPath()) ('wela-sacl-'+$nonce)
|
|
$regSub='Software\WELASelectedSacl_'+$nonce;$regProvider='HKCU:\'+$regSub
|
|
$file=Join-Path $temp 'probe.txt';$sid=[Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
$policyGuids=@('0CCE921D-69AE-11D9-BED3-505054503030','0CCE921E-69AE-11D9-BED3-505054503030')
|
|
$restored=$false
|
|
try {
|
|
$null=New-Item -ItemType Directory -Path $temp
|
|
[IO.File]::WriteAllText($file,'WELA selected-SACL disposable fixture')
|
|
$null=New-Item -Path $regProvider
|
|
$fileDefinition=[pscustomobject]@{Path=$file;Kind='FileSystem';Resolution='Resolved';PrincipalSid='S-1-1-0';Propagation='None';Inheritance='None';Rights=@('ReadData');AuditFlags=@('Success');PolicyMode='minimum';PolicySelected=$true;RequiredPolicyMask=1}
|
|
$regDefinition=[pscustomobject]@{Path=('Registry::HKEY_USERS\'+$sid+'\'+$regSub);Kind='Registry';Resolution='Resolved';PrincipalSid='S-1-1-0';Propagation='None';Inheritance='None';Rights=@('SetValue');AuditFlags=@('Success');PolicyMode='minimum';PolicySelected=$true;RequiredPolicyMask=1}
|
|
Set-ItemProperty -LiteralPath $precedencePath -Name SCENoApplyLegacyAuditPolicy -Value 1 -Type DWord
|
|
foreach($guid in $policyGuids){Set-WelaEffectiveAuditPolicy -Guid $guid -Mask 1 -Mode minimum}
|
|
$snapshots=@{}
|
|
foreach($definition in @($fileDefinition,$regDefinition)){
|
|
Write-Host ("Reading full native descriptor for owned "+$definition.Kind+" fixture.")
|
|
$before=Get-WelaSelectedSaclSnapshot $definition
|
|
Assert ($before.SecurityInformation -eq 511 -and $before.DescriptorScope -match 'future sections unobserved') 'Native receipt records all current SDK sections with its bounded observation scope.'
|
|
$ace=Get-WelaSelectedSaclAce $definition $before
|
|
Assert (-not(Test-WelaSelectedSaclAce $before $ace)) 'Fresh owned target has no requested audit ACE.'
|
|
Assert-WelaSelectedSaclPrerequisites $definition $ace
|
|
$after=Write-WelaSelectedSaclNative $definition $before $ace
|
|
Assert-WelaSelectedSaclPreserved $before $after $ace
|
|
Assert ((Get-WelaSelectedSaclSnapshotKey (Get-WelaSelectedSaclSnapshot $definition)) -ceq (Get-WelaSelectedSaclSnapshotKey $after)) 'Native readback is stable after additive SACL write.'
|
|
Assert (Test-WelaSelectedSaclAce $after $ace) 'Requested explicit native audit ACE matches on re-read, providing idempotence input.'
|
|
$snapshots[$definition.Kind]=$after
|
|
$caught='';try{Write-WelaSelectedSaclNative $definition $before $ace|Out-Null}catch{$caught=$_.Exception.Message}
|
|
Assert ($caught -match 'changed after') 'Stale descriptor is refused by the real native handle writer.'
|
|
}
|
|
$started=[DateTime]::UtcNow.AddSeconds(-1)
|
|
$null=[IO.File]::ReadAllText($file)
|
|
$valueName='Probe_'+$nonce
|
|
New-ItemProperty -LiteralPath $regProvider -Name $valueName -PropertyType String -Value $nonce | Out-Null
|
|
$found=@{};$deadline=[DateTime]::UtcNow.AddSeconds(20)
|
|
while($found.Count -lt 2 -and [DateTime]::UtcNow -lt $deadline){
|
|
$events=@();try{$events=@(Get-WinEvent -FilterHashtable @{LogName='Security';Id=@(4657,4663);StartTime=$started} -MaxEvents 512 -ErrorAction Stop)}catch{if($_.FullyQualifiedErrorId -notmatch 'NoMatchingEventsFound'){throw}}
|
|
foreach($event in $events){
|
|
try {
|
|
$xml=[xml]$event.ToXml();$data=@{};foreach($node in $xml.Event.EventData.Data){$data[[string]$node.Name]=[string]$node.'#text'}
|
|
if($event.ProviderName -ne 'Microsoft-Windows-Security-Auditing'){continue}
|
|
if($event.Id -eq 4663 -and $data.ObjectName -ieq $file -and [Convert]::ToInt64(($data.ProcessId -replace '^0x',''),16) -eq $PID){$found.FileSystem=$event.ToXml()}
|
|
$expectedRegistry='\REGISTRY\USER\'+$sid+'\'+$regSub
|
|
if($event.Id -eq 4657 -and $data.ObjectName -ieq $expectedRegistry -and $data.ObjectValueName -ceq $valueName -and $data.NewValue -ceq $nonce){$found.Registry=$event.ToXml()}
|
|
}finally{if($event -is [IDisposable]){$event.Dispose()}}
|
|
}
|
|
if($found.Count -lt 2){Start-Sleep -Milliseconds 250}
|
|
}
|
|
Assert ($found.ContainsKey('FileSystem')) 'Benign exact file read generated matched native4663 XML.'
|
|
Assert ($found.ContainsKey('Registry')) 'Benign unique registry value write generated matched native4657 XML.'
|
|
foreach($kind in @('FileSystem','Registry')){Write-Host ("Native fixture evidence "+$kind+': '+$found[$kind])}
|
|
Assert ((Invoke-WelaNative whoami.exe @('/priv','/fo','csv')).Diagnostic -ceq $privilegeBefore) 'Native target adapters restore process privilege state after success and refused writes.'
|
|
Write-Host "PASS: $script:count actual selected-SACL assertions on owned disposable targets only; no Sigma/backend/descendant claim."
|
|
} finally {
|
|
foreach($guid in $policyGuids){Set-WelaEffectiveAuditPolicy -Guid $guid -Mask $beforePolicy[$guid] -Mode exact}
|
|
if($beforePrecedence.ValueExists){Set-ItemProperty -LiteralPath $precedencePath -Name SCENoApplyLegacyAuditPolicy -Type $beforePrecedence.Type -Value $beforePrecedence.Value}
|
|
else{Remove-ItemProperty -LiteralPath $precedencePath -Name SCENoApplyLegacyAuditPolicy -ErrorAction SilentlyContinue}
|
|
$afterPolicy=Get-WelaEffectiveAuditPolicy;$afterPrecedence=Get-WelaRegistryState $precedencePath SCENoApplyLegacyAuditPolicy
|
|
if((Fingerprint $beforePolicy) -cne (Fingerprint $afterPolicy) -or ($beforePrecedence|ConvertTo-Json -Compress) -cne ($afterPrecedence|ConvertTo-Json -Compress)){throw "Fixture policy restoration failed; retain owned evidence at $temp and $regProvider."}
|
|
if(Test-Path -LiteralPath $regProvider){Remove-Item -LiteralPath $regProvider -Recurse -Force}
|
|
if(Test-Path -LiteralPath $temp){Remove-Item -LiteralPath $temp -Recurse -Force}
|
|
$restored=$true
|
|
Write-Host 'PASS: all59 native audit masks and typed precedence restored; only owned disposable targets removed.'
|
|
}
|
|
$global:LASTEXITCODE=0
|