mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-07 23:14:45 +02:00
Preserve policy arrays through Windows PowerShell JSON roundtrips
This commit is contained in:
1 parent
1ea0687616
commit
2e329c7f2f
2 files changed
+19
-8
No files matched your search
@@ -1,4 +1,12 @@
|
||||
# Explicit recovery of one completed Windows PowerShell transcription policy write.
|
||||
function Copy-WelaTranscriptRecoveryValue {
|
||||
param($Value)
|
||||
# Windows PowerShell 5.1 annotates a root array emitted by ConvertFrom-Json;
|
||||
# serializing that annotated array can introduce synthetic value/count keys.
|
||||
# Keep arrays nested during the JSON roundtrip and emit their actual items.
|
||||
$holder=ConvertFrom-WelaRecoveryJson (Get-WelaRecoveryKey ([pscustomobject]@{Data=$Value}))
|
||||
$holder.Data
|
||||
}
|
||||
function Get-WelaTranscriptRecoverySources {
|
||||
$sources=[ordered]@{}
|
||||
foreach($name in @('WELA.ps1','scripts/TranscriptionRecovery.ps1','scripts/PowerShellTranscription.ps1','scripts/Configuration.ps1','scripts/AuditRecovery.ps1','scripts/ControlApplicability.ps1','scripts/ChannelRead.ps1','scripts/ChannelReadNative.cs','scripts/WefArrival.ps1')) {
|
||||
@@ -96,7 +104,10 @@ function New-WelaTranscriptRecoveryPlan {
|
||||
$before=$entry.Before;$after=$last.After
|
||||
foreach($snapshot in @($before,$after)) {
|
||||
if($snapshot.Capability.Status -cne 'Supported' -or $snapshot.Policy -isnot [array] -or $snapshot.Policy.Count -ne 2 -or
|
||||
$snapshot.Policy[0].View -cne 'Registry64' -or $snapshot.Policy[1].View -cne 'Registry32'){throw 'Both canonical shared registry views are required.'}
|
||||
$snapshot.Policy[0].View -cne 'Registry64' -or $snapshot.Policy[1].View -cne 'Registry32'){
|
||||
$policyType=if($null -eq $snapshot.Policy){'<null>'}else{$snapshot.Policy.GetType().FullName}
|
||||
throw "Both canonical shared registry views are required. Capability=$($snapshot.Capability.Status); PolicyType=$policyType; Count=$(@($snapshot.Policy).Count); Views=$(@($snapshot.Policy.View) -join ','); Observation=$(Get-WelaRecoveryKey $snapshot)"
|
||||
}
|
||||
Test-WelaTranscriptSharedPolicy $snapshot.Policy
|
||||
foreach($name in @('EnableTranscripting','OutputDirectory')){Assert-WelaTranscriptRecoveryValue $snapshot.Policy[0].Machine.$name $name}
|
||||
}
|
||||
@@ -105,9 +116,9 @@ function New-WelaTranscriptRecoveryPlan {
|
||||
(Get-WelaTranscriptRecoveryTypedKey $before.Policy[0].Machine.EnableInvocationHeader) -cne (Get-WelaTranscriptRecoveryTypedKey $after.Policy[0].Machine.EnableInvocationHeader)){throw 'Original configuration did not preserve user/header policy.'}
|
||||
$current=Get-WelaTranscriptState $entry.Target.OutputDirectory
|
||||
if((Get-WelaRecoveryKey $current.Policy) -cne (Get-WelaRecoveryKey $after.Policy) -or (Get-WelaRecoveryKey $current.Destination) -cne (Get-WelaRecoveryKey $after.Destination)){throw 'Current policy/destination differs from the original final After state.'}
|
||||
$target=ConvertFrom-WelaRecoveryJson (Get-WelaRecoveryKey $after.Policy)
|
||||
$target=Copy-WelaTranscriptRecoveryValue $after.Policy
|
||||
foreach($view in $target){foreach($name in @('EnableTranscripting','OutputDirectory')) {
|
||||
$view.Machine.$name=ConvertFrom-WelaRecoveryJson (Get-WelaRecoveryKey $before.Policy[0].Machine.$name)
|
||||
$view.Machine.$name=Copy-WelaTranscriptRecoveryValue $before.Policy[0].Machine.$name
|
||||
# Keep the existing key; absence recovery removes only the selected value.
|
||||
$view.Machine.$name.KeyExists=$true
|
||||
}}
|
||||
@@ -197,7 +208,7 @@ function Invoke-WelaTranscriptRecovery {
|
||||
$output=New-WelaRecoveryOutput $OutputPath
|
||||
$outputObservation=Get-WelaTranscriptDestination $output
|
||||
$report=[pscustomobject][ordered]@{Status='Failed';ExitCode=1;OutputPath=$output;PlanSha256=$source.Sha256;Steps=@();Before=$plan.ExpectedPolicy;After=$null;Diagnostic='';SigmaEvtxCredit=0;Scope='Two typed Windows PowerShell machine transcription values only; no transcript, session adoption, central collection or policy persistence proof.'}
|
||||
$expected=ConvertFrom-WelaRecoveryJson (Get-WelaRecoveryKey $plan.ExpectedPolicy)
|
||||
$expected=Copy-WelaTranscriptRecoveryValue $plan.ExpectedPolicy
|
||||
try {
|
||||
if(-not $Auto -and (Read-Host 'Restore the reviewed transcription values, including any explicitly consented temporary suspension? (y/N)') -cnotin @('y','Y')){$report.Status='Declined';$report.ExitCode=0}
|
||||
else {
|
||||
@@ -206,13 +217,13 @@ function Invoke-WelaTranscriptRecovery {
|
||||
foreach($step in $plan.Steps) {
|
||||
$sequence++
|
||||
Assert-WelaTranscriptRecoveryBindings $plan $expected $source.Path $source.Sha256
|
||||
$receipt=[pscustomobject]@{Sequence=$sequence;Status='Pending';RecordedUtc=[datetime]::UtcNow.ToString('o');PlanSha256=$source.Sha256;Step=$step;Before=(ConvertFrom-WelaRecoveryJson (Get-WelaRecoveryKey $expected));After=$null}
|
||||
$receipt=[pscustomobject]@{Sequence=$sequence;Status='Pending';RecordedUtc=[datetime]::UtcNow.ToString('o');PlanSha256=$source.Sha256;Step=$step;Before=(Copy-WelaTranscriptRecoveryValue $expected);After=$null}
|
||||
Write-WelaTranscriptRecoveryArtifact $outputObservation ('{0:d3}-pending.json' -f $sequence) $receipt
|
||||
Assert-WelaTranscriptRecoveryBindings $plan $expected $source.Path $source.Sha256
|
||||
Set-WelaTranscriptRecoveryValue $step.Name $step.Value
|
||||
foreach($view in $expected){$view.Machine.($step.Name)=ConvertFrom-WelaRecoveryJson (Get-WelaRecoveryKey $step.Value)}
|
||||
foreach($view in $expected){$view.Machine.($step.Name)=Copy-WelaTranscriptRecoveryValue $step.Value}
|
||||
Assert-WelaTranscriptRecoveryBindings $plan $expected $source.Path $source.Sha256
|
||||
$receipt.Status='Confirmed';$receipt.After=ConvertFrom-WelaRecoveryJson (Get-WelaRecoveryKey $expected)
|
||||
$receipt.Status='Confirmed';$receipt.After=Copy-WelaTranscriptRecoveryValue $expected
|
||||
Write-WelaTranscriptRecoveryArtifact $outputObservation ('{0:d3}-confirmed.json' -f $sequence) $receipt
|
||||
$report.Steps += [pscustomobject]@{Sequence=$sequence;Name=$step.Name;Status='Confirmed';Value=$step.Value}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ $script:checks=0;$root=Join-Path ([IO.Path]::GetTempPath()) ('wela-transcript-re
|
||||
$null=New-Item -ItemType Directory $root
|
||||
function Assert($Value,[string]$Message){if(-not $Value){throw "FAIL: $Message"};$script:checks++}
|
||||
function Reject([scriptblock]$Action,[string]$Pattern){$message='';try{& $Action|Out-Null}catch{$message=$_.Exception.Message};Assert ($message -match $Pattern) "Expected '$Pattern', got '$message'"}
|
||||
function Copy-Value($Value){ConvertFrom-WelaRecoveryJson (Get-WelaRecoveryKey $Value)}
|
||||
function Copy-Value($Value){Copy-WelaTranscriptRecoveryValue $Value}
|
||||
function Typed($Value,$Type='DWord'){[pscustomobject]@{KeyExists=$true;ValueExists=($null -ne $Value);Value=$Value;Type=$(if($null -ne $Value){$Type}else{$null})}}
|
||||
function Get-WelaTranscriptRecoveryContext {[pscustomobject]@{Host=[pscustomobject]@{Computer='fixture';MachineGuid=$script:machine};Reader='fixture-reader'}}
|
||||
function Get-WelaTranscriptRecoverySources {[pscustomobject]@{Code=$script:code}}
|
||||
|
||||
Reference in new issue
Block a user