From b9a6534424d436ef0e0f7095e8e3eb46cd06ea94 Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:18:35 +0900 Subject: [PATCH 1/3] Exercise public channel configuration on disposable Windows --- .../workflows/native-channel-configure.yml | 47 ++++++++++ .../NativeChannelConfigure.Windows.Tests.ps1 | 94 +++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 .github/workflows/native-channel-configure.yml create mode 100644 tests/NativeChannelConfigure.Windows.Tests.ps1 diff --git a/.github/workflows/native-channel-configure.yml b/.github/workflows/native-channel-configure.yml new file mode 100644 index 00000000..ae344b83 --- /dev/null +++ b/.github/workflows/native-channel-configure.yml @@ -0,0 +1,47 @@ +name: Native channel configuration acceptance +on: + push: + branches: ['**'] + pull_request: + workflow_dispatch: +permissions: + contents: read +jobs: + native-channel-configure: + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + os: [windows-2022, windows-2025] + engine: [powershell, pwsh] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + - name: Fixtures and public guards in Windows PowerShell5.1 + if: matrix.engine == 'powershell' + shell: powershell + run: | + ./tests/NativeChannelAccess.Tests.ps1 + ./tests/NativeChannelAccess.Windows.Tests.ps1 + - name: Native channel configuration in Windows PowerShell5.1 + if: matrix.engine == 'powershell' + shell: powershell + run: ./tests/NativeChannelConfigure.Windows.Tests.ps1 -AllowDisposableChannelWrite + - name: Fixtures and public guards in PowerShell7 + if: matrix.engine == 'pwsh' + shell: pwsh + run: | + ./tests/NativeChannelAccess.Tests.ps1 + ./tests/NativeChannelAccess.Windows.Tests.ps1 + - name: Native channel configuration in PowerShell7 + if: matrix.engine == 'pwsh' + shell: pwsh + run: ./tests/NativeChannelConfigure.Windows.Tests.ps1 -AllowDisposableChannelWrite + - name: Retain owned fixture evidence + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: native-channel-configure-${{ matrix.os }}-${{ matrix.engine }} + path: ${{ runner.temp }}/wela-channel-configure-*/ + if-no-files-found: warn + retention-days: 7 diff --git a/tests/NativeChannelConfigure.Windows.Tests.ps1 b/tests/NativeChannelConfigure.Windows.Tests.ps1 new file mode 100644 index 00000000..197e057c --- /dev/null +++ b/tests/NativeChannelConfigure.Windows.Tests.ps1 @@ -0,0 +1,94 @@ +param([switch]$AllowDisposableChannelWrite) +$ErrorActionPreference='Stop' +if([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or -not $AllowDisposableChannelWrite -or $env:GITHUB_ACTIONS -ne 'true' -or $env:RUNNER_ENVIRONMENT -ne 'github-hosted'){throw 'Explicit disposable hosted Windows opt-in required.'} +$repo=Split-Path $PSScriptRoot -Parent;$script:ScriptRoot=$repo +Import-Module "$repo/modules/AuditProfiles.psm1" -Force +Import-Module "$repo/modules/EventLogSettings.psm1" -Force +Import-Module "$repo/modules/NativeProviders.psm1" -Force +Import-Module "$repo/modules/NativeChannelAccess.psm1" -Force +. "$repo/scripts/Configuration.ps1" +. "$repo/scripts/NativeChannelConfiguration.ps1" +$count=0 +function Assert($Value,$Message){if(-not $Value){throw $Message};$script:count++} +function Binary($Value){$bytes=New-Object byte[] $Value.BinaryLength;$Value.GetBinaryForm($bytes,0);[Convert]::ToBase64String($bytes)} +function Read-Raw([string]$Name){ + $r=Invoke-WelaNative wevtutil.exe @('gl',$Name,'/f:xml') + $x=New-Object Xml.XmlDocument;$x.XmlResolver=$null;$x.LoadXml(($r.Output -join "`n"));return ,$x +} +function Guard-Raw($Xml){ + $x=$Xml.CloneNode($true);$x.DocumentElement.RemoveAttribute('enabled') + foreach($name in @('channelAccess','maxSize')){foreach($node in @($x.SelectNodes("//*[local-name()='$name']"))){$null=$node.ParentNode.RemoveChild($node)}} + return $x.OuterXml +} +$engine=(Get-Process -Id $PID).Path +$root=Join-Path $env:RUNNER_TEMP ('wela-channel-configure-'+[guid]::NewGuid().ToString('N'));$null=New-Item -ItemType Directory $root +$profile=Get-WelaNativeChannelProfile +$before=@{};$raw=@{};$policies=Get-WelaEffectiveAuditPolicy +foreach($control in $profile.controls){$name=$control.channel;$before[$name]=Get-WelaNativeChannel $name;if(Test-WelaNativeChannelSnapshot $before[$name]){$raw[$name]=Read-Raw $name}} +$before|ConvertTo-Json -Depth 14|Set-Content "$root/before.json" -Encoding UTF8 +$missing=@($before.Values|Where-Object State -eq 'Not installed').Count +$expected=if($missing){1}else{0} +$capi='Microsoft-Windows-CAPI2/Operational';$app='Microsoft-Windows-AppLocker/EXE and DLL' +$primary=$null +function Run-Cli([string]$Name,[string[]]$Options){ + $prior=$ErrorActionPreference;try{$ErrorActionPreference='Continue';$lines=@(&$engine -NoLogo -NoProfile -NonInteractive -File "$repo/WELA.ps1" channel-settings @Options -ResultsPath "$root/$Name.json" 2>&1);$code=$LASTEXITCODE}finally{$ErrorActionPreference=$prior} + $lines|Out-String|Set-Content "$root/$Name.txt" -Encoding UTF8 + Assert ($code -eq $expected) "Public $Name exit $code expected $expected : $($lines -join ' ')" + $report=Get-Content "$root/$Name.json" -Raw|ConvertFrom-Json + Assert ($report.ExitCode -eq $code -and $report.ForwardingReadiness -eq 'Not verified') 'Report agrees with native command exit and makes no forwarding claim' + return $report +} +try{ + Assert ($raw.ContainsKey($capi) -and $raw.ContainsKey($app)) 'CAPI2 and AppLocker channels are required for this disposable fixture' + Assert (@($before.Values|Where-Object { $_.State -notin @('Enabled','Disabled','Not installed') }).Count -eq 0) 'Unreadable original metadata refuses fixture writes' + # Fixture-only remove this group read grant so the public opt-in must append it. + $descriptor=[Security.AccessControl.RawSecurityDescriptor]::new($before[$capi].SecurityDescriptor) + for($i=$descriptor.DiscretionaryAcl.Count-1;$i -ge 0;$i--){ + $ace=$descriptor.DiscretionaryAcl[$i] + if($ace -is [Security.AccessControl.CommonAce] -and $ace.AceQualifier -eq 'AccessAllowed' -and $ace.SecurityIdentifier.Value -eq 'S-1-5-32-573' -and ($ace.AccessMask -band 1)){$descriptor.DiscretionaryAcl.RemoveAce($i)} + } + $withoutRead=$descriptor.GetSddlForm('All');$access=Get-WelaChannelAccessPlan $withoutRead + Assert ($access.State -eq 'GrantRequired') 'Prepared actual descriptor supports one lossless read-only grant' + $null=Invoke-WelaNative wevtutil.exe @('sl',$capi,'/e:false','/ms:1048576',('/ca:'+$withoutRead)) + # A large existing log exposed numeric narrowing in older PowerShell planners. + $null=Invoke-WelaNative wevtutil.exe @('sl',$app,'/ms:2147483648') + $prepared=@{};foreach($name in $raw.Keys){$prepared[$name]=Get-WelaNativeChannel $name} + $null=Run-Cli 'plan' @('-ChannelAction','Plan','-GrantEventLogReaders') + $null=Run-Cli 'dry-run' @('-ChannelAction','Configure','-GrantEventLogReaders','-DryRun','-Auto','-BackupPath',"$root/unused") + Assert (-not (Test-Path "$root/unused")) 'DryRun creates no journal' + foreach($name in $raw.Keys){Assert (Test-WelaNativeChannelSnapshotEqual $prepared[$name] (Get-WelaNativeChannel $name)) 'Plan and DryRun preserve actual native channel settings'} + $plain=Run-Cli 'configure' @('-ChannelAction','Configure','-Auto','-BackupPath',"$root/plain-journal") + $plainCapi=Get-WelaNativeChannel $capi + Assert ($plainCapi.IsEnabled -and $plainCapi.MaximumSizeInBytes -eq 102432768 -and (Test-WelaChannelDescriptorEqual $plainCapi.SecurityDescriptor $withoutRead)) 'Public Configure enables/resizes CAPI2 and preserves its ACL without explicit grant' + Assert ((Get-WelaNativeChannel $app).MaximumSizeInBytes -eq 2147483648) 'A larger existing 2GiB buffer is preserved' + $granted=Run-Cli 'grant' @('-ChannelAction','Configure','-GrantEventLogReaders','-Auto','-BackupPath',"$root/grant-journal") + $actual=Get-WelaNativeChannel $capi + Assert (Test-WelaChannelDescriptorEqual $actual.SecurityDescriptor $access.ProposedDescriptor) 'Native readback matches the precise planned grant descriptor' + $afterAcl=[Security.AccessControl.RawSecurityDescriptor]::new($actual.SecurityDescriptor) + Assert ($afterAcl.DiscretionaryAcl.Count -eq $descriptor.DiscretionaryAcl.Count+1) 'Exactly one native DACL ACE is added' + $newAce=$afterAcl.DiscretionaryAcl[$access.AddedAceIndex] + Assert ($newAce.SecurityIdentifier.Value -eq 'S-1-5-32-573' -and $newAce.AccessMask -eq 1 -and $newAce.AceFlags -eq 0 -and -not $newAce.IsCallback) 'Added grant is unconditional read only' + $afterAcl.DiscretionaryAcl.RemoveAce($access.AddedAceIndex) + Assert ((Binary $afterAcl) -ceq (Binary $descriptor)) 'Owner/group/SACL/flags and every original ACE byte/order survive native application' + $again=Run-Cli 'idempotent' @('-ChannelAction','Configure','-GrantEventLogReaders','-Auto','-BackupPath',"$root/repeat-journal") + Assert (@($again.Results|Where-Object Status -eq 'Applied').Count -eq 0) 'Repeated native configuration does not apply another mutation' + Assert (-not (Test-Path "$root/repeat-journal/before.jsonl")) 'Idempotent invocation journals no write' + $journal=@(Get-Content "$root/grant-journal/before.jsonl"|ForEach-Object {$_|ConvertFrom-Json}) + Assert ($journal.Count -eq 1 -and $journal[0].Target.Channel -eq $capi -and (Test-WelaChannelDescriptorEqual $journal[0].Before.SecurityDescriptor $withoutRead)) 'Durable actual pre-grant journal preserves the original descriptor' + foreach($name in $raw.Keys){Assert ((Guard-Raw (Read-Raw $name)) -ceq (Guard-Raw $raw[$name])) 'Native channel path/retention/provider metadata remain unchanged'} + Write-Host "PASS: $count native public channel configuration assertions." +}catch{$primary=$_} +finally{ + $errors=@() + foreach($name in $raw.Keys){ + try{$s=$before[$name];$null=Invoke-WelaNative wevtutil.exe @('sl',$name,('/e:'+$s.IsEnabled.ToString().ToLowerInvariant()),('/ms:'+$s.MaximumSizeInBytes),('/ca:'+$s.SecurityDescriptor)) + if(-not (Test-WelaNativeChannelSnapshotEqual $s (Get-WelaNativeChannel $name)) -or (Read-Raw $name).OuterXml -cne $raw[$name].OuterXml){throw 'Original native channel configuration differs after cleanup'} + }catch{$errors+="$name : $($_.Exception.Message)"} + } + $now=Get-WelaEffectiveAuditPolicy;foreach($guid in $policies.Keys){if($policies[$guid] -ne $now[$guid]){$errors+='Audit mask changed: '+$guid}} + $after=@{};foreach($name in $before.Keys){$after[$name]=Get-WelaNativeChannel $name} + [ordered]@{CleanupVerified=($errors.Count -eq 0);Before=$before;After=$after;AuditMasksCompared=$policies.Count;Diagnostic=$errors;Assertions=$count;PrimaryError=[string]$primary}|ConvertTo-Json -Depth 14|Set-Content "$root/cleanup.json" -Encoding UTF8 + if($errors.Count){throw "Cleanup failed: $($errors -join '; '); primary: $primary"} + Write-Host 'Exact original channel metadata and all audit masks verified after cleanup; existing event records/retention duration not claimed.' +} +if($primary){throw $primary};$global:LASTEXITCODE=0 From cba6162cb537da4abc8ece574c6b33223c9a6289 Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:22:20 +0900 Subject: [PATCH 2/3] Compare configured channel XML with explicit enabled-field allowance --- tests/NativeChannelConfigure.Windows.Tests.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/NativeChannelConfigure.Windows.Tests.ps1 b/tests/NativeChannelConfigure.Windows.Tests.ps1 index 197e057c..18db6ae3 100644 --- a/tests/NativeChannelConfigure.Windows.Tests.ps1 +++ b/tests/NativeChannelConfigure.Windows.Tests.ps1 @@ -17,7 +17,7 @@ function Read-Raw([string]$Name){ } function Guard-Raw($Xml){ $x=$Xml.CloneNode($true);$x.DocumentElement.RemoveAttribute('enabled') - foreach($name in @('channelAccess','maxSize')){foreach($node in @($x.SelectNodes("//*[local-name()='$name']"))){$null=$node.ParentNode.RemoveChild($node)}} + foreach($name in @('enabled','channelAccess','maxSize')){foreach($node in @($x.SelectNodes("//*[local-name()='$name']"))){$null=$node.ParentNode.RemoveChild($node)}} return $x.OuterXml } $engine=(Get-Process -Id $PID).Path @@ -25,6 +25,7 @@ $root=Join-Path $env:RUNNER_TEMP ('wela-channel-configure-'+[guid]::NewGuid().To $profile=Get-WelaNativeChannelProfile $before=@{};$raw=@{};$policies=Get-WelaEffectiveAuditPolicy foreach($control in $profile.controls){$name=$control.channel;$before[$name]=Get-WelaNativeChannel $name;if(Test-WelaNativeChannelSnapshot $before[$name]){$raw[$name]=Read-Raw $name}} +$rawEvidence=@{};foreach($name in $raw.Keys){$rawEvidence[$name]=$raw[$name].OuterXml};$rawEvidence|ConvertTo-Json -Depth 4|Set-Content "$root/raw-before.json" -Encoding UTF8 $before|ConvertTo-Json -Depth 14|Set-Content "$root/before.json" -Encoding UTF8 $missing=@($before.Values|Where-Object State -eq 'Not installed').Count $expected=if($missing){1}else{0} @@ -75,6 +76,7 @@ try{ Assert (-not (Test-Path "$root/repeat-journal/before.jsonl")) 'Idempotent invocation journals no write' $journal=@(Get-Content "$root/grant-journal/before.jsonl"|ForEach-Object {$_|ConvertFrom-Json}) Assert ($journal.Count -eq 1 -and $journal[0].Target.Channel -eq $capi -and (Test-WelaChannelDescriptorEqual $journal[0].Before.SecurityDescriptor $withoutRead)) 'Durable actual pre-grant journal preserves the original descriptor' + $rawAfter=@{};foreach($name in $raw.Keys){$rawAfter[$name]=(Read-Raw $name).OuterXml};$rawAfter|ConvertTo-Json -Depth 4|Set-Content "$root/raw-configured.json" -Encoding UTF8 foreach($name in $raw.Keys){Assert ((Guard-Raw (Read-Raw $name)) -ceq (Guard-Raw $raw[$name])) 'Native channel path/retention/provider metadata remain unchanged'} Write-Host "PASS: $count native public channel configuration assertions." }catch{$primary=$_} From f05f852286caa5d1e0a01ab4aa2219ea290d627b Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:26:15 +0900 Subject: [PATCH 3/3] Retain native channel preservation evidence and document acceptance limits --- CHANGELOG-Japanese.md | 2 ++ CHANGELOG.md | 2 ++ docs/native-channel-access.md | 6 ++++-- tests/NativeChannelConfigure.Windows.Tests.ps1 | 9 ++++++--- website/docs/resources/changelog.ja.md | 2 ++ website/docs/resources/changelog.md | 2 ++ 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 4c13f51b..f1c90a99 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -4,6 +4,8 @@ **改善:** +- Windows PowerShell 5.1 と PowerShell 7、使い捨ての Server 2022/2025 で標準チャネル設定の公開CLIを検証するテストを追加しました。有効化・サイズ・CAPI2読み取り専用権限の適用、既存記述子と大きいバッファーの保持、変更前記録、DryRun、再実行時の無変更、元設定への復元を確認し、ハッシュ付きの証拠を保存します。転送・保存期間・Sigmaの検証は別途必要です。 (@Shirofune-Security) + - `failed-logon-probe` を追加しました。存在しないことを確認したランダムなローカル SAM アカウントに対し、固定のネイティブログオン種別・プロバイダーで一度だけ認証を試行し、正確な時刻・プロセス・アカウント情報で Security4625 を照合します。監査設定を変更せず、保護された証跡を保存します。実際の資格情報、ドメインコントローラー、リモート認証、Sigma 対応率の加算は対象外です。使い捨て Windows 環境で公開コマンドと設定復元を検証します。(@Shirofune-Security) - `wec-ingress` の Plan/Apply を追加し、明示した IPv4 範囲から Domain プロファイルの TCP5985 を許可する新規ルールを作成します。実ホスト・ログオン・プロファイル・コードと計画ハッシュ、変更前の永続記録、両ストアとフィルターの読戻しで変更や既存名を拒否します。既存の収集サーバー前提条件も、範囲を広げずに同等のIPv4ネットマスク表記・IPv6表記を照合します。使い捨て Windows テストは作成・名前衝突・再実行拒否・既存前提条件との連携・削除を検証し、リスナー・配送・Sigma の証明は加算しません。 (@Shirofune-Security) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31bfbecd..3ce8d30a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ **Improvements:** +- Added disposable Server 2022/2025 validation of public native channel configuration under Windows PowerShell 5.1 and PowerShell 7. Tests apply enable/size controls and the explicit CAPI2 read-only grant, verify descriptor preservation, journals, larger buffers, DryRun and idempotence, and retain hashed native evidence with exact fixture cleanup. Forwarding, retention-duration and Sigma validation remain separate. (@Shirofune-Security) + - Added opt-in `failed-logon-probe` for one generated, confirmed nonexistent local SAM account attempt with fixed native logon type/provider, precise worker timing and exact Security4625 correlation. Protected receipts preserve raw evidence and unchanged audit/channel/token context; real credentials, domain controllers, remote authentication and Sigma credit are excluded. Disposable Windows tests cover native public runs and exact fixture cleanup. (@Shirofune-Security) - Added explicit `wec-ingress` Plan/Apply for one new, narrowly scoped Domain TCP5985 collector firewall rule. Actual host/logon/profile/source guards, reviewed hashes, flushed pending evidence and both-store/filter readback refuse drift and existing names; partial creation remains explicit. The existing collector prerequisite recognizes equivalent native dotted netmasks and IPv6 spellings without broadening accepted scopes. Disposable native tests cover creation, collision, replay, collector integration and cleanup without listener, delivery or Sigma claims. (@Shirofune-Security) diff --git a/docs/native-channel-access.md b/docs/native-channel-access.md index a15b53cf..877b29ff 100644 --- a/docs/native-channel-access.md +++ b/docs/native-channel-access.md @@ -36,9 +36,11 @@ Recovery is manual: review each journal `Before` against the current settings, i The checked-in inventory maps source query IDs to 12 baseline channels and 8 suspect channels (18 unique combined). Baseline queries 12 (EMET) and 39 (Sysmon) are explicitly excluded. Native-only scope excludes external agents; channel settings do not create subscriptions, add service identities to groups or configure WinRM/collectors. The Microsoft [source prerequisite guidance and sample queries](https://learn.microsoft.com/en-us/windows/security/operating-system-security/device-management/use-windows-event-forwarding-to-assist-in-intrusion-detection) remain a starting point for reviewing role applicability. The report always lists token/group membership, producer configuration, representative event generation and forwarding/ingestion as unverified; required disabled or missing channels remain visible. Other inventoried channels are not automatically enabled. -Safe tests exercise the actual command/JSON/runner with mocked Windows setters. Windows PowerShell 5.1 and PowerShell 7 CI additionally exercise real descriptor serialization and read-only CLI inspection. Release packaging already includes the whole `config`, `modules` and `scripts` directories. +Safe tests exercise the actual command/JSON/runner with mocked Windows setters. Windows PowerShell 5.1 and PowerShell 7 CI also exercise real descriptor serialization and read-only CLI inspection. A separate four-way disposable Server 2022/2025 suite exercises the public Plan, Configure with DryRun, Configure without a reader grant, explicit read-only grant, and repeated idempotent configuration. It verifies exact native descriptor bytes, recovery journal contents, preservation of an existing 2 GiB buffer, all other channel XML fields, and restoration of the original channel settings and all 59 audit masks. Hashed artifacts retain original/configured XML, reports, journal and cleanup evidence. -**Isolated Windows acceptance evidence is still pending; related to issue #367, not sufficient to close it.** On patched Windows 11, member server, DC and ADCS snapshots where the channels exist: +That fixture changes only the three declared channels on explicitly opted-in GitHub-hosted disposable VMs. Restoring the original smaller sizes can discard events generated during the test; it does not restore event records or prove retention duration. It never supplies production forwarding-token access, event generation, collector arrival, policy-refresh persistence or Sigma evidence. Do not run the mutating fixture on ordinary machines. Release packaging already includes the whole `config`, `modules` and `scripts` directories. + +**Broader Windows acceptance remains pending; related to issue #367, not sufficient to close it.** Hosted server configuration checks do not cover Windows 11 or domain-specific access and forwarding behavior. On patched Windows 11, member server, DC and ADCS snapshots where the channels exist: 1. Save the plan, channel metadata, descriptor and policy context. Review capacity and the intended forwarding identity. Capture the actual identity/token memberships separately. 2. Apply the opt-in profile, retain the journal/results, then independently read enablement, exact bytes and full SDDL. Compare all original ACEs plus owner/group/SACL/flags and repeat after policy refresh. diff --git a/tests/NativeChannelConfigure.Windows.Tests.ps1 b/tests/NativeChannelConfigure.Windows.Tests.ps1 index 18db6ae3..870b60e0 100644 --- a/tests/NativeChannelConfigure.Windows.Tests.ps1 +++ b/tests/NativeChannelConfigure.Windows.Tests.ps1 @@ -16,8 +16,8 @@ function Read-Raw([string]$Name){ $x=New-Object Xml.XmlDocument;$x.XmlResolver=$null;$x.LoadXml(($r.Output -join "`n"));return ,$x } function Guard-Raw($Xml){ - $x=$Xml.CloneNode($true);$x.DocumentElement.RemoveAttribute('enabled') - foreach($name in @('enabled','channelAccess','maxSize')){foreach($node in @($x.SelectNodes("//*[local-name()='$name']"))){$null=$node.ParentNode.RemoveChild($node)}} + $x=$Xml.CloneNode($true);$x.DocumentElement.RemoveAttribute('enabled');$x.DocumentElement.RemoveAttribute('channelAccess') + foreach($node in @($x.SelectNodes("/*/*[local-name()='logging']/*[local-name()='maxSize']"))){$null=$node.ParentNode.RemoveChild($node)} return $x.OuterXml } $engine=(Get-Process -Id $PID).Path @@ -51,7 +51,7 @@ try{ $withoutRead=$descriptor.GetSddlForm('All');$access=Get-WelaChannelAccessPlan $withoutRead Assert ($access.State -eq 'GrantRequired') 'Prepared actual descriptor supports one lossless read-only grant' $null=Invoke-WelaNative wevtutil.exe @('sl',$capi,'/e:false','/ms:1048576',('/ca:'+$withoutRead)) - # A large existing log exposed numeric narrowing in older PowerShell planners. + # Existing sizes above the signed 32-bit range must not be narrowed. $null=Invoke-WelaNative wevtutil.exe @('sl',$app,'/ms:2147483648') $prepared=@{};foreach($name in $raw.Keys){$prepared[$name]=Get-WelaNativeChannel $name} $null=Run-Cli 'plan' @('-ChannelAction','Plan','-GrantEventLogReaders') @@ -93,4 +93,7 @@ finally{ if($errors.Count){throw "Cleanup failed: $($errors -join '; '); primary: $primary"} Write-Host 'Exact original channel metadata and all audit masks verified after cleanup; existing event records/retention duration not claimed.' } +$artifacts=@(Get-ChildItem -LiteralPath $root -File -Recurse|ForEach-Object {[ordered]@{Path=$_.FullName.Substring($root.Length+1);Sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash}}) +$sources=@('WELA.ps1','scripts/Configuration.ps1','scripts/NativeChannelConfiguration.ps1','modules/NativeChannelAccess.psm1','modules/NativeProviders.psm1','modules/EventLogSettings.psm1','tests/NativeChannelConfigure.Windows.Tests.ps1')|ForEach-Object {[ordered]@{Path=$_;Sha256=(Get-FileHash -LiteralPath (Join-Path $repo $_) -Algorithm SHA256).Hash}} +[ordered]@{Status=$(if($primary){'Failed'}else{'Passed'});Commit=$env:GITHUB_SHA;Engine=$PSVersionTable.PSVersion.ToString();Assertions=$count;Artifacts=$artifacts;Sources=@($sources);EventGenerationVerified=$false;ForwardingVerified=$false;ReadyRuleCredit=0}|ConvertTo-Json -Depth 8|Set-Content "$root/manifest.json" -Encoding UTF8 if($primary){throw $primary};$global:LASTEXITCODE=0 diff --git a/website/docs/resources/changelog.ja.md b/website/docs/resources/changelog.ja.md index de508ee6..e28b4916 100644 --- a/website/docs/resources/changelog.ja.md +++ b/website/docs/resources/changelog.ja.md @@ -7,6 +7,8 @@ **改善:** +- Windows PowerShell 5.1 と PowerShell 7、使い捨ての Server 2022/2025 で標準チャネル設定の公開CLIを検証するテストを追加しました。有効化・サイズ・CAPI2読み取り専用権限の適用、既存記述子と大きいバッファーの保持、変更前記録、DryRun、再実行時の無変更、元設定への復元を確認し、ハッシュ付きの証拠を保存します。転送・保存期間・Sigmaの検証は別途必要です。 (@Shirofune-Security) + - `failed-logon-probe` を追加しました。存在しないことを確認したランダムなローカル SAM アカウントに対し、固定のネイティブログオン種別・プロバイダーで一度だけ認証を試行し、正確な時刻・プロセス・アカウント情報で Security4625 を照合します。監査設定を変更せず、保護された証跡を保存します。実際の資格情報、ドメインコントローラー、リモート認証、Sigma 対応率の加算は対象外です。使い捨て Windows 環境で公開コマンドと設定復元を検証します。(@Shirofune-Security) - `wec-ingress` の Plan/Apply を追加し、明示した IPv4 範囲から Domain プロファイルの TCP5985 を許可する新規ルールを作成します。実ホスト・ログオン・プロファイル・コードと計画ハッシュ、変更前の永続記録、両ストアとフィルターの読戻しで変更や既存名を拒否します。既存の収集サーバー前提条件も、範囲を広げずに同等のIPv4ネットマスク表記・IPv6表記を照合します。使い捨て Windows テストは作成・名前衝突・再実行拒否・既存前提条件との連携・削除を検証し、リスナー・配送・Sigma の証明は加算しません。 (@Shirofune-Security) diff --git a/website/docs/resources/changelog.md b/website/docs/resources/changelog.md index 4ca9dbe7..54027f34 100644 --- a/website/docs/resources/changelog.md +++ b/website/docs/resources/changelog.md @@ -7,6 +7,8 @@ **Improvements:** +- Added disposable Server 2022/2025 validation of public native channel configuration under Windows PowerShell 5.1 and PowerShell 7. Tests apply enable/size controls and the explicit CAPI2 read-only grant, verify descriptor preservation, journals, larger buffers, DryRun and idempotence, and retain hashed native evidence with exact fixture cleanup. Forwarding, retention-duration and Sigma validation remain separate. (@Shirofune-Security) + - Added opt-in `failed-logon-probe` for one generated, confirmed nonexistent local SAM account attempt with fixed native logon type/provider, precise worker timing and exact Security4625 correlation. Protected receipts preserve raw evidence and unchanged audit/channel/token context; real credentials, domain controllers, remote authentication and Sigma credit are excluded. Disposable Windows tests cover native public runs and exact fixture cleanup. (@Shirofune-Security) - Added explicit `wec-ingress` Plan/Apply for one new, narrowly scoped Domain TCP5985 collector firewall rule. Actual host/logon/profile/source guards, reviewed hashes, flushed pending evidence and both-store/filter readback refuse drift and existing names; partial creation remains explicit. The existing collector prerequisite recognizes equivalent native dotted netmasks and IPv6 spellings without broadening accepted scopes. Disposable native tests cover creation, collision, replay, collector integration and cleanup without listener, delivery or Sigma claims. (@Shirofune-Security)