The Power of SeImpersonation

The Power of SeImpersonation


SeImpersonate is a powerful privilege that allows the ability to impersonate any token it can acquire a handle on. This is an already well researched privilege as there are a whole slew of privilege escalations that utilize this privilege and amazing articles and existing tools. What I will be doing is taking a step back and looking at the common denominator between the potato family, SeImpersonatePrivilege.

What are tokens?

Before we dive straight in, it would be good to give some background on what we are even talking about. When you login to a system whether it is locally, over the network, as a service, or even directly calling the LogonUser API function the authentication package creates a logon session and then has Local Security Authority (LSA) create an access token for the user. This token represents the account security context and contains information including:

  • The ID of the logon session
  • User and group SIDs
  • The integrity level
  • Privileges held by the user or groups the user is in

Everything in a user’s session by default runs under the same token because a child process inherits the parent token. One thing to be careful of is what is called restricted tokens. This is tied to User Account Control (UAC). When you login you will actually be assigned two tokens. One being the token with full access. The other token will be a restricted or filtered token that will only have a subset of the permissions. It is the restricted token that Windows Explorer and most processes typically will run under. The most relevant part about the restricted token is that it may not even list some of the privileges that the user has. So, if we want to use the unrestricted token, UAC requires “run as administrator” and going through the elevation prompt. That is unless the parent process is already high integrity. Now there are two types of tokens: primary and impersonation. Primary tokens are only able to be attached to a process while impersonation tokens can only be attached to threads. Impersonation is how a server can assume the identity of a client and the security access that the user has. The impersonation is only temporary and overrides the primary token for just the thread until it finishes. There are several levels of impersonation tokens.

  • Anonymous is where the server has no knowledge of the client.
  • Identification is where the server can obtain the client’s identity including its SID’s and privileges to be used for access control checks. This is the most common level.
  • Impersonation is where the server can impersonate and act on behalf of the client to the local system. This is the level we really want for privilege escalation.
  • Delegation is where the server can impersonate the client on both local and remote systems.

What are privileges?

Privileges are special rights to perform various system operations. These are assigned to the user and as mentioned earlier are listed in the user token. They can be in an enabled or disabled state. This isn’t to be confused with restricted tokens. It’s fairly trivial to enable a privilege and it is merely used as a safeguard to prevent unintended actions.

Here we have a regular command prompt using the restricted token. Whoami

Note that there are only a handful of privileges listed. This is because we are using a restricted token. Also note that some of these privileges are disabled.

Here we have an elevated command prompt with many more privileges listed. Whoami Privileged

We already mentioned that SeImpersonatePrivilege can allow us to impersonate a token of a user and run under the security context of it. There is also a very similar sibling of SeImpersonatePrivilege called SeAssignPrimaryTokenPrivilege. If we have SeImpersonatePrivilege we can call CreateProcessWithTokenW() to create a process with the token we have. It’s sibling SeAssignPrimaryTokenPrivilege allows the ability to call CreateProcessAsUserA() which performs similarly. Another option would be to create a thread and set the token of the thread with either SetThreadToken() or ImpersonateLoggedOnUser(). One API call that can come in handy is DuplicateTokenEx() which will duplicate a token but you can specify the type of token you want.

Enabling Privileges

The process of enabling privileges is fairly simple and involves a handful of API calls. We first need the token of the current process. By calling GetCurrentProcess() we can get a handle to our process. With the handle we can get the token for the process with OpenProcessToken(). For this call we need to specify what access we need which is TOKEN_ADJUST_PRIVILEGES and TOKEN_QUERY. The next information needed is the locally unique identifier (LUID) of the privilege we want to enable or disable. This can be acquired by passing the privilege name to LookupPrivilegeValue(). Next we can modify the privilege on the token with a call to AdjustTokenPrivileges() with the token of our process and the LUID of the privilege. This is the needed C# code to enable a privilege:

var locallyUniqueIdentifier = new LUID();
LookupPrivilegeValue(null, securityEntityValue, ref locallyUniqueIdentifier)
TOKEN_PRIVILEGES.PrivilegeCount = 1;
TOKEN_PRIVILEGES.Luid = locallyUniqueIdentifier;
var tokenHandle = IntPtr.Zero;
var currentProcess = GetCurrentProcess();
OpenProcessToken(currentProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out tokenHandle)
AdjustTokenPrivileges(tokenHandle, false, ref TOKEN_PRIVILEGES, 1024, IntPtr.Zero, IntPtr.Zero)

Granting Privileges

We can add or remove SeImpersonatePrivilege for a user by modifying the local security policy. Local Security Policy

If you want to enable privileges programmatically it gets more complicated. You need to export the security policy, add the SID of the user you want to the privilege, and then import the modified security policy. We can export the security policy by running

secedit /export /cfg c:\secpol.cfg

Under the Privilege Rights section in the exported policy we can see where SeImpersonatePrivilege exists. Security Policy Before Modification

The SIDs that are listed are some of the default built-in accounts. We need to get the SID of our user to add here. This can be done with the following:

$account = New-Object System.Security.Principal.NTAccount('username')
$sid = $account.Translate([System.Security.Principal.SecurityIdentifier]).value

We can then use PowerShell to add our user’s SID to that line.

(gc C:\secpol.cfg).replace("SeImpersonatePrivilege = ",
    "SeImpersonatePrivilege = *$sid,") | Out-File C:\secpol.cfg

The file should now look like this. Security Policy After Modification

We can now apply the modified policy and then remove the policy file.

secedit /configure /db c:\windows\security\local.sdb /cfg c:\secpol.cfg /areas user_rights   
rm -force c:\secpol.cfg -confirm:$false

