Add scoped outgoing NTLM audit configuration with native acceptance

This commit is contained in:
Shirofune-Security committed 2026-09-22 10:09:37 +09:00
1 parent 03039cb1c6
commit 0777a5d0f8
13 files changed
+325 -3

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/targeted-sacl-planning.md, ./docs/native-registry-sacl-validation.md, ./docs/wec-authorization.md, ./docs/channel-recovery.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/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/targeted-sacl-planning.md, ./docs/native-registry-sacl-validation.md, ./docs/wec-authorization.md, ./docs/channel-recovery.md, ./docs/outgoing-ntlm.md -Destination release-binaries/docs/
- name: Set Artifact Name
if: contains(matrix.info.os, 'windows') == true
@@ -0,0 +1,47 @@
name: Scoped outgoing NTLM auditing
on:
push:
branches: ['**']
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
scoped-outgoing-ntlm:
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/OutgoingNtlmAudit.Tests.ps1
./tests/OutgoingNtlmAudit.Cli.Tests.ps1
- name: Scoped outgoing NTLM auditing in Windows PowerShell5.1
if: matrix.engine == 'powershell'
shell: powershell
run: ./tests/OutgoingNtlmAudit.Windows.Tests.ps1 -AllowDisposableAuditWrite
- name: Fixtures and public guards in PowerShell7
if: matrix.engine == 'pwsh'
shell: pwsh
run: |
./tests/OutgoingNtlmAudit.Tests.ps1
./tests/OutgoingNtlmAudit.Cli.Tests.ps1
- name: Scoped outgoing NTLM auditing in PowerShell7
if: matrix.engine == 'pwsh'
shell: pwsh
run: ./tests/OutgoingNtlmAudit.Windows.Tests.ps1 -AllowDisposableAuditWrite
- name: Retain owned fixture evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: scoped-outgoing-ntlm-${{ matrix.os }}-${{ matrix.engine }}
path: ${{ runner.temp }}/wela-outgoing-audit-*/
if-no-files-found: warn
retention-days: 7
+2
View File
@@ -4,6 +4,8 @@
**改善:**
- `outgoing-ntlm` のAudit/Plan/Configureを追加し、送信NTLM監査DWORDを個別に設定できます。既存の拒否設定は既定で維持し、置換には明示的なAuditを要求します。不明な型・値や書込直前の変化を拒否し、元の型付き記録と再読取を保持します。Server 2022/2025のテストで範囲と復元を確認し、認証・イベント生成は未検証として報告します。(関連 #362) (@Shirofune-Security)
- Server 2022/2025 と Windows PowerShell 5.1/PowerShell 7 の破棄可能な環境で、Securityログ警告設定の公開CLIを検証します。未設定・0・高いしきい値、早い警告値の維持、DryRun、再実行、不正型の拒否と完全な復元を確認し、無関係な設定は保持します。ログ枯渇や警告イベント生成は検証範囲外です。 (@Shirofune-Security)
- Server 2022/2025 と両 PowerShell エンジンで、公開 `targeted-sacl` のレジストリ操作を検証する使い捨てテストを追加しました。テスト専用の新規ハイブをマウントし、対象選択、DryRun、監査 ACE の追加、古い計画・前提条件不足の拒否、冪等性と厳密に対応付けた Security4657 を確認します。無関係な ACE・型付き値の保持、監査ポリシー・トークンの復元、所有ハイブのアンロードと削除の証跡を保存します。製品側のハイブ読み込みや Sigma 準備完了の判定は追加しません。(関連 #373) (@Shirofune-Security)
+2
View File
@@ -4,6 +4,8 @@
**Improvements:**
- Add `outgoing-ntlm` Audit/Plan/Configure to manage the outgoing audit DWORD independently of broad configuration. Preserve existing deny by default, require explicit Audit to replace it, refuse unknown types/values and pre-write drift, and retain typed original journals plus native readback. Native Server 2022/2025 tests verify narrow scope and exact cleanup; authentication/event generation remain unverified. (Related #362) (@Shirofune-Security)
- Verify public Security-log warning configuration on disposable Server 2022/2025 hosts under Windows PowerShell 5.1/PowerShell 7: absent/zero/higher thresholds, earlier-threshold preservation, dry run, idempotence, wrong-type refusal and exact cleanup. Preserve unrelated registry values, channel configuration, audit masks and CrashOnAuditFail; no log-exhaustion or warning-event claim. (@Shirofune-Security)
- Added disposable public `targeted-sacl` registry lifecycle validation on Server 2022/2025 and both PowerShell engines. A fixture-owned mounted hive exercises reviewed selection, DryRun, additive configuration, stale/prerequisite refusal and idempotence, followed by one precisely attributed Security4657. Retained native receipts verify unrelated ACE/value preservation and exact audit-policy, token and owned-hive cleanup; production does not load hives or gain Sigma credit. (Related #373) (@Shirofune-Security)
+17 -1
View File
@@ -16,6 +16,7 @@
[switch]$Auto,
[ValidateSet("PreserveOrAudit", "Audit", "Deny")]
[string]$OutgoingNtlmMode = "PreserveOrAudit",
[ValidateSet("Audit","Plan","Configure")][string]$NtlmAction = "Audit",
[switch]$DryRun,
[string]$BackupPath,
[string]$ResultsPath,
@@ -225,6 +226,7 @@ $EidMappingPath = Join-Path $ScriptRoot "config/eid_subcategory_mapping.csv"
$AuditpolTxtPath = Join-Path $ScriptRoot "auditpol.txt"
$SaclTargetsPath = Join-Path $ScriptRoot "config/audit_sacl_targets.json"
. (Join-Path $ScriptRoot "scripts/Configuration.ps1")
. (Join-Path $ScriptRoot "scripts/OutgoingNtlmAudit.ps1")
. (Join-Path $ScriptRoot "scripts/AdcsAuditing.ps1")
. (Join-Path $ScriptRoot "scripts/AdcsRestartResume.ps1")
. (Join-Path $ScriptRoot "scripts/AuditIntegrity.ps1")
@@ -2064,6 +2066,7 @@ Usage:
./WELA.ps1 eventlog-recovery -Help # Review restoration of one completed log size/mode write
./WELA.ps1 wec-listener -Help # Review one fixed-address native HTTP5985 listener
./WELA.ps1 wec-ingress -Help # Review scoped collector firewall rule creation
./WELA.ps1 outgoing-ntlm -Help # Configure outgoing NTLM auditing independently
./WELA.ps1 wec-authorization -Help # Review source SID authorization on a disabled subscription
./WELA.ps1 wec-state -Help # Review enable/disable of one existing subscription
./WELA.ps1 wec-update -Help # Review query/description updates on a disabled subscription
@@ -2161,6 +2164,12 @@ if ($Cmd -ne 'wec-listener' -and @($PSBoundParameters.Keys | Where-Object {$_ -l
if ($Cmd -eq 'wec-listener' -and ($args.Count -or @($PSBoundParameters.Keys | Where-Object {$_ -notin @('Cmd','WecListenerAction','WecListenerComputerName','WecListenerLocalAddress','WecListenerPlanPath','WecListenerPlanHash','WecListenerOutputPath','Help')}).Count)) {throw 'wec-listener accepts only dedicated options.'}
if ($Cmd -ne 'wec-ingress' -and @($PSBoundParameters.Keys | Where-Object {$_ -like 'WecIngress*'}).Count) {throw 'WecIngress options require wec-ingress.'}
if ($Cmd -eq 'wec-ingress' -and @($PSBoundParameters.Keys | Where-Object {$_ -notin @('Cmd','WecIngressAction','WecIngressName','WecIngressLocalAddress','WecIngressRemoteAddress','WecIngressPlanPath','WecIngressPlanHash','WecIngressOutputPath','Help')}).Count) {throw 'wec-ingress accepts only dedicated options.'}
if ($Cmd -ne 'outgoing-ntlm' -and $PSBoundParameters.ContainsKey('NtlmAction')) {throw 'NtlmAction requires outgoing-ntlm.'}
if ($Cmd -eq 'outgoing-ntlm') {
if (@($PSBoundParameters.Keys | Where-Object {$_ -notin @('Cmd','NtlmAction','OutgoingNtlmMode','Auto','DryRun','BackupPath','ResultsPath','Help')}).Count) {throw 'outgoing-ntlm accepts only its dedicated options.'}
if ($OutgoingNtlmMode -eq 'Deny') {throw 'outgoing-ntlm configures auditing only; Deny enforcement is not accepted.'}
if ($NtlmAction -ne 'Configure' -and ($Auto -or $DryRun -or $BackupPath)) {throw 'Consent, dry-run and backup options require NtlmAction Configure.'}
}
if ($Cmd -ne 'wec-authorization' -and @($PSBoundParameters.Keys | Where-Object {$_ -like 'WecAuthorization*'}).Count) {throw 'WecAuthorization options require wec-authorization.'}
if ($Cmd -eq 'wec-authorization' -and ($args.Count -or @($PSBoundParameters.Keys | Where-Object {$_ -notin @('Cmd','WecAuthorizationAction','WecAuthorizationId','WecAuthorizationSourceSid','WecAuthorizationPlanPath','WecAuthorizationPlanHash','WecAuthorizationOutputPath','Help')}).Count)) {throw 'wec-authorization accepts only dedicated options.'}
if ($Cmd -ne 'wec-state' -and @($PSBoundParameters.Keys | Where-Object {$_ -like 'WecState*'}).Count) {throw 'WecState options require wec-state.'}
@@ -2249,7 +2258,7 @@ if ($Cmd -ne 'ad-object-sacl' -and @($PSBoundParameters.Keys | Where-Object {
}).Count) {
throw 'AD object SACL options require the dedicated ad-object-sacl command. No command was run.'
}
if ($DryRun -and -not ($Cmd -eq 'transcription-recovery' -and $TranscriptRecoveryAction -eq 'Restore') -and -not ($Cmd -eq 'adcs-auditing' -and $AdcsAction -eq 'Configure') -and -not ($Cmd -eq 'adcs-resume' -and $AdcsResumeAction -eq 'Resume') -and -not ($Cmd -eq 'gpo-create' -and $GpoCreateAction -eq 'Create') -and -not ($Cmd -eq 'dns-analytical' -and $DnsAction -eq 'Configure') -and -not ($Cmd -eq 'targeted-sacl' -and $TargetSaclAction -eq 'Configure') -and -not ($Cmd -eq 'audit-recovery' -and $RecoveryAction -eq 'Restore') -and -not ($Cmd -eq 'gpo-package' -and $GpoAction -eq 'Export') -and -not ($Cmd -eq 'audit-integrity' -and $IntegrityAction -eq 'Configure') -and -not ($Cmd -eq 'audit-notifications' -and $NotificationAction -eq 'Configure') -and -not ($Cmd -eq 'ldap-diagnostics' -and $LdapAction -eq 'Configure') -and -not ($Cmd -eq 'applocker-readiness' -and $AppLockerAction -eq 'Import') -and $Cmd -notin @('configure', 'configure-eventlogs') -and
if ($DryRun -and -not ($Cmd -eq 'outgoing-ntlm' -and $NtlmAction -eq 'Configure') -and -not ($Cmd -eq 'transcription-recovery' -and $TranscriptRecoveryAction -eq 'Restore') -and -not ($Cmd -eq 'adcs-auditing' -and $AdcsAction -eq 'Configure') -and -not ($Cmd -eq 'adcs-resume' -and $AdcsResumeAction -eq 'Resume') -and -not ($Cmd -eq 'gpo-create' -and $GpoCreateAction -eq 'Create') -and -not ($Cmd -eq 'dns-analytical' -and $DnsAction -eq 'Configure') -and -not ($Cmd -eq 'targeted-sacl' -and $TargetSaclAction -eq 'Configure') -and -not ($Cmd -eq 'audit-recovery' -and $RecoveryAction -eq 'Restore') -and -not ($Cmd -eq 'gpo-package' -and $GpoAction -eq 'Export') -and -not ($Cmd -eq 'audit-integrity' -and $IntegrityAction -eq 'Configure') -and -not ($Cmd -eq 'audit-notifications' -and $NotificationAction -eq 'Configure') -and -not ($Cmd -eq 'ldap-diagnostics' -and $LdapAction -eq 'Configure') -and -not ($Cmd -eq 'applocker-readiness' -and $AppLockerAction -eq 'Import') -and $Cmd -notin @('configure', 'configure-eventlogs') -and
-not ($Cmd -eq 'provider-packs' -and $ProviderAction -eq 'Configure') -and
-not ($Cmd -eq 'firewall-logging' -and $FirewallAction -eq 'Configure') -and
-not ($Cmd -eq 'firewall-recovery' -and $FirewallRecoveryAction -eq 'Restore') -and
@@ -2428,6 +2437,13 @@ switch ($Cmd.ToLower()) {
$report=Invoke-WelaWecIngress @arguments;$report
if($report.ExitCode){exit $report.ExitCode}
}
'outgoing-ntlm' {
if ($Help) {Write-Host 'Usage: outgoing-ntlm [-NtlmAction Audit|Plan|Configure] [-OutgoingNtlmMode PreserveOrAudit|Audit] [-Auto] [-DryRun] [-BackupPath new-directory] [-ResultsPath report.json]. Changes only the outgoing audit DWORD. Existing deny is preserved by default; explicit Audit authorizes replacing it. See docs/outgoing-ntlm.md.';return}
if ($NtlmAction -eq 'Configure' -and -not (TestAdministrator)) {throw 'Outgoing NTLM configuration requires Administrator privileges.'}
$report=Invoke-WelaOutgoingAuditCommand -Action $NtlmAction -Mode $OutgoingNtlmMode -Auto:$Auto -DryRun:$DryRun -BackupPath $BackupPath -ResultsPath $ResultsPath
$report
if ($report.ExitCode) {exit $report.ExitCode}
}
'wec-authorization' {
if ($Help) {Write-Host 'Usage: wec-authorization [-WecAuthorizationAction Plan] -WecAuthorizationId ID -WecAuthorizationSourceSid desired-SID1,desired-SID2 -WecAuthorizationOutputPath new-directory; then Apply with -WecAuthorizationPlanPath plan.json -WecAuthorizationPlanHash SHA256 -WecAuthorizationOutputPath new-directory. Only the explicit source SID authorization of one already disabled subscription. No SID resolution or forwarding proof. See docs/wec-authorization.md.';return}
$arguments=@{Action=$WecAuthorizationAction;OutputPath=$WecAuthorizationOutputPath}
+22
View File
@@ -0,0 +1,22 @@
# Scoped outgoing NTLM auditing
`outgoing-ntlm` audits or configures only `HKLM\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0\RestrictSendingNTLMTraffic`. The separate broad `configure` workflow retains its existing behavior. Use elevated 64-bit PowerShell for Configure on reviewed Windows 11 builds (22000/22621/22631/26100/26200) or Server 2022/2025 (20348/26100); observed role/build and the existing key are required. Role overrides are refused.
```powershell
./WELA.ps1 outgoing-ntlm -NtlmAction Audit -ResultsPath audit.json
./WELA.ps1 outgoing-ntlm -NtlmAction Plan -ResultsPath plan.json
./WELA.ps1 outgoing-ntlm -NtlmAction Configure -DryRun -ResultsPath preview.json
./WELA.ps1 outgoing-ntlm -NtlmAction Configure -Auto -BackupPath ./before -ResultsPath result.json
# Explicitly replace a previously reviewed Deny all value with auditing:
./WELA.ps1 outgoing-ntlm -NtlmAction Configure -OutgoingNtlmMode Audit -BackupPath ./before-reviewed -ResultsPath reviewed.json
```
The default `PreserveOrAudit` mode sets only DWORD **1 (Audit all)** when absent or DWORD0. Existing DWORD1 is already compliant. Existing DWORD **2 (Deny all)** is reported as `PreservedEnforcement` and skipped; exit0 for this preserved case does not mean auditing was enabled. Explicit `Audit` authorizes replacing a known DWORD2 with1. Unknown types/values fail without writes in either mode. `Deny` is refused by this scoped command. It never changes incoming/domain NTLM policy, exceptions, audit subcategories, channel settings, services, or authentication restrictions other than the explicit conversion of a known outgoing deny to audit.
Plan is a live read-only assessment, not an importable authorization file. Audit/Plan reject mutation options. Configure re-reads the actual host and typed value, journals before mutation, refuses pre-write drift, and verifies immediate/final readback. A race after the final pre-write read remains possible; these observations are not atomic with GPO or another administrator. RSoP is explicitly last-applied and potentially stale, never proof of the current registry writer. Skipped, Failed and Overridden results remain distinct. No automatic rollback occurs.
For manual recovery, inspect the selected successful result and its original `before.jsonl` entry. The original typed registry state is `Before.Policy`; preserve current policy ownership and review drift before restoring that one value/type or removing that value if it was originally absent. Never remove the parent MSV1_0 key or replay another journal kind. Failed/partial attempts require individual inspection. Keep the original journal and result together.
Native acceptance uses disposable unjoined Server2022/2025 hosts under PowerShell5.1/7, exercises actual absence/allow→audit, original journals, dry run, repeat, readback and exact cleanup. Existing enforcement and malformed values are never installed on a native runner merely for testing; portable regressions verify those preservation/refusal paths, prompt-time drift and failures. Native tests preserve incoming/domain policy, siblings/access descriptor, channels, service and all59 audit masks. Windows11/DC/ADCS acceptance, authentication behavior, representative NTLM events, GPO persistence and collector delivery remain separate work for #362. No Sigma credit is inferred. Built-in Windows only; Sysmon is excluded.
Microsoft distinguishes outgoing audit from deny, describes GPO precedence and identifies the NTLM Operational log for validation: [outgoing NTLM policy](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/security-policy-settings/network-security-restrict-ntlm-outgoing-ntlm-traffic-to-remote-servers).
+1 -1
View File
@@ -108,7 +108,7 @@ function Invoke-WelaConfigurationControl {
function Complete-WelaConfiguration {
param($Context, [string]$ResultsPath, $Plan,
[ValidateSet("native-windows-configuration", "advanced-audit-policy-only", "advanced-audit-policy-and-precedence", "firewall-text-logging-only", "event-log-size-and-mode-only", "smb-audit-policies-only", "native-channel-settings-only", "wmi-namespace-sacl-only", "ad-object-sacl-only", "windows-powershell-transcription-policy-only", "wef-source-configuration-only", "wec-collector-subscriptions-only", "audit-integrity-local-policy-only", "adcs-audit-settings-only", "disabled-unlinked-gpo-creation-only")]
[ValidateSet("native-windows-configuration", "outgoing-ntlm-audit-policy-only", "advanced-audit-policy-only", "advanced-audit-policy-and-precedence", "firewall-text-logging-only", "event-log-size-and-mode-only", "smb-audit-policies-only", "native-channel-settings-only", "wmi-namespace-sacl-only", "ad-object-sacl-only", "windows-powershell-transcription-policy-only", "wef-source-configuration-only", "wec-collector-subscriptions-only", "audit-integrity-local-policy-only", "adcs-audit-settings-only", "disabled-unlinked-gpo-creation-only")]
[string]$Scope = "native-windows-configuration",
[string]$SuccessMessage = 'Configuration completed; all requested controls verified.')
if ($Context.PSObject.Properties['CustomProfileGuard']) {
+77
View File
@@ -0,0 +1,77 @@
# Explicit outgoing audit policy; does not invoke the broad configure workflow.
function Get-WelaOutgoingAuditSnapshot {
if ($env:OS -ne 'Windows_NT' -or -not [Environment]::Is64BitProcess) { throw 'Use 64-bit PowerShell on Windows.' }
$os=Get-CimInstance Win32_OperatingSystem -Property BuildNumber,ProductType -ErrorAction Stop
$computer=Get-CimInstance Win32_ComputerSystem -Property DomainRole,PartOfDomain -ErrorAction Stop
$build=[int]$os.BuildNumber;$product=[int]$os.ProductType
if (-not (($product -eq 1 -and $build -in @(22000,22621,22631,26100,26200)) -or ($product -in @(2,3) -and $build -in @(20348,26100)))) { throw 'This Windows role/build has not been reviewed for the scoped command.' }
if ($computer.DomainRole -notin @(0,1,2,3,4,5) -or $computer.PartOfDomain -isnot [bool]) {throw 'Computer role/join context is unavailable.'}
$policy=Get-WelaRegistryState 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0' RestrictSendingNTLMTraffic
if (-not $policy.KeyExists) {throw 'The existing MSV1_0 policy key is required; no parent key will be created.'}
[pscustomobject][ordered]@{Host=[pscustomobject][ordered]@{Build=$build;ProductType=$product;DomainRole=[int]$computer.DomainRole;PartOfDomain=$computer.PartOfDomain};Policy=$policy}
}
function Get-WelaOutgoingAuditDisposition {
param($Snapshot,[ValidateSet('PreserveOrAudit','Audit')][string]$Mode)
$p=$Snapshot.Policy
if ($p.ValueExists -and ($p.Type -cne 'DWord' -or $p.Value -notin @(0,1,2))) {return 'Unknown'}
if ($p.ValueExists -and $p.Value -eq 1) {return 'AlreadyCompliant'}
if ($p.ValueExists -and $p.Value -eq 2 -and $Mode -eq 'PreserveOrAudit') {return 'PreservedEnforcement'}
return 'ChangeRequired'
}
function Get-WelaOutgoingAuditPlan {
param([ValidateSet('PreserveOrAudit','Audit')][string]$Mode='PreserveOrAudit')
try {
$snapshot=Get-WelaOutgoingAuditSnapshot
$status=Get-WelaOutgoingAuditDisposition $snapshot $Mode
$diagnostic=switch($status){
Unknown {'Unknown registry type/value is preserved; investigate it before configuration.'}
PreservedEnforcement {'Deny all (2) is authentication enforcement, preserved by default. Explicit -OutgoingNtlmMode Audit authorizes replacing it with Audit all (1).'}
AlreadyCompliant {'Audit all (1) is configured; authentication, events and policy persistence are unverified.'}
default {'Set only outgoing NTLM Audit all (DWORD 1).'}
}
[pscustomobject]@{Status=$status;Mode=$Mode;Desired=1;Before=$snapshot;Diagnostic=$diagnostic;PolicySource=Get-WelaOutgoingNtlmPolicySource}
}catch{[pscustomobject]@{Status='Unknown';Mode=$Mode;Desired=1;Before=$null;Diagnostic=$_.ToString();PolicySource='Unknown'}}
}
function Invoke-WelaOutgoingAuditCommand {
param([ValidateSet('Audit','Plan','Configure')][string]$Action='Audit',[ValidateSet('PreserveOrAudit','Audit')][string]$Mode='PreserveOrAudit',[switch]$Auto,[switch]$DryRun,[string]$BackupPath,[string]$ResultsPath)
if ($Action -ne 'Configure' -and ($Auto -or $DryRun -or $BackupPath)) {throw 'Consent, dry-run and backup options require Configure.'}
$plan=Get-WelaOutgoingAuditPlan $Mode
if ($Action -eq 'Configure') {
$context=New-WelaConfigurationContext -Auto:$Auto -DryRun:$DryRun -BackupPath $BackupPath
$path='HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0';$name='RestrictSendingNTLMTraffic'
if ($plan.Status -eq 'Unknown') {
$context.Results.Add([pscustomobject]@{Id="Registry/$path/$name";Kind='Registry';Target=@{Path=$path;Name=$name};Desired=@{Value=1;Type='DWord'};Before=$plan.Before;After=$null;Status='Failed';Diagnostic=$plan.Diagnostic})
}else{
$state=@{Observed=$null;PlannedHost=($plan.Before.Host|ConvertTo-Json -Compress);Mode=$Mode;Path=$path;Name=$name}
$read={param($s)
$snapshot=Get-WelaOutgoingAuditSnapshot
if (($snapshot.Host|ConvertTo-Json -Compress) -cne $s.PlannedHost) {throw 'Observed host context changed; review a new plan.'}
if ((Get-WelaOutgoingAuditDisposition $snapshot $s.Mode) -eq 'Unknown') {throw 'Unknown registry type/value is preserved.'}
$s.Observed=$snapshot
return $snapshot
}
$test={param($snapshot) $snapshot.Policy.ValueExists -and $snapshot.Policy.Type -ceq 'DWord' -and $snapshot.Policy.Value -eq 1}
$preserve=if($Mode -eq 'PreserveOrAudit'){{param($snapshot) if($snapshot.Policy.ValueExists -and $snapshot.Policy.Type -ceq 'DWord' -and $snapshot.Policy.Value -eq 2){'Preserved Deny all enforcement; explicit Audit mode is required to replace it.'}}}else{$null}
$apply={param($s)
$fresh=Get-WelaOutgoingAuditSnapshot
if (($fresh|ConvertTo-Json -Depth 8 -Compress) -cne ($s.Observed|ConvertTo-Json -Depth 8 -Compress)) {throw 'Outgoing NTLM state changed after the original journal snapshot; no write was attempted.'}
if ((Get-WelaOutgoingAuditDisposition $fresh $s.Mode) -ne 'ChangeRequired') {throw 'The current state no longer authorizes this write.'}
Set-ItemProperty -LiteralPath $s.Path -Name $s.Name -Value 1 -Type DWord -ErrorAction Stop
'Only outgoing NTLM Audit all (1) was requested; no authentication or event-generation test was performed.'
}
Invoke-WelaConfigurationControl -Context $context -Id "Registry/$path/$name" -Kind Registry -Target @{Path=$path;Name=$name} -Desired @{Value=1;Type='DWord'} -Read $read -Compliant $test -PreserveWhen $preserve -Apply $apply -CallbackState $state -Description $plan.Diagnostic
}
$report=Complete-WelaConfiguration -Context $context -Scope 'outgoing-ntlm-audit-policy-only' -SuccessMessage 'Outgoing NTLM configuration results recorded; inspect preserved/skipped controls separately.'
$report|Add-Member NoteProperty Plan $plan
}else{$report=[pscustomobject]@{ExitCode=$(if($plan.Status -eq 'Unknown'){1}else{0});Scope='outgoing-ntlm-audit-policy-only';Action=$Action;Plan=$plan}}
$report|Add-Member NoteProperty EventGeneration 'Not verified; registry compliance does not establish authentication, NTLM events, forwarding, GPO persistence or Sigma readiness.'
$report|Add-Member NoteProperty ReadyRuleCredit 0
if ($ResultsPath) {
try {$report|ConvertTo-Json -Depth 16|Set-Content -LiteralPath $ResultsPath -Encoding UTF8 -ErrorAction Stop}
catch {$report.ExitCode=1;Write-Host "[Failed] Writing outgoing NTLM results: $_" -ForegroundColor Red}
}
return $report
}
+18
View File
@@ -0,0 +1,18 @@
$ErrorActionPreference='Stop';$repo=Split-Path $PSScriptRoot -Parent;$engine=(Get-Process -Id $PID).Path;$count=0
$root=Join-Path ([IO.Path]::GetTempPath()) ('wela-ntlm-cli-'+[guid]::NewGuid().ToString('N'))
$cases=@(
@{Args=@('outgoing-ntlm','-Help');Code=0;Pattern='Changes only'},
@{Args=@('configure','-NtlmAction','Configure');Code=1;Pattern='requires outgoing-ntlm'},
@{Args=@('outgoing-ntlm','-OutgoingNtlmMode','Deny');Code=1;Pattern='enforcement is not accepted'},
@{Args=@('outgoing-ntlm','-Role','Client');Code=1;Pattern='dedicated options'},
@{Args=@('outgoing-ntlm','-Profile','wela-2.2.0');Code=1;Pattern='dedicated options'},
@{Args=@('outgoing-ntlm','-Auto');Code=1;Pattern='require NtlmAction Configure'},
@{Args=@('outgoing-ntlm','-DryRun');Code=1;Pattern='require NtlmAction Configure'},
@{Args=@('outgoing-ntlm','-BackupPath',$root);Code=1;Pattern='require NtlmAction Configure'},
@{Args=@('outgoing-ntlm','-Help','-ProviderAction','Configure');Code=1;Pattern='dedicated options'},
@{Args=@('outgoing-ntlm','-NtlmAction','Configure','-Typo');Code=1;Pattern='Unsupported trailing arguments'}
)
foreach($case in $cases){$prior=$ErrorActionPreference;try{$ErrorActionPreference='Continue';$text=@(&$engine -NoLogo -NoProfile -NonInteractive -File "$repo/WELA.ps1" @($case.Args) 2>&1);$code=$LASTEXITCODE}finally{$ErrorActionPreference=$prior};if(($code -eq 0) -ne ($case.Code -eq 0) -or ($text -join "`n") -notmatch $case.Pattern){throw "CLI failed: $($case.Args -join ' ') -> $code / $($text -join ' ')"};$count++}
if(Test-Path $root){throw 'Refused CLI input created unexpected output.'}
Write-Host "PASS: $count scoped outgoing NTLM CLI guards."
exit 0
+58
View File
@@ -0,0 +1,58 @@
$ErrorActionPreference='Stop';$repo=Split-Path $PSScriptRoot -Parent
. (Join-Path $repo 'scripts/Configuration.ps1')
. (Join-Path $repo 'scripts/OutgoingNtlmAudit.ps1')
$root=Join-Path ([IO.Path]::GetTempPath()) ('wela-ntlm-test-'+[guid]::NewGuid().ToString('N'));$null=New-Item -ItemType Directory $root
$count=0;$sequence=0
function Assert($Value,$Message){if(-not $Value){throw $Message};$script:count++}
function Key($Value){ConvertTo-Json -InputObject $Value -Depth 12 -Compress}
function Reset($Value,$Type='DWord'){
$script:policy=[pscustomobject][ordered]@{KeyExists=$true;ValueExists=($null -ne $Value);Value=$Value;Type=$(if($null -ne $Value){$Type}else{$null})}
$script:writes=0;$script:reads=0;$script:failRead=$false;$script:failWrite=$false;$script:ignoreWrite=$false;$script:promptChange=$null;$script:onRead=$null
}
function Get-WelaOutgoingAuditSnapshot {
$script:reads++;if($script:onRead){& $script:onRead};if($script:failRead){throw 'Access denied'}
[pscustomobject][ordered]@{Host=[pscustomobject][ordered]@{Build=26100;ProductType=3;DomainRole=2;PartOfDomain=$false};Policy=($script:policy|ConvertTo-Json|ConvertFrom-Json)}
}
function Get-WelaOutgoingNtlmPolicySource {'Unknown (fixture has no RSoP ownership evidence)'}
function Set-ItemProperty {param($LiteralPath,$Name,$Value,$Type,$ErrorAction)
Assert ($LiteralPath -ceq 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0' -and $Name -ceq 'RestrictSendingNTLMTraffic' -and $Value -eq 1 -and $Type -ceq 'DWord') 'Only the one exact audit-only target may be written.'
$script:writes++;if($script:failWrite){throw 'Write denied'};if(-not $script:ignoreWrite){$script:policy.ValueExists=$true;$script:policy.Value=1;$script:policy.Type='DWord'}
}
function Read-Host {param($Prompt) if($script:promptChange){& $script:promptChange};return 'Y'}
function Configure([string]$Mode='PreserveOrAudit',[switch]$DryRun,[switch]$Prompt){
$script:sequence++;$script:backup=Join-Path $root ('case-'+$script:sequence)
Invoke-WelaOutgoingAuditCommand -Action Configure -Mode $Mode -Auto:(-not $Prompt) -DryRun:$DryRun -BackupPath $script:backup
}
try{
foreach($initial in @($null,0,1)){
Reset $initial;$old=Key $script:policy;$r=Configure
Assert ($r.ExitCode -eq 0 -and $r.Scope -ceq 'outgoing-ntlm-audit-policy-only' -and $r.ReadyRuleCredit -eq 0) 'Public report scopes success to one policy, without detection credit.'
Assert ($script:policy.Value -eq 1 -and $script:writes -eq $(if($initial -eq 1){0}else{1})) 'Absent/allow are audited; existing audit is idempotent.'
if($initial -ne 1){$j=@(Get-Content (Join-Path $backup 'before.jsonl')|ConvertFrom-Json);Assert ($j.Count -eq 1 -and (Key $j[0].Before.Policy) -ceq $old) 'Typed original snapshot is durable before the one write.'}
else{Assert (-not(Test-Path (Join-Path $backup 'before.jsonl'))) 'Already configured mode does not journal a write.'}
}
Reset 2;$r=Configure;Assert ($script:writes -eq 0 -and $r.Results[0].Status -ceq 'Skipped' -and $r.Plan.Status -ceq 'PreservedEnforcement' -and $script:policy.Value -eq 2) 'Default mode preserves and identifies authentication enforcement.'
Reset 2;$r=Configure Audit;Assert ($script:writes -eq 1 -and $script:policy.Value -eq 1 -and $r.Results[0].Status -ceq 'Applied') 'Explicit audit mode authorizes replacing deny with auditing.'
foreach($value in @(42,'1')){foreach($mode in @('PreserveOrAudit','Audit')){
Reset $value $(if($value -is [string]){'String'}else{'DWord'});$r=Configure $mode
Assert ($r.ExitCode -eq 1 -and $script:writes -eq 0 -and $r.Results[0].Status -ceq 'Failed') 'Unknown values/types remain untouched even in explicit Audit mode.'
}}
Reset 0;$r=Configure -DryRun;Assert ($script:writes -eq 0 -and $r.DryRun -and -not(Test-Path $backup)) 'Dry run has no policy or journal-directory mutation.'
Reset 0;$script:failRead=$true;$r=Configure;Assert ($r.ExitCode -eq 1 -and $script:writes -eq 0) 'An unreadable policy fails closed.'
foreach($kind in @('failWrite','ignoreWrite')){
Reset 0;Set-Variable -Scope Script -Name $kind -Value $true;$r=Configure
Assert ($r.ExitCode -eq 1 -and $r.Results[0].Status -ceq 'Failed') 'Native failure and ignored-write readback cannot report success.'
}
foreach($changed in @(1,2,42)){
Reset 0;$script:changed=$changed;$script:promptChange={$script:policy.Value=$script:changed};$r=Configure -Prompt
Assert ($r.ExitCode -eq 1 -and $script:writes -eq 0 -and $script:policy.Value -eq $changed) 'Prompt-time drift refuses writes after preserving the exact original receipt.'
}
Reset 0;$script:onRead={if($script:reads -eq 5){$script:policy.Value=0}};$r=Configure
Assert ($script:writes -eq 1 -and $r.ExitCode -eq 1 -and $r.Results[0].Status -ceq 'Overridden') 'A later policy change fails final verification.'
Reset 0;$r=Invoke-WelaOutgoingAuditCommand -Action Plan;Assert ($r.Plan.Status -ceq 'ChangeRequired' -and $script:writes -eq 0) 'Plan is current-host assessment and does not mutate policy.'
foreach($action in @('Audit','Plan')){foreach($option in @('Auto','DryRun','BackupPath')){
$a=@{Action=$action};$a[$option]=$(if($option -eq 'BackupPath'){'unused'}else{$true});$threw=$false;try{Invoke-WelaOutgoingAuditCommand @a}catch{$threw=$true};Assert $threw 'Read-only actions reject mutation-only options.'
}}
}finally{Remove-Item -LiteralPath $root -Recurse -Force}
Write-Host "PASS: $count scoped outgoing NTLM assertions."
exit 0
+76
View File
@@ -0,0 +1,76 @@
param([switch]$AllowDisposableAuditWrite)
$ErrorActionPreference='Stop'
if(-not $AllowDisposableAuditWrite -or $env:GITHUB_ACTIONS -ne 'true' -or $env:RUNNER_ENVIRONMENT -ne 'github-hosted'){throw 'Explicit opt-in on a disposable GitHub-hosted Windows runner is required.'}
$repo=Split-Path $PSScriptRoot -Parent
. (Join-Path $repo 'scripts/Configuration.ps1')
. (Join-Path $repo 'scripts/OutgoingNtlmAudit.ps1')
Import-Module (Join-Path $repo 'modules/AuditProfiles.psm1') -Force
Import-Module (Join-Path $repo 'modules/NativeProviders.psm1') -Force
$engine=(Get-Process -Id $PID).Path;$count=0;$failure=$null;$errors=@()
$root=Join-Path $env:RUNNER_TEMP ('wela-outgoing-audit-'+[guid]::NewGuid().ToString('N'));$null=New-Item -ItemType Directory $root
$path='HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0';$name='RestrictSendingNTLMTraffic'
function Assert($Value,$Message){if(-not $Value){throw $Message};$script:count++}
function Key($Value){ConvertTo-Json -InputObject $Value -Depth 24 -Compress}
function Save($Name,$Value){ConvertTo-Json -InputObject $Value -Depth 24|Set-Content -LiteralPath (Join-Path $root $Name) -Encoding UTF8}
function Masks {$m=Get-WelaEffectiveAuditPolicy;@($m.Keys|Sort-Object|ForEach-Object{"$_=$($m[$_])"}) -join ';'}
function Other {
$base=[Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,[Microsoft.Win32.RegistryView]::Registry64);$k=$null
try{
$k=$base.OpenSubKey('SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0');if(-not $k){throw 'Existing MSV1_0 key required.'}
$values=@($k.GetValueNames()|Sort-Object|Where-Object {$_ -ine $name}|ForEach-Object{[pscustomobject][ordered]@{Name=$_;Type=$k.GetValueKind($_).ToString();Value=$k.GetValue($_,$null,[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)}})
$children=@($k.GetSubKeyNames()|Sort-Object)
$security=if($PSVersionTable.PSVersion.Major -ge 6){[Microsoft.Win32.RegistryAclExtensions]::GetAccessControl($k)}else{$k.GetAccessControl()}
$acl=$security.GetSecurityDescriptorSddlForm([Security.AccessControl.AccessControlSections]::Access -bor [Security.AccessControl.AccessControlSections]::Owner -bor [Security.AccessControl.AccessControlSections]::Group)
}finally{if($k){$k.Dispose()};$base.Dispose()}
[pscustomobject][ordered]@{Values=$values;Children=$children;Access=$acl;DomainPolicy=Get-WelaRegistryState 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' AuditNTLMInDomain;NtlmChannel=Get-WelaNativeChannel 'Microsoft-Windows-NTLM/Operational';SecurityChannel=Get-WelaNativeChannel Security;NetlogonService=[string](Get-Service Netlogon).Status}
}
function Public([string]$Label,[string[]]$Arguments){
$prior=$ErrorActionPreference
try{$ErrorActionPreference='Continue';$output=& $engine -NoLogo -NoProfile -NonInteractive -File (Join-Path $repo 'WELA.ps1') outgoing-ntlm @Arguments 2>&1|Out-String;$code=$LASTEXITCODE}finally{$ErrorActionPreference=$prior}
$output|Set-Content -LiteralPath (Join-Path $root ($Label+'.txt')) -Encoding UTF8
Assert ($code -eq 0) "Public $Label exited $code : $output"
Get-Content -Raw -LiteralPath (Join-Path $root ($Label+'.json'))|ConvertFrom-Json
}
$original=Get-WelaOutgoingAuditSnapshot
Assert ($original.Host.ProductType -eq 3 -and $original.Host.DomainRole -eq 2 -and -not $original.Host.PartOfDomain) 'Actual unjoined disposable Server is required.'
Assert (-not $original.Policy.ValueExists -or ($original.Policy.Type -ceq 'DWord' -and $original.Policy.Value -in @(0,1))) 'Fixture never replaces pre-existing enforcement or an unknown policy.'
$other=Other;$masks=Masks
Save 'original.json' @{Snapshot=$original;Unselected=$other;Masks=$masks;UBR=(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').UBR;Engine=$PSVersionTable.PSVersion.ToString();Commit=$env:GITHUB_SHA}
try{
foreach($case in @('absent','allow')){
if((Get-WelaRegistryState $path $name).ValueExists){Remove-ItemProperty -LiteralPath $path -Name $name -ErrorAction Stop}
if($case -eq 'allow'){$null=New-ItemProperty -LiteralPath $path -Name $name -PropertyType DWord -Value 0}
$prepared=Get-WelaOutgoingAuditSnapshot
$plan=Public ($case+'-plan') @('-NtlmAction','Plan','-ResultsPath',(Join-Path $root ($case+'-plan.json')))
Assert ($plan.Plan.Status -ceq 'ChangeRequired' -and (Key $plan.Plan.Before) -ceq (Key $prepared)) 'Public plan retains the exact native absence/allow state and actual host.'
$dryBackup=Join-Path $root ($case+'-dry-backup')
$dry=Public ($case+'-dry') @('-NtlmAction','Configure','-Auto','-DryRun','-BackupPath',$dryBackup,'-ResultsPath',(Join-Path $root ($case+'-dry.json')))
Assert ($dry.DryRun -and $dry.Results[0].Status -ceq 'Skipped' -and -not(Test-Path $dryBackup) -and (Key (Get-WelaOutgoingAuditSnapshot)) -ceq (Key $prepared)) 'Dry run preserves policy and creates no journal directory.'
$backup=Join-Path $root ($case+'-backup')
$report=Public $case @('-NtlmAction','Configure','-Auto','-BackupPath',$backup,'-ResultsPath',(Join-Path $root ($case+'.json')))
$after=Get-WelaOutgoingAuditSnapshot
Assert ($report.Scope -ceq 'outgoing-ntlm-audit-policy-only' -and $report.Results.Count -eq 1 -and $report.Results[0].Status -ceq 'Applied') 'Exactly one native outgoing policy is applied through public CLI.'
Assert ($after.Policy.Type -ceq 'DWord' -and $after.Policy.Value -eq 1 -and (Key $report.Results[0].After) -ceq (Key $after)) 'Native audit-only readback matches the public result.'
$journal=@(Get-Content (Join-Path $backup 'before.jsonl')|ConvertFrom-Json)
Assert ($journal.Count -eq 1 -and (Key $journal[0].Before) -ceq (Key $prepared) -and $journal[0].Target.Path -ceq $path -and $journal[0].Target.Name -ceq $name) 'One original journal retains the actual typed policy and native context.'
$repeatBackup=Join-Path $root ($case+'-repeat-backup')
$repeat=Public ($case+'-repeat') @('-NtlmAction','Configure','-Auto','-BackupPath',$repeatBackup,'-ResultsPath',(Join-Path $root ($case+'-repeat.json')))
Assert ($repeat.Results[0].Status -ceq 'AlreadyCompliant' -and -not(Test-Path (Join-Path $repeatBackup 'before.jsonl'))) 'Repeated configuration is idempotent without another original journal.'
$audit=Public ($case+'-audit') @('-NtlmAction','Audit','-ResultsPath',(Join-Path $root ($case+'-audit.json')))
Assert ($audit.Plan.Status -ceq 'AlreadyCompliant' -and $audit.ReadyRuleCredit -eq 0 -and $audit.EventGeneration -like 'Not verified*') 'Audit distinguishes registry compliance from event or authentication proof.'
Assert ((Key (Other)) -ceq (Key $other) -and (Masks) -ceq $masks) 'Incoming/domain policies, siblings, access descriptor, channels, service and all59 masks remain unchanged.'
}
Save 'completed.json' @{Status='Passed';Assertions=$count;NativeWrites=2;Scope='Only outgoing audit DWORD1. No network authentication attempt, enforcement, event generation, GPO refresh or Sigma proof.'}
}catch{$failure=$_.ToString();throw}finally{
try{
if((Get-WelaRegistryState $path $name).ValueExists){Remove-ItemProperty -LiteralPath $path -Name $name -ErrorAction Stop}
if($original.Policy.ValueExists){$null=New-ItemProperty -LiteralPath $path -Name $name -Value $original.Policy.Value -PropertyType $original.Policy.Type}
}catch{$errors+=$_.ToString()}
$checks=[ordered]@{}
foreach($pair in @(@('Policy',{(Key (Get-WelaOutgoingAuditSnapshot)) -ceq (Key $original)}),@('Unselected',{(Key (Other)) -ceq (Key $other)}),@('All59Masks',{(Masks) -ceq $masks}))){try{$checks[$pair[0]]=& $pair[1]}catch{$checks[$pair[0]]=$false;$errors+=$_.ToString()}}
$complete=$errors.Count -eq 0 -and @($checks.Values|Where-Object {-not $_}).Count -eq 0
Save 'cleanup.json' @{Complete=$complete;Checks=$checks;Errors=$errors;Failure=$failure;Assertions=$count}
if(-not $complete){throw 'Outgoing NTLM native fixture cleanup failed.'}
}
Write-Host "PASS: $count native public outgoing NTLM assertions and exact cleanup."
exit 0
+2
View File
@@ -7,6 +7,8 @@
**改善:**
- `outgoing-ntlm` のAudit/Plan/Configureを追加し、送信NTLM監査DWORDを個別に設定できます。既存の拒否設定は既定で維持し、置換には明示的なAuditを要求します。不明な型・値や書込直前の変化を拒否し、元の型付き記録と再読取を保持します。Server 2022/2025のテストで範囲と復元を確認し、認証・イベント生成は未検証として報告します。(関連 #362) (@Shirofune-Security)
- Server 2022/2025 と Windows PowerShell 5.1/PowerShell 7 の破棄可能な環境で、Securityログ警告設定の公開CLIを検証します。未設定・0・高いしきい値、早い警告値の維持、DryRun、再実行、不正型の拒否と完全な復元を確認し、無関係な設定は保持します。ログ枯渇や警告イベント生成は検証範囲外です。 (@Shirofune-Security)
- Server 2022/2025 と両 PowerShell エンジンで、公開 `targeted-sacl` のレジストリ操作を検証する使い捨てテストを追加しました。テスト専用の新規ハイブをマウントし、対象選択、DryRun、監査 ACE の追加、古い計画・前提条件不足の拒否、冪等性と厳密に対応付けた Security4657 を確認します。無関係な ACE・型付き値の保持、監査ポリシー・トークンの復元、所有ハイブのアンロードと削除の証跡を保存します。製品側のハイブ読み込みや Sigma 準備完了の判定は追加しません。(関連 #373) (@Shirofune-Security)
+2
View File
@@ -7,6 +7,8 @@
**Improvements:**
- Add `outgoing-ntlm` Audit/Plan/Configure to manage the outgoing audit DWORD independently of broad configuration. Preserve existing deny by default, require explicit Audit to replace it, refuse unknown types/values and pre-write drift, and retain typed original journals plus native readback. Native Server 2022/2025 tests verify narrow scope and exact cleanup; authentication/event generation remain unverified. (Related #362) (@Shirofune-Security)
- Verify public Security-log warning configuration on disposable Server 2022/2025 hosts under Windows PowerShell 5.1/PowerShell 7: absent/zero/higher thresholds, earlier-threshold preservation, dry run, idempotence, wrong-type refusal and exact cleanup. Preserve unrelated registry values, channel configuration, audit masks and CrashOnAuditFail; no log-exhaustion or warning-event claim. (@Shirofune-Security)
- Added disposable public `targeted-sacl` registry lifecycle validation on Server 2022/2025 and both PowerShell engines. A fixture-owned mounted hive exercises reviewed selection, DryRun, additive configuration, stale/prerequisite refusal and idempotence, followed by one precisely attributed Security4657. Retained native receipts verify unrelated ACE/value preservation and exact audit-policy, token and owned-hive cleanup; production does not load hives or gain Sigma credit. (Related #373) (@Shirofune-Security)