Manual and Automated Process to resolve Unquote Service Path issues
The Risk
The remote Windows host contains services installed that use unquoted service paths, which contains at least one whitespace. A local attacker can gain elevated privileges by inserting an executable file in the path of the affected service.
The Fix
- Open the registry editor in Administrator Mode
- Goto HKLM\System\CurrentControlSet\Services
- Locate the service which has been highlighted as the issue
e.g.
- OpenVPNConnectorService
Value name: ImagePath
Value data: C:\Program Files\OpenVPN Connect\ovpnconnector.exe run
- OpenVPNConnectorService
- Enclose the path in quote marks
e.g.
- OpenVPNConnectorService
Value name: ImagePath
Value data: "C:\Program Files\OpenVPN Connect\ovpnconnector.exe" run
- OpenVPNConnectorService
Also
You can search for any "Unquoted Path" issues using the following PowerShell command.
$pat='^\s*(?:"(?<bin>[^"]+)"|(?<bin>\S+))'; Get-CimInstance Win32_Service | Where-Object { $_.StartMode -eq 'Auto' -and $_.PathName -and $_.PathName -notmatch '(?i)\\Windows\\|%SystemRoot%\\' -and ($_.PathName -match $pat) -and ($Matches['bin'] -match '\s') -and ($_.PathName.TrimStart() -notlike '"*') } | Select-Object Name,DisplayName,PathName,StartMode | Format-Table -AutoSize
You can also run a script to modify any identified paths, either as a one-time task or as a recurring task within an RMM tool or similar. Note the below with a -WhatIf parameter will advise on what it found and "would have completed".
<#
Fix-UnquotedServicePaths.ps1
- Quotes unquoted service ImagePath binaries that contain spaces.
- Preserves REG_EXPAND_SZ vs REG_SZ.
- Run as Administrator (self-elevates). Use -WhatIf first to preview.
#>
param([switch]$WhatIf)
# --- Self-elevate if not admin ---
function Ensure-Administrator {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
if (-not $p.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {
Write-Host "Re-launching with administrative privileges..."
$argsList = @('-NoProfile','-ExecutionPolicy','Bypass','-File',"`"$PSCommandPath`"")
if ($WhatIf) { $argsList += '-WhatIf' }
Start-Process -FilePath 'powershell.exe' -ArgumentList $argsList -Verb RunAs
exit
}
}
Ensure-Administrator
Write-Host "Running in Admin Mode" -ForegroundColor Yellow
Write-Host "Checking for unquoted service ImagePath values..." -ForegroundColor Cyan
$svcKeys = Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services' -ErrorAction SilentlyContinue
if (-not $svcKeys) { Write-Host "No services found."; exit }
# Regex: capture a quoted or unquoted binary, then any remaining arguments
# bin = executable path (quoted or first token)
# args = remainder (may be empty)
$pattern = '^\s*(?:"(?<bin>[^"]+)"|(?<bin>\S+))\s*(?<args>.*)$'
$changed = 0
foreach ($k in $svcKeys) {
# Read ImagePath (REG_SZ or REG_EXPAND_SZ)
$raw = $null
try { $raw = [string](Get-ItemPropertyValue -Path $k.PSPath -Name ImagePath -ErrorAction Stop) } catch { }
if ([string]::IsNullOrWhiteSpace($raw)) { continue }
# Parse into binary + args (case-insensitive)
if ($raw -notmatch $pattern) { continue }
$bin = $Matches['bin']
$args = $Matches['args']
# Only touch if the executable path contains spaces AND is not already correctly quoted
$needsQuote = $bin -match '\s'
if (-not $needsQuote) { continue }
$new = '"' + $bin + '"'
if ($args) { $new += ' ' + $args.Trim() }
# If effectively identical, skip
if ($new.Trim() -eq $raw.Trim()) { continue }
# Preserve value kind (REG_EXPAND_SZ vs REG_SZ) when writing
try {
$regKey = (Get-Item -Path $k.PSPath).OpenSubKey('', $true)
$kind = $regKey.GetValueKind('ImagePath')
} catch {
Write-Warning "[$($k.PSChildName)] Unable to read registry value kind: $($_.Exception.Message)"
continue
}
Write-Host "[$($k.PSChildName)]" -ForegroundColor Gray
Write-Host " Old: $raw"
Write-Host " New: $new"
try {
$setParams = @{
Path = $k.PSPath
Name = 'ImagePath'
Value = $new
ErrorAction = 'Stop'
}
if ($kind -eq [Microsoft.Win32.RegistryValueKind]::ExpandString) {
$setParams['Type'] = 'ExpandString'
}
Set-ItemProperty @setParams -WhatIf:$WhatIf
$changed++
}
catch {
Write-Warning " Failed to update: $($_.Exception.Message)"
}
}
if ($WhatIf) {
$msg = if ($changed -eq 1) {
"`nDry run complete. Would update 1 entry. Use without -WhatIf to apply."
} else {
"`nDry run complete. Would update $changed entries. Use without -WhatIf to apply."
}
Write-Host $msg -ForegroundColor Yellow
} else {
$msg = if ($changed -eq 1) {
"`nCompleted. Updated 1 entry."
} else {
"`nCompleted. Updated $changed entries."
}
Write-Host $msg -ForegroundColor Green
}