If we look in the log file from secedit we can see that our user was successfully given SeImpersonateprivilege. Successfully Configured Security Policy

The Potato Family

Potatoes can come in a wide variety of ways: Hot, Juicy, Rotten, Rogue, Sweet. However, they are all potatoes. What varies from potato exploit to exploit is the means of getting hands on the token to impersonate. This token we want to be of a higher privilege user than the current user to elevate privileges. The following is brief descriptions of the potato exploits. Notice what is different and what is the same.

HotPotato (2016)Used UDP port exhaustion to cause DNS queries to fail and fallback to NBNS. Performed NBNS spoofing to serve WPAD and force HTTP authentication. Relayed HTTP NTLM authentication to SMB. The ability to relay HTTP to SMB was fixed by Microsoft (MS16-075).
RottenPotato (2016)Forced RPC authentication with CoGetInstanceFromIStorage . Relayed NTLM authentication from the RPC connection to the local NTLM negotiator. This relay requires SeImpersonatePrivilege.
LonelyPotato (2017)An adaption of RottenPotato
JuicyPotato (2018)An adaption of RottenPotato
RogueWinRM (2019)Used CoCreateInstance() to instantiate a BITS object which causes an authentication attempt to WinRM (port 5985). If WinRM is not running, which is default in Windows 10, a listener can be started on port 5985 and the NTLM HTTP authentication can be relayed to the local NTLM negotiator to obtain a token. This also requires SeImpersonatePrivilege.
RoguePotato (2020)Microsoft broke Rotten/JuicyPotato by preventing the ability to specify a custom port for an OXID resolver address. This exploit still uses CoGetInstanceFromIStorage but uses a redirector and fake OXID RPC server. The fake OXID server resolves the request to a named pipe. Named pipes also allow for impersonation with RpcImpersonateClient(). This API call requires SeImpersonatePrivilege.
SweetPotato (2020)Includes a method of using PrintSpoofer to authenticate to a named pipe and consequently impersonate.

To help understand why SeImpersonatePrivilege exists let’s look at some places it is used. Two of the most common are IIS and MSSQL. By default, IIS_IUSRS and SERVICE are configured with this privilege. Also, any IIS application pool virtual account belongs to IIS_IUSRS and SERVICE. Microsoft doesn’t make it easy removing this privilege. You can get information on removing it here. So why is this a default privilege? It is because your web application just maybe might need to impersonate a user. IIS will use the account of the application pool when accessing system resources unless impersonation is specified in the config. If it is, then IIS will use the security context of the user logged into the site.

Grow Your Own Potato

Suppose we are on a system and the user we have access to has SeImpersonatePrivilege. However, the system doesn’t have the print service running which prevents SweetPotato. In addition, WinRM is running preventing RogueWinRM. To top it all off, you don’t have outbound RPC allowed to any machine you control and the BITS service is disabled preventing RoguePotato. What can you do? Grow your own potato. The one ingredient you need to find on the machine is a way to cause an authentication. Two simple ways are with HTTP or a file write. All we need is to cause an application or user with higher privileges to authenticate to us over HTTP or write to our named pipe. This could be an application running as SYSTEM or a local administrator user. We can turn a server-side request forgery into local privilege escalation. Here is an example of what would trigger an HTTP authentication attempt. This is what would be run from a higher privilege user.

WebRequest req = WebRequest.Create(""));
req.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
WebResponse resp = req.GetResponse();

I started looking into methods to impersonate the user making the HTTP request. The first thing I tried was modifying the SweetPotato exploit code. To do this I changed the WinRMListener method in PotatoAPI.cs to request NTLM authentication. The code does all the parsing and sending of raw HTTP requests to relay the NTLM authentication to the local negotiator. There is a great article that goes into details on SSPI & NTLMSSP here–ntlmssp. It would receive any HTTP request and send back a 401 response signaling the need for authentication.

HTTP/1.1 401 Unauthorized\r\n
 WWW-Authenticate: Negotiate\r\n
 Content-Length: 0\r\n
 Connection: Keep-alive\r\n\r\n

The client would then send back a negotiate message. It would then call AcquireCredentialsHandle and then AcceptSecurityContext with the negotiate message and respond with the challenge to the client.

HTTP/1.1 401 Unauthorized\r\n
WWW-Authenticate: Negotiate {Base64 Encoded Challenge}\r\n
Content-Length: 0\r\n
Connection: Keep-alive\r\n\r\n

It would then call AcceptSecurityContext with the challenge returned from the client and acquire a token for the security context with a call to QuerySecurityContextToken.

This worked fine but was unnecessarily complicated. We can do it all in a several lines.

HttpListener listener = new HttpListener();
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
HttpListenerContext context = listener.GetContext();
var identity = (System.Security.Principal.WindowsIdentity)context.User.Identity;

Here is an example of a file write to a named pipe.

echo test > \\hostname\pipe\test

We can impersonate the client of this connection fairly easily as well. The first thing to do is allow everyone read/write access.

PipeSecurity ps = new PipeSecurity();
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
PipeAccessRule par = new PipeAccessRule(sid, PipeAccessRights.ReadWrite,

Next start a thread with the name “test” and read some data when it comes in.

pipe = new NamedPipeServerStream($"test", PipeDirection.InOut, 10,
    PipeTransmissionMode.Byte, PipeOptions.None, 2048, 2048, ps);
pipe.Read(data, 0, 4);

Then all that needs done is to impersonate the client.

pipe.RunAsClient(() => {}

With that we have some methods for helping to identify potential new vectors to escalate privileges with SeImpersonatePrivilege. I have released the code mentioned in this article in a tool at GenericPotato

Trending Tags