Private/AD/Checks/Invoke-ADNetworkChecks.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Invoke-ADNetworkChecks { [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable]$AuditData ) $checkDefs = Get-AuditCategoryDefinitions -Category 'ADNetworkChecks' $findings = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($check in $checkDefs.checks) { $funcName = "Test-Recon$($check.id -replace '-', '')" if (Get-Command $funcName -ErrorAction SilentlyContinue) { try { $finding = & $funcName -AuditData $AuditData -CheckDefinition $check if ($finding) { $findings.Add($finding) } } catch { $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'ERROR' ` -CurrentValue "Check failed: $_")) } } else { $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'SKIP' ` -CurrentValue 'Check not yet implemented')) } } return @($findings) } # Helper: parse a [Registry Values] DWORD entry's numeric value. # GptTmpl writes DWORDs as "4,<decimal>" but admins occasionally enter "0x<hex>". # [Convert]::ToInt32 doesn't accept the 0x prefix in the (string,int) overload, so # strip it. Wrap in try/catch — pathological values shouldn't take a check down. function ConvertTo-PolicyRegInt { param($Entry) if (-not $Entry) { return $null } $raw = "$($Entry.Value)".Trim() if (-not $raw) { return $null } if ($raw -match '^0x([0-9a-fA-F]+)$') { try { return [int]([Convert]::ToInt32($Matches[1], 16)) } catch { return $null } } if ($raw -match '^-?\d+$') { try { return [int]$raw } catch { return $null } } return $null } # Helper: read a DWORD from DefaultDCPolicy registry values function Get-DCPolicyReg { param( [hashtable]$NetworkConfig, [string]$KeyPath ) if (-not $NetworkConfig.DefaultDCPolicy) { return $null } return ConvertTo-PolicyRegInt -Entry $NetworkConfig.DefaultDCPolicy.Registry[$KeyPath] } function Get-DDPolicyReg { param( [hashtable]$NetworkConfig, [string]$KeyPath ) if (-not $NetworkConfig.DefaultDomainPolicy) { return $null } return ConvertTo-PolicyRegInt -Entry $NetworkConfig.DefaultDomainPolicy.Registry[$KeyPath] } # Helper: read a service start type from either Default Domain Policy or Default DC Policy. function Get-PolicyServiceStart { param( [hashtable]$NetworkConfig, [string]$ServiceName, [ValidateSet('DDP', 'DDCP')] [string]$Source ) $section = if ($Source -eq 'DDP') { $NetworkConfig.DefaultDomainPolicy } else { $NetworkConfig.DefaultDCPolicy } if (-not $section) { return $null } $svc = $section.Services[$ServiceName] if (-not $svc) { return $null } return [int]$svc.StartType } # Helper: produce a SKIP finding when the network config payload is unavailable. function New-NetworkSkipFinding { param( [hashtable]$CheckDefinition, [string]$Reason ) return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' -CurrentValue $Reason } # ── ADNET-001: LDAP Signing Required on DCs ──────────────────────────────── function Test-ReconADNET001 { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$AuditData, [Parameter(Mandatory)][hashtable]$CheckDefinition ) $net = $AuditData.Network if (-not $net) { return New-NetworkSkipFinding -CheckDefinition $CheckDefinition ` -Reason 'Network policy data not available (SYSVOL unreadable or collection skipped).' } if (-not $net.DefaultDCPolicy) { return New-NetworkSkipFinding -CheckDefinition $CheckDefinition ` -Reason 'Default Domain Controllers Policy GptTmpl.inf not readable.' } $val = Get-DCPolicyReg -NetworkConfig $net ` -KeyPath 'MACHINE\System\CurrentControlSet\Services\NTDS\Parameters\LDAPServerIntegrity' if ($null -eq $val) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'LDAPServerIntegrity not set in Default Domain Controllers Policy (DC default may apply, but it is not policy-enforced)' ` -Details @{ ConfiguredValue = $null; RequiredValue = 2 } } if ($val -eq 2) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'Default Domain Controllers Policy requires LDAP signing (LDAPServerIntegrity = 2)' ` -Details @{ ConfiguredValue = 2 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Default Domain Controllers Policy sets LDAPServerIntegrity = $val (0 = None, 1 = Negotiate; should be 2 = Require)" ` -Details @{ ConfiguredValue = $val; RequiredValue = 2 } } # ── ADNET-002: LDAP Channel Binding Enforced on DCs ──────────────────────── function Test-ReconADNET002 { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$AuditData, [Parameter(Mandatory)][hashtable]$CheckDefinition ) $net = $AuditData.Network if (-not $net -or -not $net.DefaultDCPolicy) { return New-NetworkSkipFinding -CheckDefinition $CheckDefinition ` -Reason 'Default Domain Controllers Policy not readable from SYSVOL.' } $val = Get-DCPolicyReg -NetworkConfig $net ` -KeyPath 'MACHINE\System\CurrentControlSet\Services\NTDS\Parameters\LdapEnforceChannelBinding' if ($null -eq $val) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'LdapEnforceChannelBinding not set in Default Domain Controllers Policy' ` -Details @{ ConfiguredValue = $null; RequiredValue = 2 } } if ($val -eq 2) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'Default Domain Controllers Policy enforces LDAP channel binding (LdapEnforceChannelBinding = 2)' ` -Details @{ ConfiguredValue = 2 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Default Domain Controllers Policy sets LdapEnforceChannelBinding = $val (0 = Never, 1 = When supported; should be 2 = Always)" ` -Details @{ ConfiguredValue = $val; RequiredValue = 2 } } # ── ADNET-003: SMB Server Signing Required ───────────────────────────────── function Test-ReconADNET003 { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$AuditData, [Parameter(Mandatory)][hashtable]$CheckDefinition ) $net = $AuditData.Network if (-not $net -or -not $net.DefaultDomainPolicy) { return New-NetworkSkipFinding -CheckDefinition $CheckDefinition ` -Reason 'Default Domain Policy not readable from SYSVOL.' } $val = Get-DDPolicyReg -NetworkConfig $net ` -KeyPath 'MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\RequireSecuritySignature' if ($null -eq $val) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'RequireSecuritySignature (LanManServer) not set in Default Domain Policy' ` -Details @{ ConfiguredValue = $null; RequiredValue = 1 } } if ($val -eq 1) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'Default Domain Policy requires SMB server signing' ` -Details @{ ConfiguredValue = 1 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Default Domain Policy sets LanManServer\RequireSecuritySignature = $val (should be 1)" ` -Details @{ ConfiguredValue = $val; RequiredValue = 1 } } # ── ADNET-004: SMB Client Signing Required ───────────────────────────────── function Test-ReconADNET004 { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$AuditData, [Parameter(Mandatory)][hashtable]$CheckDefinition ) $net = $AuditData.Network if (-not $net -or -not $net.DefaultDomainPolicy) { return New-NetworkSkipFinding -CheckDefinition $CheckDefinition ` -Reason 'Default Domain Policy not readable from SYSVOL.' } $val = Get-DDPolicyReg -NetworkConfig $net ` -KeyPath 'MACHINE\System\CurrentControlSet\Services\LanmanWorkstation\Parameters\RequireSecuritySignature' if ($null -eq $val) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'RequireSecuritySignature (LanmanWorkstation) not set in Default Domain Policy' ` -Details @{ ConfiguredValue = $null; RequiredValue = 1 } } if ($val -eq 1) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'Default Domain Policy requires SMB client signing' ` -Details @{ ConfiguredValue = 1 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Default Domain Policy sets LanmanWorkstation\RequireSecuritySignature = $val (should be 1)" ` -Details @{ ConfiguredValue = $val; RequiredValue = 1 } } # ── ADNET-005: LLMNR Disabled ────────────────────────────────────────────── # Note: the LLMNR control is delivered via an administrative template, which writes # to Registry.pol (binary PReg format) rather than GptTmpl.inf. This MVP collector # does not parse Registry.pol, so we can confirm a PASS only if an admin has set # the same value via the security-settings [Registry Values] section (uncommon). # Absence from GptTmpl.inf does NOT prove the mitigation is missing — but it does # mean we couldn't verify, which is worth a WARN. function Test-ReconADNET005 { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$AuditData, [Parameter(Mandatory)][hashtable]$CheckDefinition ) $net = $AuditData.Network if (-not $net -or -not $net.DefaultDomainPolicy) { return New-NetworkSkipFinding -CheckDefinition $CheckDefinition ` -Reason 'Default Domain Policy not readable from SYSVOL.' } $val = Get-DDPolicyReg -NetworkConfig $net ` -KeyPath 'MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\EnableMulticast' if ($null -eq $val) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'LLMNR EnableMulticast not in GptTmpl.inf. The administrative-template path (Registry.pol) is not parsed by this MVP — verify directly: gpresult /h or registry key HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\EnableMulticast on a member host.' ` -Details @{ ConfiguredValue = $null; RequiredValue = 0; Caveat = 'Registry.pol not parsed in MVP' } } if ($val -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'LLMNR disabled via Default Domain Policy security settings (EnableMulticast = 0)' ` -Details @{ ConfiguredValue = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Default Domain Policy sets DNSClient\EnableMulticast = $val (should be 0 to disable LLMNR)" ` -Details @{ ConfiguredValue = $val; RequiredValue = 0 } } # ── ADNET-006: NetBIOS over TCP/IP Configuration ─────────────────────────── function Test-ReconADNET006 { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$AuditData, [Parameter(Mandatory)][hashtable]$CheckDefinition ) # No reliable signal in GPO security settings for NBT — it's interface-specific. # Surface the gap so the auditor knows to check DHCP/imaging. return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'NetBIOS over TCP/IP cannot be disabled via standard GPO security settings; verify out-of-band (DHCP option 1, registry GPO, or imaging baseline)' ` -Details @{ Note = 'Per-interface registry under Tcpip\Parameters\Interfaces\<GUID>\NetbiosOptions; not detectable from SYSVOL alone' } } # ── ADNET-007: IPv6 mitm6 Mitigation ─────────────────────────────────────── function Test-ReconADNET007 { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$AuditData, [Parameter(Mandatory)][hashtable]$CheckDefinition ) $net = $AuditData.Network if (-not $net -or -not $net.DefaultDomainPolicy) { return New-NetworkSkipFinding -CheckDefinition $CheckDefinition ` -Reason 'Default Domain Policy not readable from SYSVOL.' } $val = Get-DDPolicyReg -NetworkConfig $net ` -KeyPath 'MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\DisabledComponents' if ($null -eq $val) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'DisabledComponents not in GptTmpl.inf. This is typically set via administrative template (Registry.pol) which the MVP does not parse — verify directly: registry HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\DisabledComponents on a member host. Either set it to 0xFF if IPv6 is unused, or confirm RA Guard / DHCPv6 Guard at the switching layer.' ` -Details @{ ConfiguredValue = $null; Caveat = 'Registry.pol not parsed in MVP' } } # 0xFF (255) = disable all components; 0x20 = prefer IPv4 over IPv6 (weaker); 0x0 = default if ($val -eq 0xFF) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'IPv6 components disabled via GPO (DisabledComponents = 0xFF) — mitm6 attack vector blocked' ` -Details @{ ConfiguredValue = '0xFF' } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue ("IPv6 DisabledComponents = $val (decimal). Not fully disabled. Confirm RA Guard / DHCPv6 Guard at the switching layer, or set to 0xFF if IPv6 is unused.") ` -Details @{ ConfiguredValue = $val } } # ── ADNET-008: WPAD Auto-Discovery Disabled ──────────────────────────────── function Test-ReconADNET008 { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$AuditData, [Parameter(Mandatory)][hashtable]$CheckDefinition ) $net = $AuditData.Network if (-not $net -or -not $net.DefaultDomainPolicy) { return New-NetworkSkipFinding -CheckDefinition $CheckDefinition ` -Reason 'Default Domain Policy not readable from SYSVOL.' } $wpadSvcStart = Get-PolicyServiceStart -NetworkConfig $net -ServiceName 'WinHttpAutoProxySvc' -Source 'DDP' if ($wpadSvcStart -eq 4) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'WinHttpAutoProxySvc disabled by Default Domain Policy (no WPAD lookups will occur)' ` -Details @{ ServiceStartType = 4 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'WinHttpAutoProxySvc not disabled by Default Domain Policy. Verify the DNS GlobalQueryBlockList contains "wpad" out-of-band: dnscmd /Info /GlobalQueryBlockList' ` -Details @{ ServiceStartType = $wpadSvcStart } } # ── ADNET-009: Print Spooler Service on Domain Controllers ───────────────── function Test-ReconADNET009 { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$AuditData, [Parameter(Mandatory)][hashtable]$CheckDefinition ) $net = $AuditData.Network if (-not $net -or -not $net.DefaultDCPolicy) { return New-NetworkSkipFinding -CheckDefinition $CheckDefinition ` -Reason 'Default Domain Controllers Policy not readable from SYSVOL.' } $spoolerStart = Get-PolicyServiceStart -NetworkConfig $net -ServiceName 'Spooler' -Source 'DDCP' if ($spoolerStart -eq 4) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'Print Spooler disabled by Default Domain Controllers Policy (PrinterBug coercion neutralized at DCs)' ` -Details @{ ServiceStartType = 4 } } if ($null -eq $spoolerStart) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue 'Print Spooler is not explicitly disabled by Default Domain Controllers Policy — relies on per-DC manual disable, which drifts' ` -Details @{ ServiceStartType = $null; RequiredStartType = 4 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Default Domain Controllers Policy sets Spooler StartType = $spoolerStart (2 = Auto, 3 = Manual; should be 4 = Disabled)" ` -Details @{ ServiceStartType = $spoolerStart; RequiredStartType = 4 } } # ── ADNET-010: WebClient Service Default State ───────────────────────────── function Test-ReconADNET010 { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$AuditData, [Parameter(Mandatory)][hashtable]$CheckDefinition ) $net = $AuditData.Network if (-not $net -or -not $net.DefaultDomainPolicy) { return New-NetworkSkipFinding -CheckDefinition $CheckDefinition ` -Reason 'Default Domain Policy not readable from SYSVOL.' } $webclientStart = Get-PolicyServiceStart -NetworkConfig $net -ServiceName 'WebClient' -Source 'DDP' if ($webclientStart -eq 4) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'WebClient (WebDAV redirector) disabled by Default Domain Policy — workstation HTTP coercion path closed' ` -Details @{ ServiceStartType = 4 } } if ($null -eq $webclientStart) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'WebClient service not explicitly disabled by Default Domain Policy. On-demand start (Windows default) — coercion via UNC-with-hostname-@-port still possible from workstations.' ` -Details @{ ServiceStartType = $null; RecommendedStartType = 4 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Default Domain Policy sets WebClient StartType = $webclientStart (should be 4 = Disabled for non-WebDAV environments)" ` -Details @{ ServiceStartType = $webclientStart; RecommendedStartType = 4 } } |