Skip to content

Instantly share code, notes, and snippets.

@jborean93
Last active November 28, 2022 14:34
Show Gist options
  • Save jborean93/4c0f090df3db0b3f05bffbb131e8daee to your computer and use it in GitHub Desktop.
Save jborean93/4c0f090df3db0b3f05bffbb131e8daee to your computer and use it in GitHub Desktop.
Functions to help set up a KDC proxy server and add client proxy servers - https://syfuhs.net/kdc-proxy-for-remote-access
# Copyright: (c) 2022, Jordan Borean (@jborean93) <jborean93@gmail.com>
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
Function Install-KDCProxyServer {
<#
.SYNOPSIS
Set up a KDC Proxy server.
.DESCRIPTION
Sets up the KDC proxy server on the current host.
This is done by reserving a URL and mapping a certificate to that URL.
Optional settings are applied before the KDC proxy service is set to start automatically and enabled.
.PARAMETER CertificateThumbprint
The thumbprint of the certificate in the certificate store to use for the KDC proxy.
This must be set and should be signed by a CA that is trusted by the clients otherwise they will fail to connect.
.PARAMETER Path
The URL path to assign the proxy to.
This defaults to 'KdcProxy'.
.PARAMETER Port
The port to bind the proxy against.
This defaults to 443.
.PARAMETER DisableCertificateAuth
Disabled certificate authentication support on the proxy.
Certificate auth is used by smart card credentials or Windows Hello for authentication.
.PARAMETER AllowPasswordAuth
Enables use of password authentication on the proxy.
.EXAMPLE Create a self signed certificate and setup proxy with password auth
$cert = New-SelfSignedCertificate -DnsName "proxy.realm.com" -CertStoreLocation "cert:\LocalMachine\My"
Install-KDCProxyServer -CertificateThumbprint $cert.Thumbprint -AllowPasswordAuth
New-NetFirewallRule -DisplayName "KDC Proxy" -Direction Inbound -LocalPort 443 -Protocol TCP -Action Allow
.EXAMPLE Create KDCProxy with custom port and path
$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.FriendlyName -eq "KDCProxy" }
Install-KDCProxyServer -CertificateThumbprint $cert.Thumbprint -Port 8443 -Path SpecialKdcProxy
New-NetFirewallRule -DisplayName "KDC Proxy" -Direction Inbound -LocalPort 8443 -Protocol TCP -Action Allow
.NOTES
This can only be run once and will fail if a URL reservation is set.
To remove all existing registrations run the following:
netsh http delete urlacl url=https://+:443/KdcProxy
netsh http delete sslcert ipport=0.0.0.0:443
Replace the port and URL path if a custom one was used.
If using Windows Firewall, the port also needs to be opened to allow inbound access.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]
$CertificateThumbprint,
[string]
$Path = "KdcProxy",
[int]
$Port = 443,
[switch]
$DisableCertificateAuth,
[switch]
$AllowPasswordAuth
)
$invokeNetsh = {
$netshArgs = $args
Write-Verbose -Message "Calling netsh $netshArgs"
$stdout = $null
$stderr = . { netsh.exe @netshArgs | Set-Variable stdout } 2>&1 | ForEach-Object ToString
$rc = $LASTEXITCODE
$formattedStdout = (($stdout -split "\r?\n" | Where-Object { $_ }) -join " - ").Trim()
$formattedStderr = (($stderr -split "\r?\n" | Where-Object { $_ }) -join " - ").Trim()
Write-Verbose -Message "Netsh STDOUT: $formattedStdout"
Write-Verbose -Message "Netsh STDERR: $formattedStderr"
Write-Verbose -Message "Netsh RC: $rc"
if ($rc) {
throw "Netsh failed - STDOUT: $stdout`nSTDERR: $stderr`nRC: $rc"
}
$stdout
}
$appId = [Guid]::NewGuid()
$url = "https://+:$Port/$Path"
$urlGroup = $null
if ($Port -ne 443 -or $Path -ne "KdcProxy") {
# If not using the defaults then a special string needs to be set in the registry.
$urlGroup = "+:$Port"
if ($Path -ne "KdcProxy") {
$urlGroup += ":$Path"
}
}
$networkServiceSid = [System.Security.Principal.SecurityIdentifier]::new(
[System.Security.Principal.WellKnownSidType]::NetworkServiceSid,
$null
)
$networkService = $networkServiceSid.Translate([System.Security.Principal.NTAccount]).Value
try {
Write-Verbose -Message "Reserving $url for $networkService"
&$invokeNetsh http add urlacl url=$url "user=`"$networkService`"" | Out-Null
}
catch {
$err = [System.Management.Automation.ErrorRecord]::new(
$_.Exception,
"UrlAclAdd.Error",
[System.Management.Automation.ErrorCategory]::InvalidOperation,
$url
)
$PSCmdlet.WriteError($err)
return
}
try {
Write-Verbose -Message "Reserving certificate thumbprint to KDCProxy URL"
&$invokeNetsh http add sslcert ipport=0.0.0.0:$Port certhash=$CertificateThumbprint "appid=`"{$appId}`"" | Out-Null
}
catch {
$err = [System.Management.Automation.ErrorRecord]::new(
$_.Exception,
"SslCertAdd.Error",
[System.Management.Automation.ErrorCategory]::InvalidOperation,
$url
)
$PSCmdlet.WriteError($err)
return
}
$regParams = @{
LiteralPath = "HKLM:\SYSTEM\CurrentControlSet\Services\KPSSVC\Settings"
Force = $True
}
if ($urlGroup) {
Write-Verbose -Message "Setting custom binding for KPSSVC '$urlGroup'"
New-ItemProperty @regParams -Name HttpsUrlGroup -Value $urlGroup -PropertyType MultiString | Out-Null
}
if ($DisableCertificateAuth) {
Write-Verbose -Message "Disabling certificate authentication"
New-ItemProperty @regParams -Name HttpsClientAuth -Value 0 -PropertyType DWord | Out-Null
}
if ($AllowPasswordAuth) {
Write-Verbose -Message "Enabling password authentication"
New-ItemProperty @regParams -Name DisallowUnprotectedPasswordAuth -Value 0 -PropertyType DWord | Out-Null
}
Write-Verbose -Message "Setting KDC Proxy service kpssvc to automatic and start it"
Set-Service -Name kpssvc -StartupType Automatic -Status Running
}
Function Add-KDCProxyServer {
<#
.SYNOPSIS
Add KDC proxy client configuration.
.DESCRIPTION
Adds KDC proxy server client configuration settings through the registry.
.PARAMETER Server
The proxy URI to register as a client KDC proxy.
If no port is set then the scheme default is used.
If no path is set then KdcProxy is used.
If -Realm is not defined then the realm is derived from the hostname in the proxy server URI.
For example https://test.realm.com/ will register a proxy 'https://test.realm.com:443/KdcProxy' for 'realm.com'.
.PARAMETER Realm
Use this as the realm to register the proxy with rather than deriving it from the proxy URI.
.PARAMETER SkipCertificateCheck
Disable certificate verification for https endpoints.
Setting this will also disable the certificate check performed by the cmdlet if a https proxy is set.
This should only be used for testing purposes.
.PARAMETER ForceProxy
Forces the the use of the KdcProxy for the realms configured if set.
This is useful to avoid using the typical connection to the KDC if it's resolveable but ultimately unreachable.
This is applied globally, if omitted from the call it will be unset.
.EXAMPLE Add KDC proxy server with explicit realm
Add-KDCProxyServer -Server https://proxy.contoso.com/ -Realm testing.contoso.com
.EXAMPLE Add multiple KDC proxy servers
'https://proxy.athena.com/', 'https://proxy.mit.test.com:8443/KdcEndpoint | Add-KDCProxyServer
.NOTE
A reboot is not required after adding a new proxy entry but LSA might have cached an existing ticket/session for a
server requiring a logoff and back on for it to then try a new authentication attempt. A reboot is required once a
proxy mapping has been removed.
FUTURE look at configuring the following reg keys (comments from Steve Syfuhs)
ProxyUsageLevel - DWORD? Use system creds for pre-pre-auth
ProxyCacheExpiry - DWORD? Expiry is how long to skip it (maybe minutes)
TrustSuppliedProxyServer - DWORD? Trust dictates whether the system accepts client-supplied proxy info vs system registry
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[uri[]]
[Alias("InputObject")]
$Server,
[Parameter(ValueFromPipelineByPropertyName)]
[string]
$Realm,
[switch]
$SkipCertificateCheck,
[switch]
$ForceProxy
)
begin {
$proxyReg = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos"
}
process {
$mappingReg = "$proxyReg\KdcProxy\ProxyServers"
if (-not (Test-Path -LiteralPath $mappingReg)) {
New-Item -Path $mappingReg -Force | Out-Null
}
$explicitRealm = [bool]$Realm
foreach ($proxyServer in $Server) {
try {
if (-not $SkipCertificateCheck -and $proxyServer.Scheme -eq 'https') {
# If certs verification is not ignored then ensure the client trusts the cert and if not display
# a warning to help with debugging.
$tcp = [System.Net.Sockets.TcpClient]::new()
$ssl = $null
try {
$tcp.Connect($proxyServer.DnsSafeHost, $proxyServer.Port)
$ssl = [System.Net.Security.SslStream]::New($tcp.GetStream(), $false, { $true })
$ssl.AuthenticateAsClient($proxyHost)
}
catch [System.Security.Authentication.AuthenticationException] {
$msg = "KDCProxy cert verify failed, the proxy will likely not work: $($_.Exception.Message)"
Write-Warning -Message $msg
}
finally {
if ($ssl) { $ssl.Dispose() }
$tcp.Dispose()
}
}
if (-not $explicitRealm) {
$Realm = $proxyServer.Host.Split('.', 2)[1]
if (-not $Realm) {
throw "Failed to derive realm from $proxyServer.Authority"
}
}
$proxyPath = "KdcProxy"
if ($proxyServer.AbsolutePath -ne "/") {
$proxyPath = $proxyServer.AbsolutePath.Substring(1)
}
$proxyValue = "<$($proxyServer.Scheme) $($proxyServer.IdnHost):$($proxyServer.Port):$proxyPath />"
Write-Verbose -Message "Writing proxy entry for '$Realm' - $proxyValue"
New-ItemProperty -Path $mappingReg -Name $Realm -Value $proxyValue -Force | Out-Null
}
catch {
$PSCmdlet.WriteError($_)
}
}
}
end {
$regParams = @{
PropertyType = 'DWord'
Force = $true
}
Write-Verbose -Message "Enabling KdcProxy client configuration"
if (-not (Test-Path -LiteralPath $proxyReg)) {
New-Item -Path $proxyReg -Force | Out-Null
}
New-ItemProperty -LiteralPath $proxyReg -Name KdcProxyServer_Enabled -Value 1 @regParams | Out-Null
$kerbParametersReg = "$proxyReg\Parameters"
if (-not (Test-Path -LiteralPath $kerbParametersReg)) {
New-Item -Path $kerbParametersReg -Force | Out-Null
}
if ($SkipCertificateCheck) {
Write-Verbose -Message "Disabling certificate verification of KDC proxies"
New-ItemProperty -LiteralPath $kerbParametersReg -Name NoRevocationCheck -Value 0 @regParams | Out-Null
}
elseif (Get-ItemProperty -LiteralPath $kerbParametersReg -Name NoRevocationCheck -ErrorAction SilentlyContinue) {
Write-Verbose -Message "Enabling certificate verification of KDC proxies"
Remove-ItemProperty -LiteralPath $kerbParametersReg -Name NoRevocationCheck -Force
}
if ($ForceProxy) {
Write-Verbose -Message "Setting ForceProxy global configuration"
New-ItemProperty -LiteralPath $kerbParametersReg -Name ForceProxy -Value 1 @regParams | Out-Null
}
elseif (Get-ItemProperty -LiteralPath $kerbParametersReg -Name ForceProxy -ErrorAction SilentlyContinue) {
Write-Verbose -Message "Removing ForceProxy setting of KDC proxies"
Remove-ItemProperty -LiteralPath $kerbParametersReg -Name ForceProxy -Force
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment