Private/AD/Core/Get-ADNetworkConfig.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 Get-ADNetworkConfig { <# .SYNOPSIS Collects domain-wide network-layer security policy from SYSVOL. .DESCRIPTION Reads the security-settings INI (GptTmpl.inf) from the Default Domain Policy and Default Domain Controllers Policy in SYSVOL. Parses [Registry Values] and [Service General Setting] sections to surface the settings that govern the NTLM-relay, LLMNR/NetBIOS poisoning, and IPv6/mitm6 attack chains. Custom GPOs that override these defaults are NOT walked in this MVP — auditors who use non-default GPOs to set these values will see this collector report "Not configured" and should rely on the verbose Details payload to investigate. #> [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable]$Connection, [switch]$Quiet ) $result = @{ SysvolReadable = $false DefaultDomainPolicy = $null DefaultDCPolicy = $null Errors = @() } # Derive DNS domain name from the DN, e.g. "DC=contoso,DC=com" -> "contoso.com" $domainDns = ($Connection.DomainDN -replace '^DC=', '' -replace ',DC=', '.').ToLower() if (-not $domainDns) { $result.Errors += 'Could not derive DNS domain name from connection.' return $result } # Well-known GUIDs for the two default GPOs that ship with every AD domain $ddpGuid = '{31B2F340-016D-11D2-945F-00C04FB984F9}' # Default Domain Policy $ddcpGuid = '{6AC1786C-016F-11D2-945F-00C04fB984F9}' # Default Domain Controllers Policy $sysvolRoot = "\\$domainDns\SYSVOL\$domainDns\Policies" $gptRelative = 'MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf' Write-Verbose "Reading default-policy security settings from $sysvolRoot" foreach ($spec in @( @{ Key = 'DefaultDomainPolicy'; Guid = $ddpGuid; Label = 'Default Domain Policy' } @{ Key = 'DefaultDCPolicy'; Guid = $ddcpGuid; Label = 'Default Domain Controllers Policy' } )) { $path = Join-Path (Join-Path $sysvolRoot $spec.Guid) $gptRelative try { if (-not (Test-Path -LiteralPath $path -ErrorAction Stop)) { $result.Errors += "$($spec.Label): GptTmpl.inf not found at $path (policy has no security-settings section, or insufficient SYSVOL access)." continue } $result.SysvolReadable = $true $content = Get-Content -LiteralPath $path -Raw -ErrorAction Stop $result[$spec.Key] = ConvertFrom-GptTmpl -Content $content } catch { $result.Errors += "$($spec.Label): $($_.Exception.Message)" } } return $result } function ConvertFrom-GptTmpl { <# .SYNOPSIS Parses the INI-style GptTmpl.inf content into a structured hashtable. .DESCRIPTION GptTmpl.inf is a Microsoft security-template INI. Sections of interest: [Registry Values] key=type,value (type 4 = REG_DWORD) [Service General Setting] ServiceName,StartType,"AclDescriptor" [System Access] key = value Returns: @{ Registry = @{ '<full key path>' = @{ Type=int; Value=string } } Services = @{ '<service>' = @{ StartType=int; Acl=string } } SystemAccess = @{ '<key>' = '<value>' } } #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Content ) $parsed = @{ Registry = @{} Services = @{} SystemAccess = @{} } $currentSection = $null # Strip a UTF-16 BOM if present (Windows secedit emits these as UTF-16 LE) $Content = $Content -replace "^", '' foreach ($rawLine in ($Content -split "\r?\n")) { $line = $rawLine.Trim() if (-not $line) { continue } if ($line.StartsWith(';')) { continue } if ($line.StartsWith('#')) { continue } if ($line -match '^\[(.+)\]$') { $currentSection = $Matches[1].Trim() continue } switch -Regex ($currentSection) { '^Registry Values$' { # key=type,value (value may contain commas inside quotes) if ($line -match '^([^=]+?)\s*=\s*(\d+)\s*,\s*(.*)$') { $key = $Matches[1].Trim() $type = [int]$Matches[2] $val = $Matches[3].Trim() $parsed.Registry[$key] = @{ Type = $type; Value = $val } } } '^Service General Setting$' { # ServiceName,StartType,"AclDescriptor" if ($line -match '^([^,]+?)\s*,\s*(\d+)\s*,?\s*(.*)$') { $svc = $Matches[1].Trim() $start = [int]$Matches[2] $acl = $Matches[3].Trim().Trim('"') $parsed.Services[$svc] = @{ StartType = $start; Acl = $acl } } } '^System Access$' { if ($line -match '^([^=]+?)\s*=\s*(.*)$') { $parsed.SystemAccess[$Matches[1].Trim()] = $Matches[2].Trim() } } default { } } } return $parsed } |