Document scoped PowerShell controls and align native evidence boundaries

This commit is contained in:
Shirofune-Security committed 2026-09-22 14:43:40 +09:00
1 parent 3f613ea19c
commit e419b073fe
9 files changed
+85 -12

No files matched your search

+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
Copy-Item -Recurse -Path ./scripts -Destination release-binaries/
Copy-Item -Recurse -Path ./modules -Destination release-binaries/
New-Item -ItemType Directory -Path release-binaries/docs -Force | Out-Null
Copy-Item -Path ./docs/gpo-audit-packages.md, ./docs/gpo-package-deployment.md, ./docs/gpo-creation.md, ./docs/wmi-probe.md, ./docs/firewall-logging.md, ./docs/firewall-logging-recovery.md, ./docs/ipsec-prerequisites.md, ./docs/wec-state.md, ./docs/wec-update.md, ./docs/wec-runtime.md, ./docs/wef-deployment.md, ./docs/native-channel-access.md, ./docs/eventlog-settings.md, ./docs/channel-read.md, ./docs/native-rule-eligibility.md, ./docs/native-validation.md, ./docs/wef-arrival.md, ./docs/smb-runtime-activation.md, ./docs/smb-auditing.md, ./docs/wec-ingress.md, ./docs/capi2-probe.md, ./docs/powershell-transcription.md, ./docs/transcription-recovery.md, ./docs/eventlog-recovery.md, ./docs/failed-logon-probe.md, ./docs/wec-listener.md, ./docs/file-access-probe.md, ./docs/applocker-script-probe.md, ./docs/applocker-probe.md, ./docs/selected-sacl-configuration.md, ./docs/registry-sacl-recovery.md, ./docs/targeted-sacl-planning.md, ./docs/native-registry-sacl-validation.md, ./docs/wec-authorization.md, ./docs/channel-recovery.md, ./docs/outgoing-ntlm.md, ./docs/wec-collector-observation.md, ./docs/native-provider-packs.md, ./docs/native-provider-acceptance.md, ./docs/audit-catalog-mappings.md, ./docs/native-token-right-attribution.md, ./docs/audit-notifications.md, ./docs/native-onesettings-acceptance.md, ./docs/ntlm-auditing.md, ./docs/wef-query.md, ./docs/native-filesystem-sacl-validation.md -Destination release-binaries/docs/
Copy-Item -Path ./docs/gpo-audit-packages.md, ./docs/gpo-package-deployment.md, ./docs/gpo-creation.md, ./docs/wmi-probe.md, ./docs/firewall-logging.md, ./docs/firewall-logging-recovery.md, ./docs/ipsec-prerequisites.md, ./docs/wec-state.md, ./docs/wec-update.md, ./docs/wec-runtime.md, ./docs/wef-deployment.md, ./docs/native-channel-access.md, ./docs/eventlog-settings.md, ./docs/channel-read.md, ./docs/native-rule-eligibility.md, ./docs/native-validation.md, ./docs/wef-arrival.md, ./docs/smb-runtime-activation.md, ./docs/smb-auditing.md, ./docs/wec-ingress.md, ./docs/capi2-probe.md, ./docs/powershell-transcription.md, ./docs/powershell-logging.md, ./docs/transcription-recovery.md, ./docs/eventlog-recovery.md, ./docs/failed-logon-probe.md, ./docs/wec-listener.md, ./docs/file-access-probe.md, ./docs/applocker-script-probe.md, ./docs/applocker-probe.md, ./docs/selected-sacl-configuration.md, ./docs/registry-sacl-recovery.md, ./docs/targeted-sacl-planning.md, ./docs/native-registry-sacl-validation.md, ./docs/wec-authorization.md, ./docs/channel-recovery.md, ./docs/outgoing-ntlm.md, ./docs/wec-collector-observation.md, ./docs/native-provider-packs.md, ./docs/native-provider-acceptance.md, ./docs/audit-catalog-mappings.md, ./docs/native-token-right-attribution.md, ./docs/audit-notifications.md, ./docs/native-onesettings-acceptance.md, ./docs/ntlm-auditing.md, ./docs/wef-query.md, ./docs/native-filesystem-sacl-validation.md -Destination release-binaries/docs/
- name: Set Artifact Name
if: contains(matrix.info.os, 'windows') == true
+2
View File
@@ -4,6 +4,8 @@
**改善:**
- Windows PowerShell 5.1 のモジュール/スクリプトブロック監査を明示的に選択する `powershell-logging` Audit/Plan/Configure を追加しました。モジュール名、型付き変更前記録、観測したポリシーの変化と再読取を確認し、呼出しログ・転写・Core設定・既存モジュール一覧を保持します。Server 2022/2025 の破棄可能なテストで正確な 4103/4104 と復元を要求し、PowerShell 7 のフォールバック、管理ポリシーの継続性、転送、Sigma 対応は別途検証します。(関連 #364, #366, #387) (@Shirofune-Security)
- Server2022/2025 と PowerShell5.1/7 で、Token Right Adjusted の正規GUIDに対する Security4703 の実機検証を追加しました。所有する子プロセスの既存権限を固定手順で無効化・復元し、候補となる2つの監査マスクを比較して、実イベント・実行条件・ハッシュとポリシー/トークンの復元を記録します。過去の候補は条件付きのまま維持し、製品用プローブ・全OS共通の対応関係・Sigma加点は追加しません。(関連 #380) (@Shirofune-Security)
- OneSettingsポリシーと明示的なPrivacyチャネル設定の公開CLIを実機検証します。Server 2022で実際の適用、型付き復元記録と再読取、冪等性、不正値の拒否を確認し、Server 2025の既存の拒否を維持します。両PowerShellで厳密な後処理を検証し、イベント生成やSigma対応は主張しません。通知コントロール省略時の引数渡しを修正し、既定のAuditは両項目を読み取り、項目未選択のConfigureは非ゼロで失敗します。(関連 #378) (@Shirofune-Security)
+2
View File
@@ -4,6 +4,8 @@
**Improvements:**
- Added explicit `powershell-logging` Audit/Plan/Configure for selected Windows PowerShell 5.1 module and script-block logging. Reviewed module names, typed original journals, full observed-policy drift guards and native readback preserve invocation/transcription/Core settings and other module entries. Disposable Server 2022/2025 tests require exact local 4103/4104 evidence and policy cleanup; PowerShell 7 fallback, managed-host persistence, forwarding and Sigma readiness remain separate. (Related #364, #366, #387) (@Shirofune-Security)
- Added disposable native Security4703 attribution for the canonical Token Right Adjusted GUID on Server2022/2025 and PowerShell5.1/7. A fixed owned-child privilege disable/restore compares the two historical audit-mask candidates, retains exact event/context/hash evidence and verifies policy/token cleanup. Historical candidates remain conditional; no production probe, universal mapping or Sigma credit. (Related #380) (@Shirofune-Security)
- Add native public OneSettings policy and explicit Privacy-channel configuration acceptance: actual apply, typed journals/readback, idempotence and invalid-value refusals on Server 2022; preserve the existing Server 2025 refusal. Both PowerShell engines verify exact fixture cleanup without event or Sigma claims. Fix omitted notification-control dispatch so default Audit reads both controls and Configure without a selection fails with a nonzero exit. (Related #378) (@Shirofune-Security)
+62
View File
@@ -0,0 +1,62 @@
# Scoped Windows PowerShell event logging
`powershell-logging` audits, plans and explicitly enables selected **Windows PowerShell 5.1** module or script-block logging. This is separate from [text transcription](powershell-transcription.md), advanced Security audit profiles, 4688 command-line capture and the broader `configure` command. It changes no channel, execution policy, service, invocation-logging preference, audit mask or transcript setting. Sysmon is excluded.
```powershell
# Read-only inventory of machine/current-user Windows PowerShell policy,
# PowerShell Core policy, protected-event policy and the Operational channel.
./WELA.ps1 powershell-logging -ResultsPath new-audit.json
# Select exactly the requested controls and module names.
./WELA.ps1 powershell-logging -PowerShellLoggingAction Plan `
-PowerShellLoggingControl Module,ScriptBlock `
-PowerShellLoggingModuleName Microsoft.PowerShell.Utility -ResultsPath new-plan.json
./WELA.ps1 powershell-logging -PowerShellLoggingAction Configure `
-PowerShellLoggingControl Module,ScriptBlock `
-PowerShellLoggingModuleName Microsoft.PowerShell.Utility -DryRun
# After reviewing existing module names and the before-state:
./WELA.ps1 powershell-logging -PowerShellLoggingAction Configure `
-PowerShellLoggingControl Module,ScriptBlock `
-PowerShellLoggingModuleName Microsoft.PowerShell.Utility `
-Auto -BackupPath C:\WELA-Recovery\new-run -ResultsPath new-result.json
```
Use a native 64-bit Windows PowerShell 5.1 or PowerShell 7 host. The actual Windows role, build, patch and installed Windows PowerShell engine are read; caller role/build overrides are refused. Reviewed families are Windows 11 builds 22000, 22621, 22631, 26100 and 26200, and Server 2022/2025 builds 20348/26100. Native client, joined-member, DC and AD CS acceptance remains separate from hosted standalone-server tests. `Winmgmt` and `EventLog` must already run; the command starts no service. Configure requires elevation and a fresh recovery directory. Plan and Configure require explicit controls; there is no implicit enable-everything selection. Audit without selection inventories existing policy only.
The array examples are PowerShell syntax. When launching `powershell.exe -File` from another shell, use a reviewed PowerShell wrapper to bind multiple array elements correctly. Literal module names can contain ASCII letters, digits, dots, underscores and hyphens, up to 128 characters each. Up to 32 unique names can be selected. Paths and wildcard patterns are refused; the exact `*` value is accepted **only when explicitly supplied** to request all modules. Installation and execution of arbitrary selected modules are not performed or asserted.
## Requested values and preserved scope
Under `HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell`:
| Selection | Requested values |
|---|---|
| `ScriptBlock` | `ScriptBlockLogging\EnableScriptBlockLogging` = DWORD 1 |
| `Module` | An explicit REG_SZ entry under `ModuleLogging\ModuleNames` for each selected module (name and data both equal the selected literal), then `ModuleLogging\EnableModuleLogging` = DWORD 1 |
An existing matching module entry is retained. The command does not delete or replace other module names: **enabling Module logging also activates the existing configured module list**, which can already include `*` or broader patterns. Review the entire `Plan.Before.Machine` tree, not just the newly selected names. A collision between a selected value name and different existing data is refused. Unknown DWORD values, incorrect selected types, and non-string/empty existing module entries block the complete preflight. Unselected existing values and key access descriptors are preserved. Missing selected keys may be created below the existing Microsoft Windows policy parent; their absence is recorded in the original snapshot.
Microsoft documents machine policy precedence over user policy, module pipeline logging, script-block logging, and the additional volume generated by invocation start/stop logging in [Windows PowerShell Group Policy settings](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_group_policy_settings?view=powershell-5.1). The [ADMX mapping](https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-admx-powershellexecutionpolicy#enablemodulelogging) documents the ModuleLogging registry location. This command offers explicit source controls; it does not import a Microsoft/CIS/ASD baseline or claim complete compliance with one.
`SOFTWARE\Policies` is [shared across registry views](https://learn.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys). Both views are compared before a native-view write; no literal `Wow6432Node` policy tree is created. Snapshots include the Windows PowerShell machine and current-user policy trees, each bounded to 64 keys, eight levels, 128 values per key and one Mi character serialized data. Incomplete, denied, unstable or excessive trees are refused. Existing owner/group/DACL observations are recorded; these snapshots do not claim registry SACL enumeration or effective access for other principals.
PowerShell 7 has [separate PowerShell Core settings and an optional Windows-policy fallback](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_group_policy_settings?view=powershell-7.5). Its registry policy trees are observed and preserved; `powershell.config.json`, session behavior and fallback selection are not assessed. **A PowerShell 7 deployment using that fallback may inherit changes to Windows PowerShell policy.** Running WELA under PowerShell 7 does not establish PowerShell 7 event coverage. Existing sessions are not restarted or asserted to adopt the changes.
Script-block and module event content can include command arguments and script text. Existing protected-event settings are observed and preserved; the command does not provision encryption certificates, decrypt events, change event-reader permissions or assess whether a collector can read the resulting content.
## Journals, drift and recovery
Every changed value has an original `before.jsonl` entry containing the full typed observed state, requested value and source fingerprints before its native setter runs. Module entries are added before the enable DWORD. The shared configuration runner handles DryRun, declined changes, immediate readback and final verification. The command compares host/engine/source hashes, channel metadata and all observed policy trees again after approval and journaling, then validates that only the one requested value and required new ancestor keys changed. Failed reads, partial writes, wrong readback or unrelated drift stop later operations. Available original/results evidence remains; no automatic rollback overwrites later policy.
This is not an atomic registry transaction. Another administrator or GPO can change state between observations. Local registry compliance does not identify the current authoritative GPO/MDM source, prove persistence after refresh or imply provider event generation. `-Auto` skips ordinary per-value prompts only. No group-policy refresh is invoked. A skipped/dry-run result is not configuration evidence; failures produce a nonzero exit. Each explicit ResultsPath must be a new file and is created without overwrite; protect the recovery parent and resulting host/policy evidence.
For manual recovery, compare the original journal, confirmed results and current state. Restore only each proven changed value, using its original registry type/data or removing that exact value when it was absent. Preserve other module names and all unrelated values. Remove a newly created key only when it was originally absent and remains empty. Never delete the whole PowerShell policy tree or restore old full descriptors. Determine whether a newer authoritative policy has superseded the recorded state before restoration.
## Native validation and limits
The focused suite covers explicit selection, exact types, module-name collisions, preservation, journal-before-write, missing-key creation, idempotence, dry runs, refused output reuse, partial failures and approval-time drift. Public CLI tests reject unrelated/profile/role options before dispatch.
The opt-in disposable Server 2022/2025 matrix runs WELA under Windows PowerShell 5.1 and PowerShell 7. It saves native state, prepares selected disabled values, exercises the public Audit/Plan/DryRun/Configure path, checks original journals and idempotence, then launches a new fixed **native Windows PowerShell 5.1** utility command with a unique benign marker. Native Operational event XML must match the owned child PID, provider GUID/name, actual SID, computer, record boundary and measured process-lifetime interval; script-block evidence additionally matches the exact script path/text and complete fragment counts. Only the fixture prepares policy or generates events. Wrong-type refusal and exact typed policy/key, channel and all-59-mask cleanup are required. Native event artifacts must be reviewed before claiming a matrix run passed.
These are configuration and local benign-event checks, not a Sigma rule match. Windows 11, managed clients, DC/CA hosts, x86 Windows PowerShell, PowerShell 7 event generation, forwarding, backend normalization/queries, retention and volume remain separate acceptance. Reports retain `ReadyRuleCredit=0`; issues #364, #366 and #387 remain open for their broader requirements.
+5 -4
View File
@@ -124,7 +124,7 @@ function Invoke-WelaPowerShellLogging {
param([ValidateSet('Audit','Plan','Configure')][string]$Action='Audit',[string[]]$Control=@(),[string[]]$ModuleName=@(),[switch]$Auto,[switch]$DryRun,[string]$BackupPath,[string]$ResultsPath)
Assert-WelaPsLoggingSelection $Action $Control $ModuleName
if($Action -ne 'Configure' -and ($Auto -or $DryRun -or $BackupPath)){throw 'Consent, dry-run and backup options require PowerShellLoggingAction Configure.'}
if($ResultsPath -and (Test-Path -LiteralPath $ResultsPath)){throw 'ResultsPath must name a new file.'}
if($ResultsPath){$ResultsPath=$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ResultsPath);if(Test-Path -LiteralPath $ResultsPath){throw 'ResultsPath must name a new file.'};if(-not (Test-Path -LiteralPath ([IO.Path]::GetDirectoryName($ResultsPath)) -PathType Container)){throw 'ResultsPath parent must already exist.'}}
$definitions=@(Get-WelaPsLoggingDefinitions $Control $ModuleName);$before=$null;$diagnostic='';$known=$false
try {$before=Get-WelaPsLoggingSnapshot;Assert-WelaPsLoggingKnown $before $definitions;$known=$true}catch{$diagnostic=$_.Exception.Message}
$plan=[pscustomobject]@{Selection=@($Control);ModuleNames=@($ModuleName);Before=$before;Controls=@(foreach($definition in $definitions){[pscustomobject]@{Definition=$definition;Status=$(if(-not $known){'Unknown'}elseif(Test-WelaPsLoggingValue $before $definition){'AlreadyCompliant'}else{'ChangeRequired'})}});Status=$(if($known){'Observed'}else{'Unknown'});Diagnostic=$diagnostic;Provenance=@('https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_group_policy_settings?view=powershell-5.1','https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-admx-powershellexecutionpolicy');Meaning='Explicit Windows PowerShell 5.1 machine-policy selection. Existing module names remain active when Module logging is enabled; no claim of a complete Microsoft/CIS/ASD baseline.'}
@@ -132,8 +132,9 @@ function Invoke-WelaPowerShellLogging {
if(-not $known){$report=[pscustomobject]@{ExitCode=1;Scope='windows-powershell-event-logging-policy-only';Results=@();Diagnostic=$diagnostic}}
else {
$context=New-WelaConfigurationContext -Auto:$Auto -DryRun:$DryRun -BackupPath $BackupPath
$shared=@{Expected=$before;Definitions=$definitions;Failed=$false}
$shared=@{Expected=$before;Definitions=$definitions;Failed=$false;StopReason=$null}
foreach($definition in $definitions){
if($shared.StopReason){$context.Results.Add([pscustomobject]@{Id=('PowerShellLogging/'+$definition.Path+'/'+$definition.Name);Kind='Registry';Target=@{Path=$definition.Path;Name=$definition.Name};Desired=@{Type=$definition.Type;Value=$definition.Value};Before=$null;After=$null;Status='Skipped';Diagnostic=$shared.StopReason});continue}
$state=@{Shared=$shared;Definition=$definition}
$read={param($s) if($s.Shared.Failed){throw 'An earlier operation failed; remaining operations are stopped.'};$snapshot=Get-WelaPsLoggingSnapshot;if((ConvertTo-WelaPsLoggingKey $snapshot) -cne (ConvertTo-WelaPsLoggingKey $s.Shared.Expected)){throw 'Policy, host, channel, engine or source changed from the reviewed state.'};Assert-WelaPsLoggingKnown $snapshot $s.Shared.Definitions;return $snapshot}
$test={param($snapshot,$s) Test-WelaPsLoggingValue $snapshot $s.Definition}
@@ -141,8 +142,8 @@ function Invoke-WelaPowerShellLogging {
try {$fresh=Get-WelaPsLoggingSnapshot;if((ConvertTo-WelaPsLoggingKey $fresh) -cne (ConvertTo-WelaPsLoggingKey $s.Shared.Expected)){throw 'Pre-write state drifted after journal/approval; no write attempted.'};Set-WelaPsLoggingValue $s.Definition;$after=Get-WelaPsLoggingSnapshot;Assert-WelaPsLoggingTransition $fresh $after $s.Definition;$s.Shared.Expected=$after;'Only the named Windows PowerShell policy value was changed and read back.'}catch{$s.Shared.Failed=$true;throw}
}
Invoke-WelaConfigurationControl -Context $context -Id ('PowerShellLogging/'+$definition.Path+'/'+$definition.Name) -Kind Registry -Target @{Hive='LocalMachine';View='Registry64';Path=('SOFTWARE\Policies\Microsoft\Windows\PowerShell\'+$definition.Path);Name=$definition.Name} -Desired @{Type=$definition.Type;Value=$definition.Value} -Read $read -Compliant $test -Apply $apply -CallbackState $state -Description 'Enable the explicitly selected event-logging policy; existing module names are preserved.'
if($context.Results[$context.Results.Count-1].Status -eq 'Failed'){$shared.Failed=$true;break}
if($context.Results[$context.Results.Count-1].Status -eq 'Skipped' -and -not $DryRun){break}
if($context.Results[$context.Results.Count-1].Status -eq 'Failed'){$shared.Failed=$true;$shared.StopReason='Not attempted because an earlier selected operation failed.'}
if($context.Results[$context.Results.Count-1].Status -eq 'Skipped' -and -not $DryRun){$shared.StopReason='Not attempted because an earlier selected operation was declined.'}
}
$report=Complete-WelaConfiguration -Context $context -Scope 'windows-powershell-event-logging-policy-only' -SuccessMessage 'Selected local machine policy values verified; fresh-session events and policy persistence remain separate.'
}
+1 -1
View File
@@ -57,7 +57,7 @@ try {
$bad=Invoke-WelaPowerShellLogging -Action Configure -Control Module -ModuleName Microsoft.PowerShell.Utility -Auto -BackupPath (Join-Path $root 'badname')
Assert ($bad.ExitCode -eq 1 -and -not (Test-Path (Join-Path $root 'badname'))) 'Unknown module value fails entire preflight'
Reset;$script:failWrite=$true;$failed=Invoke-WelaPowerShellLogging -Action Configure -Control Module,ScriptBlock -ModuleName Microsoft.PowerShell.Utility -Auto -BackupPath (Join-Path $root 'writefailure')
Assert ($failed.ExitCode -eq 1 -and $script:writes -eq 1) 'Native failure stops later writes'
Assert ($failed.ExitCode -eq 1 -and $script:writes -eq 1 -and @($failed.Results|Where-Object Status -eq Skipped).Count -eq 2) 'Native failure stops and explicitly reports later writes'
Reset;$script:corrupt=$true;$failed=Invoke-WelaPowerShellLogging -Action Configure -Control Module,ScriptBlock -ModuleName Microsoft.PowerShell.Utility -Auto -BackupPath (Join-Path $root 'corrupt')
Assert ($failed.ExitCode -eq 1 -and $script:writes -eq 1) 'Preservation failure stops later writes'
Reset;$snapshot=CloneFixture $script:observed;$definition=$definitions[0];$after=Mutate $snapshot $definition;Assert-WelaPsLoggingTransition $snapshot $after $definition;Assert $true 'Exact additive transition accepted'
+8 -6
View File
@@ -6,7 +6,7 @@ $repo=Split-Path $PSScriptRoot -Parent
. (Join-Path $repo 'scripts/PowerShellLogging.ps1')
Import-Module (Join-Path $repo 'modules/AuditProfiles.psm1') -Force
$root=Join-Path $env:RUNNER_TEMP ('wela-powershell-logging-'+[guid]::NewGuid().ToString('N'));$null=New-Item -ItemType Directory $root
$engine=(Get-Process -Id $PID).Path;$script:count=0;$failure=$null;$cleanupErrors=@();$original=$null;$prepared=$null;$masks=$null;$workerPath=$null
$engine=(Get-Process -Id $PID).Path;$script:count=0;$failure=$null;$cleanupErrors=@();$original=$null;$prepared=$null;$masks=$null;$workerPath=$null;$mutationStarted=$false
function Assert($Value,$Message){if(-not $Value){throw $Message};$script:count++}
function Save($Name,$Value){$Value|ConvertTo-Json -Depth 28|Set-Content -LiteralPath (Join-Path $root $Name) -Encoding UTF8}
function Masks {$m=Get-WelaEffectiveAuditPolicy;@($m.Keys|Sort-Object|ForEach-Object{"$_=$($m[$_])"}) -join ';'}
@@ -63,7 +63,7 @@ function ReadEvents([int]$OwnedId,[long]$Watermark){
$query="*[System[(EventID=4103 or EventID=4104) and Execution[@ProcessID='$OwnedId'] and EventRecordID > $Watermark]]"
$q=[Diagnostics.Eventing.Reader.EventLogQuery]::new('Microsoft-Windows-PowerShell/Operational',[Diagnostics.Eventing.Reader.PathType]::LogName,$query);$q.TolerateQueryErrors=$false
$reader=$null;$list=New-Object 'System.Collections.Generic.List[string]'
try{$reader=[Diagnostics.Eventing.Reader.EventLogReader]::new($q);for($i=0;$i -le 64;$i++){$record=$reader.ReadEvent([TimeSpan]::FromSeconds(2));if(-not $record){break};try{$xml=$record.ToXml();if($xml.Length -gt 262144){throw 'Owned child event exceeds XML bound.'};$list.Add($xml)}finally{$record.Dispose()};if($i -eq 64){throw 'Owned child event candidate cap exceeded.'}};foreach($status in $reader.LogStatus){if($status.StatusCode -ne 0){throw 'Native query reports an incomplete channel read.'}};return @($list.ToArray())}finally{if($reader){$reader.Dispose()}}
try{$reader=[Diagnostics.Eventing.Reader.EventLogReader]::new($q);for($i=0;$i -le 64;$i++){$record=$reader.ReadEvent([TimeSpan]::FromSeconds(2));if(-not $record){break};try{$xml=$record.ToXml();if($xml.Length -gt 262144){throw 'Owned child event exceeds XML bound.'};$list.Add($xml)}finally{$record.Dispose()};if($i -eq 64){throw 'Owned child event candidate cap exceeded.'}};$statuses=@($reader.LogStatus);if($statuses.Count -ne 1 -or $statuses[0].LogName -cne 'Microsoft-Windows-PowerShell/Operational' -or $statuses[0].StatusCode -ne 0){throw 'Native query must report exactly one complete successful expected channel.'};return @($list.ToArray())}finally{if($reader){$reader.Dispose()}}
}
try{
$original=Get-WelaPsLoggingSnapshot;Save 'original.json' $original;$masks=Masks
@@ -72,6 +72,7 @@ try{
$protected=@($original.ProtectedEventLogging.Keys|ForEach-Object {$_.Values}|Where-Object {$_.Name -eq 'EnableProtectedEventLogging' -and $_.Value -ne 0})
Assert ($protected.Count -eq 0) 'Protected logging must not obscure this plaintext event fixture.'
# Test fixture only: prepare explicit disabled values; production has no disable action.
$mutationStarted=$true
foreach($pair in @(@('ModuleLogging','EnableModuleLogging'),@('ScriptBlockLogging','EnableScriptBlockLogging'))){
$base=[Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,[Microsoft.Win32.RegistryView]::Registry64);$key=$null
try{$key=$base.CreateSubKey('SOFTWARE\Policies\Microsoft\Windows\PowerShell\'+$pair[0]);$key.SetValue($pair[1],0,[Microsoft.Win32.RegistryValueKind]::DWord);$key.Flush()}finally{if($key){$key.Dispose()};$base.Dispose()}
@@ -95,7 +96,7 @@ try{
Assert (@($again.Results|Where-Object Status -eq AlreadyCompliant).Count -eq 3 -and -not (Test-Path (Join-Path $root 'repeat-backup/before.jsonl'))) 'Second Configure makes no native writes.'
Assert ((ConvertTo-WelaPsLoggingKey (Get-WelaPsLoggingSnapshot)) -ceq (ConvertTo-WelaPsLoggingKey $configured)) 'Repeated configuration preserves complete observed state.'
$nonce='WELA_PS_LOG_'+[guid]::NewGuid().ToString('N');$workerPath=Join-Path $root ('worker-'+[guid]::NewGuid().ToString('N')+'.ps1')
$workerText="Microsoft.PowerShell.Utility\Write-Output -InputObject '$nonce'`r`n"
$workerText="Microsoft.PowerShell.Utility\Write-Output -InputObject '$nonce'`n"
[IO.File]::WriteAllText($workerPath,$workerText,[Text.UTF8Encoding]::new($false));Save 'worker-source.json' @{Path=$workerPath;Sha256=(Get-FileHash $workerPath -Algorithm SHA256).Hash;Text=$workerText;Nonce=$nonce}
$latest=Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' -MaxEvents 1 -ErrorAction Stop;try{$watermark=[long]$latest.RecordId}finally{$latest.Dispose()}
$process=Child 'event-worker' $configured.Engine.Path @('-NoLogo','-NoProfile','-NonInteractive','-File',$workerPath)
@@ -104,7 +105,8 @@ try{
do{
$candidates=@(ReadEvents $process.Pid $watermark);$selected=@{}
foreach($xml in $candidates){
$doc=[xml]$xml;$ns=[Xml.XmlNamespaceManager]::new($doc.NameTable);$ns.AddNamespace('e','http://schemas.microsoft.com/win/2004/08/events/event');$system=$doc.SelectSingleNode('/e:Event/e:System',$ns);$data=@{}
$settings=[Xml.XmlReaderSettings]::new();$settings.DtdProcessing=[Xml.DtdProcessing]::Prohibit;$settings.XmlResolver=$null;$settings.MaxCharactersInDocument=262144;$xmlReader=[Xml.XmlReader]::Create([IO.StringReader]::new($xml),$settings)
try{$doc=[Xml.XmlDocument]::new();$doc.XmlResolver=$null;$doc.PreserveWhitespace=$true;$doc.Load($xmlReader)}finally{$xmlReader.Dispose()};$ns=[Xml.XmlNamespaceManager]::new($doc.NameTable);$ns.AddNamespace('e','http://schemas.microsoft.com/win/2004/08/events/event');$system=$doc.SelectSingleNode('/e:Event/e:System',$ns);$data=@{}
foreach($node in $doc.SelectNodes('/e:Event/e:EventData/e:Data',$ns)){if($data.ContainsKey($node.GetAttribute('Name'))){throw 'Duplicate native event field.'};$data[$node.GetAttribute('Name')]=$node.InnerText}
$id=[int]$system.SelectSingleNode('e:EventID',$ns).InnerText
if($system.SelectSingleNode('e:Provider',$ns).GetAttribute('Name') -cne 'Microsoft-Windows-PowerShell' -or $system.SelectSingleNode('e:Provider',$ns).GetAttribute('Guid').Trim('{}') -ine 'a0c1853b-5c40-4b15-8766-3cf1c58f985a' -or [int]$system.SelectSingleNode('e:Execution',$ns).GetAttribute('ProcessID') -ne $process.Pid -or $system.SelectSingleNode('e:Channel',$ns).InnerText -cne 'Microsoft-Windows-PowerShell/Operational' -or $system.SelectSingleNode('e:Computer',$ns).InnerText -ine $env:COMPUTERNAME){continue}
@@ -127,12 +129,12 @@ try{
Assert ((ConvertTo-WelaPsLoggingKey (Get-WelaPsLoggingSnapshot)) -ceq (ConvertTo-WelaPsLoggingKey $wrong)) 'Refusal preserves the typed wrong value.'
}catch{$failure=$_.ToString();Save 'failure.json' @{Error=$failure;Stack=$_.ScriptStackTrace}}
finally{
if($original){
if($original -and $mutationStarted){
foreach($item in @(@('ScriptBlockLogging','EnableScriptBlockLogging'),@('ModuleLogging','EnableModuleLogging'),@('ModuleLogging\ModuleNames','Microsoft.PowerShell.Utility'))){try{RestoreValue $original.Machine $item[0] $item[1]}catch{$cleanupErrors+=$_.ToString()}}
try{RemoveCreatedKeys $original.Machine}catch{$cleanupErrors+=$_.ToString()}
try{$after=Get-WelaPsLoggingSnapshot;Save 'cleanup-after.json' $after;if((ConvertTo-WelaPsLoggingKey $after) -cne (ConvertTo-WelaPsLoggingKey $original)){$cleanupErrors+='Full policy/host/source/channel snapshot did not restore exactly.'};if($masks -and (Masks) -cne $masks){$cleanupErrors+='Audit masks changed.'}}catch{$cleanupErrors+=$_.ToString()}
}
Save 'cleanup.json' @{Status=$(if($cleanupErrors.Count){'Failed'}else{'Restored'});Errors=$cleanupErrors;OriginalCaptured=[bool]$original;All59MasksUnchanged=($masks -and (Masks) -ceq $masks)}
Save 'cleanup.json' @{Status=$(if($cleanupErrors.Count){'Failed'}else{'Restored'});Errors=$cleanupErrors;OriginalCaptured=[bool]$original;MutationStarted=$mutationStarted;All59MasksUnchanged=($masks -and (Masks) -ceq $masks)}
$artifacts=@(Get-ChildItem -LiteralPath $root -File -Recurse|ForEach-Object{[pscustomobject]@{Path=$_.FullName.Substring($root.Length+1);Sha256=(Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant()}})
Save 'manifest.json' @{Kind='WelaPowerShellLoggingNativeFixture';Head=$env:GITHUB_SHA;Engine=$PSVersionTable.PSVersion.ToString();Assertions=$script:count;Failure=$failure;CleanupErrors=$cleanupErrors;ReadyRuleCredit=0;Artifacts=$artifacts}
}
+2
View File
@@ -7,6 +7,8 @@
**改善:**
- Windows PowerShell 5.1 のモジュール/スクリプトブロック監査を明示的に選択する `powershell-logging` Audit/Plan/Configure を追加しました。モジュール名、型付き変更前記録、観測したポリシーの変化と再読取を確認し、呼出しログ・転写・Core設定・既存モジュール一覧を保持します。Server 2022/2025 の破棄可能なテストで正確な 4103/4104 と復元を要求し、PowerShell 7 のフォールバック、管理ポリシーの継続性、転送、Sigma 対応は別途検証します。(関連 #364, #366, #387) (@Shirofune-Security)
- Server2022/2025 と PowerShell5.1/7 で、Token Right Adjusted の正規GUIDに対する Security4703 の実機検証を追加しました。所有する子プロセスの既存権限を固定手順で無効化・復元し、候補となる2つの監査マスクを比較して、実イベント・実行条件・ハッシュとポリシー/トークンの復元を記録します。過去の候補は条件付きのまま維持し、製品用プローブ・全OS共通の対応関係・Sigma加点は追加しません。(関連 #380) (@Shirofune-Security)
- OneSettingsポリシーと明示的なPrivacyチャネル設定の公開CLIを実機検証します。Server 2022で実際の適用、型付き復元記録と再読取、冪等性、不正値の拒否を確認し、Server 2025の既存の拒否を維持します。両PowerShellで厳密な後処理を検証し、イベント生成やSigma対応は主張しません。通知コントロール省略時の引数渡しを修正し、既定のAuditは両項目を読み取り、項目未選択のConfigureは非ゼロで失敗します。(関連 #378) (@Shirofune-Security)
+2
View File
@@ -7,6 +7,8 @@
**Improvements:**
- Added explicit `powershell-logging` Audit/Plan/Configure for selected Windows PowerShell 5.1 module and script-block logging. Reviewed module names, typed original journals, full observed-policy drift guards and native readback preserve invocation/transcription/Core settings and other module entries. Disposable Server 2022/2025 tests require exact local 4103/4104 evidence and policy cleanup; PowerShell 7 fallback, managed-host persistence, forwarding and Sigma readiness remain separate. (Related #364, #366, #387) (@Shirofune-Security)
- Added disposable native Security4703 attribution for the canonical Token Right Adjusted GUID on Server2022/2025 and PowerShell5.1/7. A fixed owned-child privilege disable/restore compares the two historical audit-mask candidates, retains exact event/context/hash evidence and verifies policy/token cleanup. Historical candidates remain conditional; no production probe, universal mapping or Sigma credit. (Related #380) (@Shirofune-Security)
- Add native public OneSettings policy and explicit Privacy-channel configuration acceptance: actual apply, typed journals/readback, idempotence and invalid-value refusals on Server 2022; preserve the existing Server 2025 refusal. Both PowerShell engines verify exact fixture cleanup without event or Sigma claims. Fix omitted notification-control dispatch so default Audit reads both controls and Configure without a selection fails with a nonzero exit. (Related #378) (@Shirofune-Security)