From 70e6207a84c1b9e52cb6688dddb5d5b6e4c16c96 Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Sat, 19 Sep 2026 04:37:02 +0900 Subject: [PATCH] Match rule channel patterns consistently against concrete sources --- CHANGELOG-Japanese.md | 2 +- CHANGELOG.md | 2 +- WELA.ps1 | 4 +++- config/baselines.json | 4 ++-- docs/native-provider-assessment.md | 2 +- tests/NativeProviders.Tests.ps1 | 28 ++++++++++++++++++++++---- website/docs/resources/changelog.ja.md | 2 +- website/docs/resources/changelog.md | 2 +- 8 files changed, 34 insertions(+), 12 deletions(-) diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index ecbe5083..50e8c8f6 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -14,7 +14,7 @@ **バグ修正:** -- Windows標準チャネルを一律に`Enabled`と表示していた処理を、実際の有効状態・ログモード・ACLの読み取りとプロバイダーの前提条件の確認に置き換えた。AppLocker、NTLM、Defenderなどのイベント生成は検証されるまで条件付きとし、チャネルが有効なだけではルールを利用可能と判定しない。アクセス拒否・未登録の状態とソースの確認結果を保持するJSON/HTML監査レポート出力を追加した。 (#395) (@Shirofune-Security) +- Windows標準チャネルを一律に`Enabled`と表示していた処理を、実際の有効状態・ログモード・ACLの読み取りとプロバイダーの前提条件の確認に置き換えた。AppLocker、NTLM、Defenderなどのイベント生成は検証されるまで条件付きとし、チャネルが有効なだけではルールを利用可能と判定しない。アクセス拒否・未登録の状態とソースの確認結果を保持するJSON/HTML監査レポート出力を追加した。ルールのチャネルパターンを具体的なカタログのチャネル名と照合する処理を、ルールの絞り込みとソースの対応付けで統一した。 (#395) (@Shirofune-Security) - `configure`が既定で送信NTLM認証をブロックしていた問題を修正した。未設定またはAllow allの場合はAudit all (`RestrictSendingNTLMTraffic=1`)を設定し、既存のDeny all (`2`)や不明な値・型は維持する。拒否設定を明示的に監査へ変更するには`-OutgoingNtlmMode Audit`、ブロックを有効にするには`Deny`を指定する。書き込み前にポリシーを再確認し、変更後の値の検証、失敗の報告、確認できたポリシー状態と取得可能な最終適用RSoP情報の表示に対応した。 (#388) (@Shirofune-Security) - `audit-settings`でホストの役割に適用されない監査ポリシーを`Not applicable`と表示し、カテゴリの有効・無効の集計から除外するようにした。NTLMポリシーの値は、DWORD型で保存されている場合にのみ有効な設定値として解釈・検証する。 (#392) (@Shirofune-Security) - 設定時に外部コマンドの終了コードと変更後の設定値を確認し、処理の終了前にも再確認するようにした。書き込み失敗、設定の未反映、CAサービスの再起動失敗、最終確認時の設定の不一致を明示的に報告し、一律に成功とせず、0以外の終了コードを返すようにした。 (#392) (@Shirofune-Security) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9396bd16..3cbaa87c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ **Bug Fixes:** -- Replaced static native-channel `Enabled` claims with actual channel state, mode and ACL reads plus provider prerequisite observations. AppLocker, NTLM, Defender and other native sources remain conditional until event generation is validated; channel enablement alone grants no usable-rule credit. Added JSON/HTML audit assessment exports preserving denied/absent states and source evidence. (#395) (@Shirofune-Security) +- Replaced static native-channel `Enabled` claims with actual channel state, mode and ACL reads plus provider prerequisite observations. AppLocker, NTLM, Defender and other native sources remain conditional until event generation is validated; channel enablement alone grants no usable-rule credit. Added JSON/HTML audit assessment exports preserving denied/absent states and source evidence. Rule channel patterns now match concrete catalog channels consistently during filtering and source mapping. (#395) (@Shirofune-Security) - Fixed `configure` enabling outgoing NTLM blocking by default. It now sets Audit all (`RestrictSendingNTLMTraffic=1`) for unset or Allow policies while preserving existing Deny all (`2`) and unknown values/types. Use `-OutgoingNtlmMode Audit` to explicitly replace a deny policy, or `Deny` to enable blocking. Configuration rechecks policy before writing, verifies changes, reports failures, and displays the observed policy and available last-applied RSoP information. (#388) (@Shirofune-Security) - `audit-settings` now reports role-inapplicable audit policies as `Not applicable` and excludes them from category enablement totals. NTLM policy values are interpreted and verified only when stored as DWORDs. (#392) (@Shirofune-Security) - Configuration now checks native command exit codes, verifies settings after applying changes, and checks them again before finishing. Failed writes, ineffective changes, CA restart failures and settings that no longer match at the final check produce explicit results and a nonzero exit code instead of unconditional success. (#392) (@Shirofune-Security) diff --git a/WELA.ps1 b/WELA.ps1 index 515a6a07..84406824 100644 --- a/WELA.ps1 +++ b/WELA.ps1 @@ -172,7 +172,9 @@ function RuleFilter { $hasCriteria = $true if (-not ($rule.channel | Where-Object { $ruleChannel = $_ - $category_channels | Where-Object { $ruleChannel -like $_ } + # Catalog channels are concrete names/aliases; rule channels are patterns, + # matching the convention used by Get-WelaNativeSources. + $category_channels | Where-Object { $_ -like $ruleChannel } })) { return $false } diff --git a/config/baselines.json b/config/baselines.json index 5df74d22..368ad7db 100644 --- a/config/baselines.json +++ b/config/baselines.json @@ -854,7 +854,7 @@ "type": "filter", "eventIds": [], "channels": [ - "Microsoft-Windows-Security-Mitigations*" + "Microsoft-Windows-Security-Mitigations/KernelMode" ], "guid": "" }, @@ -874,7 +874,7 @@ "type": "filter", "eventIds": [], "channels": [ - "Microsoft-Windows-Security-Mitigations*" + "Microsoft-Windows-Security-Mitigations/UserMode" ], "guid": "" }, diff --git a/docs/native-provider-assessment.md b/docs/native-provider-assessment.md index b1a15276..bfe01d05 100644 --- a/docs/native-provider-assessment.md +++ b/docs/native-provider-assessment.md @@ -21,7 +21,7 @@ The JSON and self-contained HTML reports preserve channel state, log mode, secur | Provider `Unknown` | A required provider/status query was unavailable or failed. The channel observation is still retained separately. | | Domain NTLM `Not applicable` | The host is confirmed not to be a domain controller. An unreadable role remains unknown. | -Each exact channel is read independently. An enabled AppLocker EXE/DLL channel does not hide a disabled MSI/Script channel. PowerShell's `pwsh` metadata alias for classic event 400 maps to `Windows PowerShell`; it does not prove PowerShell 7 logging. The Security-Mitigations catalog now reads separate KernelMode and UserMode channels, preserving the existing wildcard rule selector without issuing broad wildcard channel queries. +Each exact channel is read independently. An enabled AppLocker EXE/DLL channel does not hide a disabled MSI/Script channel. PowerShell's `pwsh` metadata alias for classic event 400 maps to `Windows PowerShell`; it does not prove PowerShell 7 logging. Catalog selectors use concrete channel names or metadata aliases. In both filtering and source mapping, rule channels are patterns matched against these concrete names. The Security-Mitigations catalog reads separate KernelMode and UserMode channels, mapping the existing wildcard rules to both without issuing broad wildcard channel queries or mapping a channel-specific rule to its sibling. ## Provider evidence and coverage limits diff --git a/tests/NativeProviders.Tests.ps1 b/tests/NativeProviders.Tests.ps1 index e0b340a4..69b4750a 100644 --- a/tests/NativeProviders.Tests.ps1 +++ b/tests/NativeProviders.Tests.ps1 @@ -83,6 +83,11 @@ $script:ScriptRoot = Join-Path ([IO.Path]::GetTempPath()) ('wela-native-provider $null = New-Item -ItemType Directory -Path $script:ScriptRoot $script:SecurityRulesPath = Join-Path $script:ScriptRoot 'rules.json' try { + # Exercise the real corpus's rule-side wildcard against the exact catalog + # selectors, through the public CSV/JSON/HTML assessment path. + $wildcardRules = @(Get-Content -LiteralPath (Join-Path $PSScriptRoot '../config/security_rules.json') -Raw | + ConvertFrom-Json | Where-Object { $_.channel -contains 'Microsoft-Windows-Security-Mitigations*' }) + Assert-Equal ($wildcardRules.Count -gt 0) $true 'Real Security-Mitigations wildcard rules are present' $fixtures = @( @{ id = 'application'; channel = @('Application') } @{ id = 'print'; channel = @('Microsoft-Windows-PrintService/Operational') } @@ -97,6 +102,9 @@ try { @{ id = 'mitigation-user'; channel = @('Microsoft-Windows-Security-Mitigations/UserMode') } @{ id = 'security'; channel = @('sec'); subcategory_guids = @('0CCE9215-69AE-11D9-BED3-505054503030') } ) + foreach ($rule in $wildcardRules) { + $fixtures += @{ id = $rule.id; channel = @($rule.channel); event_ids = @($rule.event_ids) } + } foreach ($rule in $fixtures) { $rule.level = 'medium'; $rule.title = $rule.id if (-not $rule.ContainsKey('event_ids')) { $rule.event_ids = @() } @@ -154,13 +162,25 @@ try { $classic = $sources | Where-Object { $_.Channel.Name -eq 'Windows PowerShell' } Assert-Equal ($classic.MappedRuleIds -contains 'classic') $true 'Classic PowerShell alias maps to its real channel' $kernel = $sources | Where-Object { $_.Channel.Name -eq 'Microsoft-Windows-Security-Mitigations/KernelMode' } - Assert-Equal ($kernel.MappedRuleIds -contains 'mitigation-kernel') $true 'Exact mitigation channel matches existing wildcard catalog selector' + $user = $sources | Where-Object { $_.Channel.Name -eq 'Microsoft-Windows-Security-Mitigations/UserMode' } + Assert-Equal ($kernel.MappedRuleIds -contains 'mitigation-kernel') $true 'Exact mitigation channel matches its concrete catalog selector' Assert-Equal ($kernel.MappedRuleIds -contains 'mitigation-user') $false 'Kernel mitigation source does not inherit user-mode rules' - Assert-Equal (($rows | Where-Object { $_.NativeSources.Channel.Name -contains 'Microsoft-Windows-Security-Mitigations/KernelMode' }).RuleCount) 1 'Category count uses actual source mappings, not a sibling wildcard match' - Assert-Equal $report.Coverage.TotalRules 12 'Whole rule corpus remains in coverage denominator' + Assert-Equal ($user.MappedRuleIds -contains 'mitigation-user') $true 'User mitigation source retains its exact rule' + Assert-Equal ($user.MappedRuleIds -contains 'mitigation-kernel') $false 'User mitigation source does not inherit kernel rules' + foreach ($rule in $wildcardRules) { + Assert-Equal ($kernel.MappedRuleIds -contains $rule.id) $true 'Rule-side wildcard maps to the concrete KernelMode source' + Assert-Equal ($user.MappedRuleIds -contains $rule.id) $true 'Rule-side wildcard maps to the concrete UserMode source' + Assert-Equal (RuleFilter $rule @('999') @($kernel.Channel.Name) '') $false 'A wildcard channel match still requires the selected event ID' + Assert-Equal (RuleFilter $rule @($rule.event_ids[0]) @($kernel.Channel.Name) '') $true 'Matching channel and event ID pass together' + } + foreach ($source in @($kernel, $user)) { + Assert-Equal (($rows | Where-Object { $_.NativeSources.Channel.Name -contains $source.Channel.Name }).RuleCount) (1 + $wildcardRules.Count) 'Each category includes exact and wildcard rules without unrelated sibling rules' + } + Assert-Equal @(& $module { $script:channelReads | Where-Object { $_ -match '[*?]' } }).Count 0 'Wildcard rules never trigger wildcard native channel reads' + Assert-Equal $report.Coverage.TotalRules $fixtures.Count 'Rules mapped to both native sources appear once in the full denominator' Assert-Equal $report.Coverage.UsableRules 1 'Only the independently enabled Security source receives usable credit' Assert-Equal @(Import-Csv (Join-Path $script:ScriptRoot 'UsableRules.csv')).Count 1 'Usable CSV matches conservative JSON coverage' - Assert-Equal @(Import-Csv (Join-Path $script:ScriptRoot 'UnusableRules.csv')).Count 11 'Unconfirmed native rules are retained in CSV' + Assert-Equal @(Import-Csv (Join-Path $script:ScriptRoot 'UnusableRules.csv')).Count ($fixtures.Count - 1) 'Unconfirmed native rules are retained once in CSV' Assert-Equal @($script:heatmapRules | Where-Object { $_.id -ne 'security' -and ($_.applicable -or $_.ideal) }).Count 0 'No current or ideal native provider uplift is fabricated' $html = Get-Content -LiteralPath $htmlPath -Raw Assert-Equal ($html -match 'Not installed') $true 'HTML retains absent-feature state' diff --git a/website/docs/resources/changelog.ja.md b/website/docs/resources/changelog.ja.md index a09ef45e..1a070c1b 100644 --- a/website/docs/resources/changelog.ja.md +++ b/website/docs/resources/changelog.ja.md @@ -17,7 +17,7 @@ **バグ修正:** -- Windows標準チャネルを一律に`Enabled`と表示していた処理を、実際の有効状態・ログモード・ACLの読み取りとプロバイダーの前提条件の確認に置き換えた。AppLocker、NTLM、Defenderなどのイベント生成は検証されるまで条件付きとし、チャネルが有効なだけではルールを利用可能と判定しない。アクセス拒否・未登録の状態とソースの確認結果を保持するJSON/HTML監査レポート出力を追加した。 (#395) (@Shirofune-Security) +- Windows標準チャネルを一律に`Enabled`と表示していた処理を、実際の有効状態・ログモード・ACLの読み取りとプロバイダーの前提条件の確認に置き換えた。AppLocker、NTLM、Defenderなどのイベント生成は検証されるまで条件付きとし、チャネルが有効なだけではルールを利用可能と判定しない。アクセス拒否・未登録の状態とソースの確認結果を保持するJSON/HTML監査レポート出力を追加した。ルールのチャネルパターンを具体的なカタログのチャネル名と照合する処理を、ルールの絞り込みとソースの対応付けで統一した。 (#395) (@Shirofune-Security) - `configure`が既定で送信NTLM認証をブロックしていた問題を修正した。未設定またはAllow allの場合はAudit all (`RestrictSendingNTLMTraffic=1`)を設定し、既存のDeny all (`2`)や不明な値・型は維持する。拒否設定を明示的に監査へ変更するには`-OutgoingNtlmMode Audit`、ブロックを有効にするには`Deny`を指定する。書き込み前にポリシーを再確認し、変更後の値の検証、失敗の報告、確認できたポリシー状態と取得可能な最終適用RSoP情報の表示に対応した。 (#388) (@Shirofune-Security) - `audit-settings`でホストの役割に適用されない監査ポリシーを`Not applicable`と表示し、カテゴリの有効・無効の集計から除外するようにした。NTLMポリシーの値は、DWORD型で保存されている場合にのみ有効な設定値として解釈・検証する。 (#392) (@Shirofune-Security) - 設定時に外部コマンドの終了コードと変更後の設定値を確認し、処理の終了前にも再確認するようにした。書き込み失敗、設定の未反映、CAサービスの再起動失敗、最終確認時の設定の不一致を明示的に報告し、一律に成功とせず、0以外の終了コードを返すようにした。 (#392) (@Shirofune-Security) diff --git a/website/docs/resources/changelog.md b/website/docs/resources/changelog.md index 651452d6..8e099441 100644 --- a/website/docs/resources/changelog.md +++ b/website/docs/resources/changelog.md @@ -19,7 +19,7 @@ **Bug Fixes:** -- Replaced static native-channel `Enabled` claims with actual channel state, mode and ACL reads plus provider prerequisite observations. AppLocker, NTLM, Defender and other native sources remain conditional until event generation is validated; channel enablement alone grants no usable-rule credit. Added JSON/HTML audit assessment exports preserving denied/absent states and source evidence. (#395) (@Shirofune-Security) +- Replaced static native-channel `Enabled` claims with actual channel state, mode and ACL reads plus provider prerequisite observations. AppLocker, NTLM, Defender and other native sources remain conditional until event generation is validated; channel enablement alone grants no usable-rule credit. Added JSON/HTML audit assessment exports preserving denied/absent states and source evidence. Rule channel patterns now match concrete catalog channels consistently during filtering and source mapping. (#395) (@Shirofune-Security) - Fixed `configure` enabling outgoing NTLM blocking by default. It now sets Audit all (`RestrictSendingNTLMTraffic=1`) for unset or Allow policies while preserving existing Deny all (`2`) and unknown values/types. Use `-OutgoingNtlmMode Audit` to explicitly replace a deny policy, or `Deny` to enable blocking. Configuration rechecks policy before writing, verifies changes, reports failures, and displays the observed policy and available last-applied RSoP information. (#388) (@Shirofune-Security) - `audit-settings` now reports role-inapplicable audit policies as `Not applicable` and excludes them from category enablement totals. NTLM policy values are interpreted and verified only when stored as DWORDs. (#392) (@Shirofune-Security) - Configuration now checks native command exit codes, verifies settings after applying changes, and checks them again before finishing. Failed writes, ineffective changes, CA restart failures and settings that no longer match at the final check produce explicit results and a nonzero exit code instead of unconditional success. (#392) (@Shirofune-Security)