Tag: windows

Verifying WinRM access to CAPI

Powershell script that verifies WinRM access and lists certs from CAPI store.

# ============================================================================
# User-configurable variables
# ============================================================================

$TargetHost = ‘hostname.example.com’
$TargetLocalComputerName = ‘HOSTNAME
$Username = ‘localuserid’
$Password = ‘localuserpassword’
$Port = 5985

Set-StrictMode -Version Latest
$ErrorActionPreference = ‘Stop’

# Remote certificate store to inspect.
# Common values:
# Cert:\LocalMachine\My
# Cert:\LocalMachine\WebHosting
# Cert:\LocalMachine\Root
# Cert:\LocalMachine\CA
$RemoteCertStorePath = ‘Cert:\LocalMachine\My’

# Optional subject filter. Leave blank to return everything in the store.
$CertificateSubjectFilter = ”

# ============================================================================
# Build local SAM credential
# ============================================================================

# Unqualified – fails
# $QualifiedUsername = $Username
# .\ – works
# $QualifiedUsername = ‘{0}\{1}’ -f ‘.’, $Username
# With hostname – works
$QualifiedUsername = ‘{0}\{1}’ -f $TargetLocalComputerName, $Username

$SecurePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
$Credential = [System.Management.Automation.PSCredential]::new($QualifiedUsername, $SecurePassword)

# ============================================================================
# Check TrustedHosts on the client
# ============================================================================

$trustedHostsValue = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
$trustedHostEntries = @()

if (-not [string]::IsNullOrWhiteSpace($trustedHostsValue)) {
$trustedHostEntries = $trustedHostsValue -split ‘\s*,\s*’ | Where-Object {
-not [string]::IsNullOrWhiteSpace($_)
}
}

$trustedHostMatch = $false
foreach ($entry in $trustedHostEntries) {
if ($TargetHost -like $entry -or $TargetLocalComputerName -like $entry) {
$trustedHostMatch = $true
break
}
}

Write-Host ”
Write-Host ‘=== Client Context ===’ -ForegroundColor Cyan
[pscustomobject]@{
RunningAs = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
TargetHost = $TargetHost
Port = $Port
CredentialUser = $Credential.UserName
TrustedHostsValue = $trustedHostsValue
TrustedHostMatched = $trustedHostMatch
RemoteCertStorePath = $RemoteCertStorePath
CertificateSubjectFilter = $CertificateSubjectFilter
} | Format-List

if (-not $trustedHostMatch) {
Write-Warning “TrustedHosts does not appear to include $TargetHost or $TargetLocalComputerName. HTTP/5985 with a local account will usually fail until that is fixed.”
}

# ============================================================================
# Raw TCP check
# ============================================================================

Write-Host ”
Write-Host ‘=== TCP Connectivity Check ===’ -ForegroundColor Cyan

