Give Me Back My Privileges! Please?

I want to tell you the story of a service account which lost all its powers (a.k.a. privileges). Windows world is getting increasingly ruthless and when the system considers you are not worthy, this is what happens. Fortunately for our service account, all is not lost, there’s still hope. In this merciless world, you can always turn to the old sages to find some comfort and support. Among them, the Task Scheduler might be willing to help and restore what was lost, provided that you ask kindly…

Sorry. No Impersonation Privileges For You.

A few weeks ago, Phillip Langlois and Edward Torkington of NCC Group published an interesting write-up about a privilege escalation vulnerability in the UPnP Device Host Service. If you’re interested in Windows Privilege Escalation bugs on Windows, you should definitely have a look at it. They explained how they were able to leverage a COM object exposed by this service to get arbitrary code execution in the context of NT AUTHORITY\LOCAL SERVICE.

Usually, in this situation, you could then elevate your privileges to NT AUTHORITY\SYSTEM thanks to the impersonation capabilities of this service account. However, in that case, it wasn’t that simple as they outlined in their conclusion.

On Windows 10, the UPnP Device Host service is configured to execute without impersonation privileges as the user NT AUTHORITY\LOCAL SERVICE with a ServiceSidType set to SERVICE_SID_TYPE_UNRESTRICTED. […] Unfortunately, this prevents elevation to NT AUTHORITY\SYSTEM via well-known methods […].

If you check the properties of the service, you’ll see that it’s executed with the -k LocalServiceAndNoImpersonation option.

And if you check the token associated to the process, you’ll see that it has only two privileges indeed.

Other services, such as Bluetooth Support for example, run with the -k LocalService option.

In this case, we can see that the privileges of the NT AUTHORITY\LOCAL SERVICE account are maintained.

Note: as a reminder, it might be a bit confusing but the Enabled/Disabled status of a privilege doesn’t matter. What matters is that it is present within the token. If so, you can enable/disable them at runtime whenever you want.

All that being said, in the case of a service such as the UPNP Device Host Service, is there a way to get all these privileges back? The answer is “yes” (otherwise there would be no point in writing this article :grin:). What is more surprising though is that it is quite simple to achieve.

Reproducing the conditions of the exploit

In order to reproduce the conditions of the UPnP Device Host Service vulnerability, I’ll use NirSoft’s RunFromProcess tool to open a bindshell as a subprocess of the service. This requires admin privileges of course. For the bindshell, I’ll use powercat. As its name implies, it’s a PowerShell implementation of netcat and it’s definitely one to have in your toolbox if you’re a pentester.

RunFromProcess usage is pretty simple. The first argument is the name or the PID of the process you want to run your executable from. The second argument is the absolute path of the executable. Then you can append a list of arguments for your executable.

RunFromProcess-x64.exe 3636 "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -ep Bypass -Command ". C:\TOOLS\powercat.ps1;powercat -l -p 7001 -ep"

Be careful, it won’t tell you whether it succeeded or not so you should check your command twice before running it. Once it’s done, we can use powercat in “client mode” to connect to the bindshell.

. .\powercat.ps1
powercat -c -p 7001

Nice! :thumbsup: We have a shell in the context of NT AUTHORITY\LOCAL SERVICE and as you can see we have only two privileges. We can therefore start working from this prompt.

The Task Scheduler Has Got Your Back!

Any user can create its own scheduled tasks in Windows and NT AUTHORITY\LOCAL SERVICE is no exception to this rule. By default, the account used to run a Task is the same as the Task’s “author”. The scheduled task creation process is done through a series of local (or remote) RPC calls but I won’t go into detail about this.

Let’s see what happens when we create a task in the context of a LOCAL SERVICE account. This can be achieved in three simple steps using PowerShell.

First, we need to create an Action object for the task. That’s where you specify the program/script to execute along with some optional arguments. Here, we want to open a bindshell on port 7002 so we will use the following command.

$TaskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-Exec Bypass -Command `". C:\TOOLS\powercat.ps1; powercat -l -p 7002 -ep`""

Then, you can “register” the task and start it manually.

Register-ScheduledTask -Action $TaskAction -TaskName "SomeTask"
Start-ScheduledTask -TaskName "SomeTask"

Let’s use powercat once again in client mode to connect to the new bindshell and…

