This blog series is aimed at giving defense practitioners a thorough understanding of Windows access tokens for the purposes of detection engineering.
Here in Part 1, we'll cover key concepts in Windows Security. The desired outcome is to help defenders understand how access tokens work in Windows environments. In Part 2 of the series, we’ll build on the concepts outlined in Part 1 and cover how attackers abuse legitimate Windows functionality to move laterally and compromise entire Active Directory domains in depth.
As a note of caution, this blog has deliberately attempted to abstract away the workings of specific Windows network authentication protocols (e.g., NTLM and Kerberos) and Security Support Providers (e.g., CredSSP, Negotiate, etc.). As a consequence, there may be instances where behavior unique to these protocols/packages differs with the behavior described below (i.e., Kerberos constrained delegation). Additionally, this blog owes a huge debt of gratitude to Programming Windows Security by Keith Brown which is a fantastic resource for learning more about fundamental Windows security concepts.
Logon sessions and access tokens
The key concept to grasp in order to understand authentication in Windows environments is the relationship between logon sessions and access tokens. A logon session is used to represent the presence of a user on a machine and begins when a user is successfully authenticated and ends when the user logs off.
For example, when a user physically logs on to a Windows workstation (i.e., interactively), they supply a username and password, which is then checked by the Local Security Authority (LSA). If the account is a local account (i.e., only valid on that specific computer) the LSA will check the credentials against its own security database. In the case of a Windows Active Directory domain environment, the authentication attempt is referred to the closest domain controller (DC) which will process the request and authenticate the user.
Once the user has been successfully authenticated, the LSA will create a new logon session and produce an access token, as shown above.1 A logon session can have multiple access tokens associated with it, but an access token can only ever be linked to one logon session (which is typically the successful logon attempt that generated it).2 Windows has legitimate functionality which can be used to change the logon session (and hence cached credentials) that your current token is associated with. This will be covered in more detail in part 2.
Every new logon session is identifiable via a 64 bit locally unique identifier (LUID), referred to as the logon ID, and every access token must contain an Authentication Id (or AuthId) parameter that identifies the origin/linked logon session via this LUID. This is highlighted in the diagram below:
The main function of an access token is to act as a “volatile repository for security settings associated with the logon session” which can be adjusted and modified on the fly.2 In this sense, access tokens act as a proxy or stand-in for the logon session and so when making security decisions, Windows developers never interact with the logon session itself (which is “hidden” away in lsass), but with an access token which represents it (and hence predominantly via the Windows access token API).
Therefore, a developer can copy existing tokens (DuplicateTokenEx), modify the security settings for a given token (Get/SetTokenInformation) etc. to their heart's content, but these tokens are still just abstractions representing the security settings from the originating logon session.
Most importantly, the access token represents the security context of the user. The security context can be defined as the privileges and permissions that a user has on a specific workstation (and across the network). An access token caches a number of attributes which determine its security context, such as:
- The security identifier (SID) for the user
- Group memberships
- Privileges held
- A logon ID which references the origin logon session
For example, the screenshot below shows the cached security attributes for an access token using James Forshaw’s TokenViewer:
As discussed previously, the Authentication ID parameter, which is the key link between an access token and the logon session that it represents, contains a 64 bit LUID (logon ID) which identifies the origin logon session that this access token is associated with. Note also that it is possible to infer a number of other conclusions about the state of this token, e.g., it is a primary token, it is not elevated (medium integrity), and the user is an administrator (Elevation Type = limited means the token is a ‘filtered’ admin token and hence UAC is enabled).
Whenever a thread attempts to access a securable object managed by the Windows kernel, such as a process, thread, handle, semaphore, token, etc., Windows will perform an access check. To perform this check, Windows needs three pieces of information2:
- Who is requesting access?
- What are their intentions with the object?
- Who can access the object?
Hence, Windows will first check the token associated with the calling thread and look at the authorization attributes cached in it (e.g., user sid, group memberships, privileges etc.). Secondly, Windows will look at the desired access requested by the thread. In the Windows security model you must state your intentions upfront; for performance reasons an access check only occurs once and no further checks are performed on any additional handle operations (unless a user attempts to perform an action that the handle did not have rights to, e.g., write to a read-only handle). Thirdly, Windows will retrieve the security descriptor for the target object. The security descriptor contains a discretionary access control list (DACL) which specifies what users/groups have access to the object and the type of access granted.
Based on these three sources of information, Windows can give a boolean answer to whether a principal has access to a given object. This is why every process must have a primary token; it is the user that is “charged” for any objects that process attempts to access.2
As a note, some privileges can be thought of as simply enabling a user to bypass/skip the access check in the kernel for a given object. For example, if a token has the SeDebugPrivilege privilege enabled, the Windows kernel will skip the DACL checks for any process and thread objects (hence why it is so powerful).3
Following successful authentication from an interactive logon, Windows will execute the user’s shell (normally explorer.exe) on behalf of the newly logged-on user. The operating system performs this action by using the newly minted access token to spawn explorer.exe as that user via CreateProcessAsUserA. This function takes a handle to a token and spawns a new process as the user specified in the token (i.e., in a different security context).
Typically, every process created by the user is a child of the shell process (i.e., explorer.exe)4 and every new process will (by default) run in the same security context as its parent; hence the child process will inherit its parent’s access token upon creation.5 Therefore, all processes will inherit their own local copy of an access token.
As stated previously, access tokens act as a local “volatile repository” for the security settings associated with the logon session. As each process has its own local copy of an access token, a process can modify the volatile security settings stored in its copy without affecting other processes.2
For example, a browser such as Chrome may want to create a restricted version of its access token in order to effectively sandbox the application in the event an attacker is able to exploit the browser and obtain arbitrary code execution on the compromised machine. As a consequence of the sandbox, any actions performed by the attacker will be restricted and help prevent further damage. As previously discussed, all access checks in Windows make decisions based on the attributes stored in the calling thread’s token, and so by ‘hardening’ the token a developer can restrict its access.
The key point is that Chrome can modify its local copy of the token without affecting other applications. This can be achieved via APIs such as AdjustTokenGroups/AdjustTokenPrivileges, which can be used to disable dangerous groups and privileges, respectively. Alternatively, a new restricted copy of a specified access token can be created with CreateRestrictedToken. As an example, the relevant functionality in the chromium source code can be found here.
This is so important because, as discussed above, access tokens are the core component of the Windows security model and so by being able to change the information cached in them, a developer can limit what securable objects a token can touch and hence restrict its access across a system.
The diagram below summarizes the logon process and an example access check for an interactive user, ASTRO\cosmo:
Having covered local authentication and access control, what happens under the hood when a user needs to access some resource located across the network? For example, a user could attempt to view the available shares on another host by running the following command:
The user’s logon session is unique to their workstation (as is their access token and privileges) and they cannot simply send their access token over the wire. The token would be meaningless as it does not correspond to a valid logon session on the remote host. Furthermore, this authentication mechanism would be an obvious target for replay attacks.2
In this case, the user needs to re-authenticate and establish a new logon session on the remote machine (assuming the user has access). For an interactive logon (and actually all other logon types like service, batch, etc., except network6) Windows will automatically cache the credentials as part of the Windows single sign-on (SSO) mechanism.7 This is the intended design of the Windows SSO mechanism and prevents the user from having to constantly re-enter their password when accessing network resources.
As a consequence, access tokens which link back to these types of logon sessions can authenticate to remote hosts and Windows will automatically authenticate on the users behalf whenever a network resource is accessed by a thread or process.8 Note that Windows will always use the credentials cached in the logon session that the access token is linked to when authenticating remotely (e.g., Windows will find the token’s linked logon session, via the AuthId, and use the credentials cached for that logon session, as shown below).
Therefore, in order to establish a new logon session, the SMB server will need to authenticate the client over the network. In Windows domains, network authentication is typically performed via Kerberos or the legacy challenge-response protocol NTLM. Irrespective of the network authentication protocol used, on receiving an authentication request the target host will forward the credential information to the DC and, following successful authentication, establish a new network login session for the user (i.e., this login “represents a remote client”).2
Network logins do not cache credentials and therefore you cannot use this token to authenticate to another remote host.9 This is commonly referred to as the ‘double hop’ problem. Note that due to the inherent design of the NTLM challenge response protocol (e.g., the client encrypts a challenge with the user’s NTLM hash) it fundamentally does not support credential delegation.
Most importantly from the server’s perspective, following the successful authentication of the remote user, it is presented with a newly minted access token which represents the network logon of the remote client. The diagram below illustrates this process:
This neatly leads to the second key concept for Windows access tokens: impersonation. As previously mentioned, access tokens encode a wealth of information about the security context of the user and enable a handy way for developers to make “localized” changes to this context without affecting other processes.2 However, in multi-threaded applications, problems and difficult-to-debug race conditions may arise if different threads start enabling/disabling different privileges or modifying default token DACLs.2 10
As a result, Windows has a feature called impersonation. By default all threads will inherit the same security context as their process’s primary token. However, impersonation allows a thread to switch to a different security context. Specifically, it enables threads to have their own local copy of a token; known as an impersonation token. This is the best way to remember the distinction between primary and impersonation tokens, in that impersonation tokens are always applied to threads, whereas primary tokens are associated with processes. In this way, the SMB server can handle each incoming client request in a separate thread and impersonate the access token representing the remote client.
Also note that switching security context has two implications. The first is that locally the thread is now impersonating a different access token, and hence any local access checks will be performed using this new token. Secondly, as this impersonated token may be linked to a different logon session (and therefore potentially have different cached credentials (if a non-network login)) the thread’s security context remotely is also different. Furthermore, as we shall see in the next blog post in this series, do not always assume that the cached credentials stored in lsass match the user specified in the token.
In summary, from the perspective of a listening server process (say an SMB file server), the following steps must occur following a connection request from a remote client:
- The user is authenticated and a new logon session is created (NETWORK_ONLY)
- The server process is presented with a handle to an impersonation token which links back to the remote client’s new network logon session
- The server can use this token to impersonate the client to perform work on their behalf
This approach has the added benefit of making use of the existing Windows’ access control model, as all actions performed while impersonating are under the security context of the user’s identity (hence any local access check decisions will use the information cached in the impersonated user’s token). Therefore, if that user does not already have access to a specific file on a share, they will be denied access.
For most of Windows’ key IPC mechanisms (e.g., named pipes, RPC, COM) this process is handled automatically. The server needs only to call the appropriate API in order to obtain a handle to the remote clients’ security context (i.e., access token) and start impersonating the client via functions such as2:
Hopefully this has been an informative overview of some key concepts in Windows Security. Stay tuned for Part 2 of this blog series, where we’ll build on the concepts outlined above and cover in more depth how attackers abuse legitimate Windows functionality to move laterally and compromise entire Active Directory domains, and how you can detect and respond to access token manipulation within your environment.
- With UAC enabled, Windows actually creates two tokens for an administrative user: a filtered user token and a ‘linked’ administrative token. Therefore, this is a slightly simplified description. For more info see: https://www.tiraniddo.dev/2017/05/reading-your-way... / https://www.tiraniddo.dev/2017/05/reading-your-way... / https://www.tiraniddo.dev/2017/05/reading-your-way...
- Programming Windows Security, Keith Brown
- This can be verified by looking at PsOpenProcess/Thread in IDA and looking for a call to SePrivilegeCheck.
- There are many exceptions where this obviously isn’t the case, such as when OS services will execute an app on behalf of the user; e.g., metro apps/calc in Windows 10, process creation services such as Sec Logon, Task Scheduler, WMI, etc.
- This is true even if the thread is currently impersonating a different security context (as we will cover in the next blog post in this series). See James Forshaw’s presentation on Process Failure Modes for more info: / https://drive.google.com/file/d/0B5sMkPVXQnfPaVB6T2N3Mk5UX28/view
- A full list of the available logon types can be found here under dwLogonType: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera
- It is worth noting that this design, along with insecure legacy protocols such as NTLM, have had a long history of security issues, such as NTLM relaying, and are often still difficult to fully mitigate in complex enterprise environments.
- For example, if during a penetration test you run mimikatz on an SMB file share for a large enterprise you will see a huge number of network logins corresponding to remote clients browsing the share with no cached credentials.
- N.B The default DACL for a token specifies the default DACL that is applied to any securable object that a thread/process running with that token creates at runtime.