$tcpClient = [System.Net.Sockets.TcpClient]::new()
try {
$asyncResult = $tcpClient.BeginConnect($TargetHost, $Port, $null, $null)
if (-not $asyncResult.AsyncWaitHandle.WaitOne(3000, $false)) {
throw “Timed out connecting to $TargetHost`:$Port”
}

$null = $tcpClient.EndConnect($asyncResult)
Write-Host “TCP connect to $TargetHost`:$Port succeeded.” -ForegroundColor Green
}
catch {
Write-Host “TCP connect to $TargetHost`:$Port failed: $($_.Exception.Message)” -ForegroundColor Red
throw
}
finally {
$tcpClient.Dispose()
}

# ============================================================================
# WinRM over HTTP/5985 test + remote machine-store certificate inventory
# ============================================================================

Write-Host ”
Write-Host ‘=== WinRM HTTP/5985 Test ===’ -ForegroundColor Cyan

$session = $null

try {
$session = New-PSSession `
-ComputerName $TargetHost `
-Port $Port `
-Authentication Negotiate `
-Credential $Credential `
-ErrorAction Stop

$remoteResult = Invoke-Command -Session $session -ErrorAction Stop -ArgumentList $RemoteCertStorePath, $CertificateSubjectFilter -ScriptBlock {
param(
[string]$StorePath,
[string]$SubjectFilter
)

$latfp = $null
try {
$latfp = Get-ItemPropertyValue `
-Path ‘HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System’ `
-Name ‘LocalAccountTokenFilterPolicy’ `
-ErrorAction Stop
}
catch {
$latfp = $null
}

$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [System.Security.Principal.WindowsPrincipal]::new($identity)
$isAdmin = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)

if (-not (Test-Path -Path $StorePath)) {
throw “Certificate store path not found: $StorePath”
}

$certs = Get-ChildItem -Path $StorePath -ErrorAction Stop

if (-not [string]::IsNullOrWhiteSpace($SubjectFilter)) {
$certs = $certs | Where-Object { $_.Subject -like “*$SubjectFilter*” }
}

$certInventory = foreach ($cert in $certs | Sort-Object NotAfter, Subject) {
$sanEntries = @()
$dnsNameList = $null

try {
$dnsNameList = $cert.DnsNameList
}
catch {
$dnsNameList = $null
}

if ($null -ne $dnsNameList) {
foreach ($dnsName in $dnsNameList) {
if ($null -ne $dnsName -and -not [string]::IsNullOrWhiteSpace($dnsName.Unicode)) {
$sanEntries += $dnsName.Unicode
}
}
}

$ekuEntries = @()
foreach ($eku in $cert.EnhancedKeyUsageList) {
if (-not [string]::IsNullOrWhiteSpace($eku.FriendlyName)) {
$ekuEntries += $eku.FriendlyName
}
elseif (-not [string]::IsNullOrWhiteSpace($eku.ObjectId)) {
$ekuEntries += $eku.ObjectId
}
}

[pscustomobject]@{
StorePath = $StorePath
Subject = $cert.Subject
Thumbprint = $cert.Thumbprint
NotBefore = $cert.NotBefore
NotAfter = $cert.NotAfter
HasPrivateKey = $cert.HasPrivateKey
Issuer = $cert.Issuer
SerialNumber = $cert.SerialNumber
SignatureAlgorithm = $cert.SignatureAlgorithm.FriendlyName
PublicKeyOid = $cert.PublicKey.Oid.FriendlyName
Archived = $cert.Archived
DnsNames = ($sanEntries -join ‘; ‘)
EnhancedKeyUsage = ($ekuEntries -join ‘; ‘)
PSParentPath = $cert.PSParentPath
}
}

[pscustomobject]@{
RemoteComputerName = $env:COMPUTERNAME
RemoteIdentity = $identity.Name
IsAdministrator = $isAdmin
LocalAccountTokenFilterPolicy = $latfp
WinRMServiceStatus = (Get-Service -Name WinRM).Status.ToString()
PSVersion = $PSVersionTable.PSVersion.ToString()
QueriedStorePath = $StorePath
CertificateCount = @($certInventory).Count
Certificates = @($certInventory)
}
}

Write-Host ‘WinRM HTTP/5985 test succeeded.’ -ForegroundColor Green

Write-Host ”
Write-Host ‘=== Remote Probe Data ===’ -ForegroundColor Cyan
[pscustomobject]@{
RemoteComputerName = $remoteResult.RemoteComputerName
RemoteIdentity = $remoteResult.RemoteIdentity
IsAdministrator = $remoteResult.IsAdministrator
LocalAccountTokenFilterPolicy = $remoteResult.LocalAccountTokenFilterPolicy
WinRMServiceStatus = $remoteResult.WinRMServiceStatus
PSVersion = $remoteResult.PSVersion
QueriedStorePath = $remoteResult.QueriedStorePath
CertificateCount = $remoteResult.CertificateCount
} | Format-List

Write-Host ”
Write-Host “=== Certificates in $($remoteResult.QueriedStorePath) ===” -ForegroundColor Cyan

if ($remoteResult.CertificateCount -eq 0) {
Write-Host ‘No certificates matched the requested store/filter.’ -ForegroundColor Yellow
}
else {
$remoteResult.Certificates |
Select-Object Subject, Thumbprint, NotAfter, HasPrivateKey, DnsNames, EnhancedKeyUsage |
Format-Table -Wrap -AutoSize
}

Write-Host ”
Write-Host ‘=== Computed Summary ===’ -ForegroundColor Cyan
[pscustomobject]@{
Http5985Success = $true
RemoteIdentity = $remoteResult.RemoteIdentity
IsAdministrator = $remoteResult.IsAdministrator
LocalAccountTokenFilterPolicy = $remoteResult.LocalAccountTokenFilterPolicy
WinRMServiceStatus = $remoteResult.WinRMServiceStatus
QueriedStorePath = $remoteResult.QueriedStorePath
CertificateCount = $remoteResult.CertificateCount
} | Format-List
}
catch {
Write-Host “WinRM HTTP/5985 test failed: $($_.Exception.Message)” -ForegroundColor Red

[pscustomobject]@{
Http5985Success = $false
ErrorMessage = $_.Exception.Message
HResult = (‘0x{0:X8}’ -f ($_.Exception.HResult -band 0xffffffff))
FullyQualifiedErrorId = $_.FullyQualifiedErrorId
} | Format-List
}
finally {
if ($null -ne $session) {
Remove-PSSession -Session $session -ErrorAction SilentlyContinue
}
}

Setting Windows Dynamic Port Range

In case anyone else ever needs to set a windows dynamic port range for magic RPC “stuff” — there’s a minimum range size of 255. If you make the range to small, you get an incredibly vague and not-useful “the parameter is incorrect” error. Increase num to at least the min value, and you don’t be going in circles trying to figure out what in your command doesn’t match the parameters in the documentation!

 

Client Connections to HTTPS IIS Site Fail After Upgrade to Windows Server 2022

Client connections to the HTTPS IIS site failed with the following error:

Secure Connection Failed

An error occurred during a connection to webhost.example.com.

PR_CONNECT_RESET_ERROR

Error code: PR_CONNECT_RESET_ERROR

The page you are trying to view cannot be shown because the authenticity of the received data could not be verified. Please contact the website owners to inform them of this problem.

 

The IIS site was set to “accept” client certificates.

  • Client Certificates = Accept means IIS/HTTP.sys will try to retrieve a client certificate only if the app touches Request.ClientCertificate (or a module that maps/validates client certs). That retrieval is done via TLS renegotiation in TLS 1.2.
  • On Server 2022, browsers prefer TLS 1.3. TLS 1.3 does not support the old renegotiation used to fetch a client cert mid‑request. When your app/module at “/” accesses the client cert, IIS attempts renegotiation, fails, and the connection is reset.

Setting Client Certificates to “Ignore” in the site’s “SSL Settings” prevents IIS from attempting to renegotiate, so the site loads. This obviously isn’t a solution if you want to use client certificates to authenticate … but we’re authenticating through Ping, so don’t actually need the client certs.

PowerShell to Uninstall an Application

I was curious if, instead of getting prompted for the local admin account for each application I want to remove, I could run PowerShell “as a different user” then use it to uninstall an application or list of applications. In this case, all of the .NET 6 stuff. Answer: absolutely.

# List all installed applications containing string "NET"
# Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like '*NET*' } | Select-Object -Property Name

# Define a static list of application names to uninstall
$appsToUninstall = @(
    'Microsoft .NET Runtime - 6.0.36 (x86)',
    'Microsoft .NET Host FX Resolver - 6.0.36 (x64)',
    'Microsoft .NET Host - 6.0.36 (x64)',
    'Microsoft .NET Host FX Resolver - 6.0.36 (x86)',
    'Microsoft .NET Host - 6.0.36 (x86)',
    'Microsoft .NET Runtime - 6.0.36 (x64)',
    'Microsoft.NET.Workload.Emscripten.net6.Manifest (x64)'
)

# Loop through each application name in the static list
foreach ($appName in $appsToUninstall) {
    # Find the application object by name
    $app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -eq $appName }
    
    # Check if the application was found before attempting to uninstall
    if ($app) {
        Write-Output "Uninstalling $($app.Name)..."
        $app.Uninstall() | Out-Null
        Write-Output "$($app.Name) has been uninstalled successfully."
    }
    else {
        Write-Output "Application $appName not found."
    }
}

Office 365 Activation Failure

We’ve been working to lock down our workstations … not “so secure you cannot use it”, but just this side of the functional/nonfunctional line. Everything went surprisingly well except I use the Office 365 suite for work. Periodically, it has to “phone home” and verify my work account is still valid. And that didn’t seem to go through the proxy well. The authentication screen would pop up and immediately throw an error:

No internet connection. Please check your network settings and try again [2604]

I spent a whole bunch of time playing around with the firewall rules, the proxy rules … and finally went so far as to just turn off the firewall and remove the proxy. And it still didn’t work. Which was nice because it means I didn’t break it … but also meant it was going to be a lot harder to fix!

Finally found the culprit — a new Windows installation, for some reason, uses really old SSL/TLS versions. Turned on 1.2 and, voila, I’ve got a sign-on screen. Sigh! Turned the firewall & proxy back on, and everything works beautifully. I think I’m going to add these settings to the domain policy so I don’t have to configure this silliness every time.

ISC Bind 9.18 and Windows DNS

After upgrading all of our Linux hosts to Fedora 39, we are running ISC bind 9.18.21 … and it seems the ISC folks are finally done with Microsoft’s “kinda sorta RFC compliance”. Instead of just working around Windows DNS servers having some quirks … they now fail to AXFR the domain.

Fortunately, you can tell bind to stop doing edns ‘stuff‘ by adding a server{} section to named.conf — this gives the server some instructions on how to communicate with the listed server. When bind is no longer trying to do edns “stuff”, Windows doesn’t have an opportunity to provide a bad response, so the AXFR doesn’t fail.

Web Proxy Auto Discovery (WPAD) DNS Failure

I wanted to set up automatic proxy discovery on our home network — but it just didn’t work. The website is there, it looks fine … but it doesn’t work. Turns out Microsoft introduced some security idea in Windows 2008 that prevents Windows DNS servers from serving specific names. They “banned” Web Proxy Auto Discovery (WPAD) and Intra-site Automatic Tunnel Addressing Protocol (ISATAP). Even if you’ve got a valid wpad.example.com host recorded in your domain, Windows DNS server says “Nope, no such thing!”. I guess I can appreciate the logic — some malicious actor can hijack all of your connections by tunnelling or proxying your traffic. But … doesn’t the fact I bothered to manually create a hostname kind of clue you into the fact I am trying to do this?!?

I gave up and added the proxy config to my group policy — a few computers, then, needed to be manually configured. It worked. Looking in the event log for a completely different problem, I saw the following entry:

Event ID 6268

The global query block list is a feature that prevents attacks on your  network by blocking DNS queries for specific host names. This feature has caused the DNS server to fail a query with error code NAME ERROR for wpad.example.com. even though data for this DNS name exists in the DNS database. Other queries in all locally authoritative zones for other names
that begin with labels in the block list will also fail, but no event will be logged when further queries are blocked until the DNS server service on this computer is restarted. See product documentation for information about this feature and instructions on how to configure it.

The oddest bit is that this appears to be a substring ‘starts with’ query — like wpadlet or wpadding would also fail? A quick search produced documentation on this Global Query Blocklist … and two quick ways to resolve the issue.

(1) Change the block list to contain only the services you don’t want to use. I don’t use ISATAP, so blocking isatap* hostnames isn’t problematic:

dnscmd /config /globalqueryblocklist isatap

View the current blocklist with:

dnscmd /info /globalqueryblocklist

– Or –

(2) Disable the block list — more risk, but it avoids having to figure this all out again in a few years when a hostname starting with isatap doesn’t work for no reason!

dnscmd /config /enableglobalqueryblocklist 0

 

Verifying Connectivity From Locked Down Windows Desktop or Server

We frequently encounter individuals who cannot use something from their server or desktop — but their IT group has Windows locked down so they cannot just telnet to the destination on the port and check if it’s connecting. Windows doesn’t have a whole lot of useful tools of its own. Fortunately, I’ve found that nmap.org publishes a local install zip file for Windows.

Download latest Win32 zip file from https://nmap.org/dist/ — on 8/8/2023, that is https://nmap.org/dist/nmap-7.92-win32.zip

 

Extract the zip file contents somewhere (I use tmp, right in downloads works, whatever)
Open command prompt and change directory (cd) into the folder where nmap was extracted — e.g. cd /d c:\tmp\nmap-7.92

— A quick trick for opening a command prompt to a directory location: If you have a file explorer window open to the location, click into the header where the file path is shown and remove the text that appears there

Type cmd and hit enter

And voila — a command prompt opened to the same location you were viewing

In the command prompt, run an map command to test a specific port (-p) and host. Since some hosts do not return ICMP requests, I also include -P0 instructing nmap not to attempt pinging the host first. This example verifies we have connectivity to google.com on port 443 (https)

 

*Un*Registering SysInternals ProcMon as Task Manager Replacement

I like the sysinternals tools — I use them frequently at work. But, generally? When trying to look at the running Windows processes or how much memory is being used … I need a really small, simple tool that doesn’t add to the bogging that’s already happening. Which is why I hate when people replace taskmgr.exe with the SysInternals task manager on steroids. It’s too much information. The worst part is that the menu option to replace task manager doesn’t un-replace it.

To accomplish that — to revert to the real Windows task manager — you need to edit the registry. Navigate to HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\taskmgr.exe and remove the key named “Debugger” which points to the SysInternals binary.