. .\powercat.ps1
powercat -c -p 7002

…we have all our privileges back!!! :astonished:

Wait a minute… Do we really have ALL of them back?! :eyes:

According to Microsoft documentation, the local service account has the following privileges by default:

  • SE_AUDIT_NAME (disabled)
  • SE_SHUTDOWN_NAME (disabled)
  • SE_UNDOCK_NAME (disabled)
  • Any privileges assigned to users and authenticated users

All of them are present, at the exception of SE_IMPERSONATE_NAME. What’s going on?!

Going The Extra Mile

At this point, we have the SE_ASSIGNPRIMARYTOKEN_NAME and SE_INCREASE_QUOTA_NAME privileges. That’s enough for impersonating any user so we could consider it’s “game over”. However, the fact that SE_IMPERSONATE_NAME was missing was bugging me.

Therefore, I spent more time browsing Microsoft documentation in the hope of finding an explanation and I found it. It turns out that there is a small chapter dedicated to Task Security Hardening. At the very end of this chapter you can read the following.

If RequiredPrivileges is not present in the task definition, the default privileges of task principal account without the SeImpersonatePrivilege will be used for task process. If ProcessTokenSidType is not present in the task definition, “unrestricted” is used as the default.

In a single phrase, we have multiple answers.

  • A task process is created with the default privileges of the task principal account.

  • If RequiredPrivileges is not present, the default privilege set associated to the account is used without SeImpersonatePrivilege.

This explains why we were able to get our privileges back - except SeImpersonatePrivilege - simply by creating a scheduled task. However, it leads to another question: what is this RequiredPrivileges thing and can we do somehing about it?

If you take a look at the documentation of the Register-ScheduledTask PowerShell command, you’ll see that it accepts a Principal as an optional argument. You can use this Principal to run a task under the security context of a specified account.

This Principal can be created using the New-ScheduledTaskPrincipal command, which accepts the following arguments.

   [[-Id] <String>]
   [[-RunLevel] <RunLevelEnum>]
   [[-ProcessTokenSidType] <ProcessTokenSidTypeEnum>]
   [[-RequiredPrivilege] <String[]>]
   [-UserId] <String>
   [[-LogonType] <LogonTypeEnum>]
   [-CimSession <CimSession[]>]
   [-ThrottleLimit <Int32>]

Here is the RequiredPrivilege option that was mentioned in the Task Security Hardening article. This parameter specifies an array of user rights that Task Scheduler uses to run the tasks that are associated with the principal.

Note: you can find a complete list of Privilege constants here.

We have all the information we need. Let’s do this!

First, we create a String array containing all the privileges we want. Then, we pass it as a parameter to the New-ScheduledTaskPrincipal command to create the Principal object for our new scheduled task.

# Create a list of privileges 
[System.String[]]$Privs = "SeAssignPrimaryTokenPrivilege", "SeAuditPrivilege", "SeChangeNotifyPrivilege", "SeCreateGlobalPrivilege", "SeImpersonatePrivilege", "SeIncreaseQuotaPrivilege", "SeShutdownPrivilege", "SeUndockPrivilege", "SeIncreaseWorkingSetPrivilege", "SeTimeZonePrivilege"
# Create a Principal for the task 
$TaskPrincipal = New-ScheduledTaskPrincipal -UserId "LOCALSERVICE" -LogonType ServiceAccount -RequiredPrivilege $Privs

Then, we can use the same commands as before and specify our Principal object using the appropriate parameter.

# Create an action for the task 
$TaskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-Exec Bypass -Command `". C:\TOOLS\powercat.ps1; powercat -l -p 7003 -ep`""
# Create the task
Register-ScheduledTask -Action $TaskAction -TaskName "SomeTask2" -Principal $TaskPrincipal
# Start the task
Start-ScheduledTask -TaskName "SomeTask2"

Nice! :sunglasses: At least, no error/exception is triggered. Let’s see if it worked by trying to connect to the new bindshell.

It worked! And we have the SeImpersonatePrivilege privilege back! \o/

As a conclusion, I just want to say that Windows is a very complex Operating System and Microsoft has made huge efforts to harden its security over the years while still maintaining compatibility with legacy stuff. And, this turns out to be a very difficult and thankless job, especially when this same legacy stuff is also what cripples new security models.

This post is licensed under CC BY 4.0 by the author.