In our previous blog post on Windows access tokens for security practitioners, we covered:
The relationship between logon sessions and access tokens
How network authentication works in Windows environments
Having covered some of the key concepts in Windows security, we will now build on this knowledge and start to look at how attackers can abuse legitimate Windows functionality to move laterally and compromise Active Directory domains.
This blog has deliberately attempted to abstract away the workings of specific Windows network authentication protocols (e.g., NTLM and Kerberos) where possible. As a consequence, there may be instances where behaviour unique to these protocols differs with the behavior described below. It also assumes some basic understanding of the Kerberos authentication protocol1.
Additionally, the material covered in this blog series was used for a BlackHat 2020 presentation, “Detecting Access Token Manipulation”. The presentation can be found here and the slides here.
Access Token Manipulation (ATT&CK technique: T1134) Having explained the basic principles of how logon sessions and access tokens work in our previous blog post, both locally and for distributed applications, this section will explain how attackers can abuse access tokens and target the fundamental trust relationships in Windows domains to compromise entire networks. The aim of this section is to describe access token manipulation techniques used by attackers within the context of a simulated compromise.
As a note, there is already an extensive body of excellent research on access token manipulation (which will be linked to liberally throughout this post). This blog attempts to build on this body of knowledge via considering access token manipulation from a different approach, namely through the relationship between access tokens, logon sessions and cached credentials. In the author's opinion, any description of token manipulation without considering these relationships represents only the tip of the iceberg. As a consequence, this blog’s definition of access token manipulation is perhaps much broader than commonly understood.
Initial compromise In the event that an attacker obtains a foothold in a network via spear phishing, they will typically end up with a shell running in the security context of the compromised user. This could be achieved via spawning a new process or injecting directly into memory (depending on the payload), but the end result is the same: the attacker’s code is running in a process which has an access token belonging to the compromised user.
This means that any local access checks will use the compromised user’s access token and any remote authentication attempts will use the compromised user’s cached credentials2. Hence, the attacker can, both locally and across the network, perform all the actions that the compromised user can. For example, if any internal web applications use Windows SSO, an attacker will be able to access them as if they were the user.
Token Manipulation: The ‘Art of the possible’ Typically, an attacker will want to move from the compromised endpoint to another host as quickly as possible3. When considering lateral movement from a token manipulation perspective, the attacker effectively has three options4, each of which is constrained by the fundamental relationship between access tokens, logon sessions, and cached credentials, as illustrated below:
Figure 1 - The relationship between access tokens, logon sessions and cached credentials
If an attacker wants to move laterally via Windows SSO then all of these three links must be in place (e.g., they have a handle to a token which is linked to a logon session backed by their target credentials). Otherwise, an attacker’s freedom of movement relies on either creating new links (e.g., new logon sessions) or modifying existing ones (e.g., changing cached credentials or the logon session that their access token points to). These constraints are discussed in more detail in the three options below:
- Steal the token of an already logged-on privileged user (non-network logon) If another privileged user is already logged on to the compromised host, an attacker can escalate their privileges and obtain a handle to an access token representing this user. Irrespective of whether the attacker impersonates the stolen token or starts a new process, if that token is linked to a non-network logon session, it will have cached credentials, and hence the attacker can auth off the box to another host5. Hence, this technique allows an attacker to use another user’s credentials to access remote hosts across the network (via Windows SSO), and therefore pivot without needing to dump credentials6.
As a note, token manipulation attacks generally relate to two distinct objectives: moving laterally (which this blog is concerned with) and local privilege escalation7. Token theft tends to be associated with the latter (e.g., stealing/impersonating a token for the purpose of bypassing local access checks, rather than for the purpose of using the cached credentials for remote authentication) and so this blog will not discuss it in any further detail, but the following resources are useful further reading:
https://posts.specterops.io/understanding-and-defending-against-access-token-theft-finding-alternatives-to-winlogon-exe-80696c8a73b
https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege-escalation-from-service-accounts-to-system/
https://labs.f-secure.com/assets/BlogFiles/mwri-security-implications-of-windows-access-tokens-2008-04-14.pdf
- Create a new logon session with stolen credentials and impersonate the returned token or spawn a new process with it In this case, there is no privileged user already logged on (and hence no corresponding useful access token/logon session), but the attacker still needs to find a way to change their security context.
Hence, the attacker must find credentials elsewhere and use these stolen credentials to create a new logon session as the compromised user. As Windows will automatically cache credentials for certain logon types, the attacker can now obtain a newly minted access token which is backed up by the stolen credentials. Once the attacker has a handle to a token representing the compromised user, they can authenticate off the box making use of the standard Windows SSO process.
Typically, plain text credentials are found by attackers via either Kerberoasting or searching for unsecured plain text credentials across all accessible resources, such as network shares, Sharepoint, internal wikis, enterprise GitHub, Zendesk, etc.8
- Change the cached credentials associated with their current access token to stolen credentials (e.g., legitimately via an API or “illegitimately” by directly modifying lsass memory) In this scenario, rather than create a new logon session, the attacker modifies the cached credentials associated with their current access token (and hence logon session). As we shall see, many Windows Security Support Providers (SSPs) provide native ways to do this (and which do not require elevated privileges).
Alternatively, attackers can go the “direct” route and manually modify cached credentials stored in lsass. This requires elevated privileges in order to obtain a write handle (e.g., PROCESS_VM_WRITE) to lsass via OpenProcess. This is typical of pass-the-hash type attacks as we shall cover later on.
Access Token Manipulation attacks This blog post will look at four common techniques used by attackers (all of which can be classified as variations of option 3 above):
The NETONLY flag
Pass-The-Ticket
Pass-The-Hash
Overpass-The-Hash
- The NETONLY flag The Windows API provides the LogonUser function to create a new logon session for a given user (or principal)9:
BOOL LogonUserW( LPCWSTR lpszUsername, LPCWSTR lpszDomain, LPCWSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, PHANDLE phToken );
The key parameter to take note of here is the dwLogonType, which specifies the type of logon to perform. For example, in the case of a user physically logging into their workstation, it will be set to LOGON32_LOGON_INTERACTIVE. The logon type specified will determine the type and privileges of the token returned.
For example, in the case of an interactive logon, LogonUserW will return a primary access token, and, if UAC is enabled, this token will be a filtered token (meaning it will be medium integrity and unelevated). This has one exception: if the user is a local administrator account (e.g., a *-500 SID) Windows will automatically return an elevated token10.
In the case of a network logon (LOGON32_LOGON_NETWORK), an impersonation token is returned (as typically this would be used by a server to perform work on the remote clients behalf). Furthermore, if the user is in the local administrators group, the token is elevated and has all privileges enabled11.
These permutations of LogonUser are captured in the table below:
dwLogonType
Token returned
Cache credentials?
Is returned token elevated? (if admin)
Interactive (LOGON32_LOGON_INTERACTIVE)
Primary
Yes
No (UAC applies)
Interactive (Local admin account, e.g., rid-500)
Primary
Yes
Yes
Network (LOGON32_LOGON_NETWORK)
Impersonation
No12
Yes (+ all privileges enabled)
Network (Local admin account, e.g., rid-500
Impersonation
No
Depends on remote UAC settings13
Table 1 - The permutations of LogonUser for the corresponding dwLogonType
The key point is that LogonUser returns a handle to a newly minted token, which can now be used for impersonation.
If the token returned is a primary token it must first be converted in to an impersonation token via DuplicateTokenEx by passing a TokenType of TokenImpersonate14:
BOOL DuplicateTokenEx( HANDLE hExistingToken, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, TOKEN_TYPE TokenType, PHANDLE phNewToken );
The SetThreadToken function can then be used to assign the returned impersonation token to the current thread:
BOOL SetThreadToken( PHANDLE Thread, HANDLE Token );
Alternatively, the Windows API provides the ImpersonateLoggedOnUser function, which will allow the calling thread to impersonate the security context of the user represented by the token passed:
BOOL ImpersonateLoggedOnUser( HANDLE hToken );
ImpersonateLoggedOnUser has the added benefit that it will automatically check the type of the token passed and convert it to an impersonation token (via NtDuplicateToken) if a primary token was passed (as this token type cannot be used by a thread to impersonate)15.
Note that from a defense evasion perspective, both these impersonation APIs are lightweight wrappers over the undocumented syscall NtSetInformationThread (e.g., called with a ThreadInformationClass of ThreadImpersonationToken). Therefore, they are a good target for attackers to use direct syscalls to bypass user-mode hooks via techniques such as https://github.com/jthuraisamy/SysWhispers.
Furthermore, it is important to stress that Windows has strict rules around impersonation. These are listed below and taken from the MSDN page for ImpersonateLoggedOnUser:
All impersonate functions, including ImpersonateLoggedOnUser allow the requested impersonation if one of the following is true:
- The requested impersonation level of the token is less than SecurityImpersonation, such as SecurityIdentification or SecurityAnonymous
- The caller has the SeImpersonatePrivilege privilege.
- A process (or another process in the caller’s logon session) created the token using explicit credentials through LogonUser or LsaLogonUser function. The authenticated identity is the same as the caller
Additionally, the impersonated token’s integrity level must also be less or equal to the calling process’s integrity level or else the impersonation call will also fail16. Therefore, assuming an unelevated attacker logs on an admin user interactively via stolen credentials, and UAC is enabled, they will receive an unelevated (e.g., filtered) token back and hence will have no issues impersonating the returned user and moving laterally, etc.
“The curious /NETONLY flag”17 An attacker may find however that attempting to log on a user with stolen credentials fails. This may be due to a multitude of reasons, such as the credentials are valid, but the account does not have permissions to log onto that specific workstation / they’re only valid in a different domain, etc. Furthermore, the attacker may also want to avoid logging in a highly privileged account entirely, as this may appear highly anomalous in certain contexts (e.g., a domain admin logging on to a low privileged business user’s host should be incredibly suspicious).18
In this scenario the LOGON32_LOGON_NEW_CREDENTIALS flag comes to the attacker’s rescue. If an attacker calls the LogonUserW function with this flag and passes a valid set of credentials (say found from sniffing around on file shares), Windows will enable the caller to duplicate their current token but make it point to a new logon session, referred to as a New Credentials logon session, which caches the stolen credentials. As a result, the user still has the same security context locally (e.g., they still have a copy of the same access token; it just points to a new logon session), however, any attempts to authenticate remotely will supply the new credentials passed in the call to LogonUserW19. This is illustrated in the diagram below:
Figure 2 - How the LOGON32_LOGON_NEW_CREDENTIALS flag works under the hood
Hence, the LOGON32_LOGON_NEW_CREDENTIALS flag provides a native mechanism to make your current access token point to a different logon session and hence different credentials.20
Note, that calling LogonUserW with the LOGON32_LOGON_NEW_CREDENTIALS flag does not validate the credentials when the call is made (they can be complete junk), but are only validated by a Domain Controller at the time of any remote authentication requests.
As a further example, a quick review of the code for the ‘MakeToken’ task from the open source .NET C2 framework Covenant reveals exactly the same approach: it takes a username/password combination and creates a new logon session/token with them via passing the LOGON32_LOGON_NEW_CREDENTIALS flag before proceeding to impersonate the returned token.
Furthermore, you can replicate the exact same behaviour with CreateProcessWithLogonW by passing a dwLogonFlags of LOGON_NETCREDENTIALS_ONLY.21
BOOL CreateProcessWithLogonW( LPCWSTR lpUsername, LPCWSTR lpDomain, LPCWSTR lpPassword, DWORD dwLogonFlags, LPCWSTR lpApplicationName, LPWSTR lpCommandLine, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
The key difference is that this involves spawning a new process with the returned token, as opposed to the intra process impersonation discussed previously. In fact, the built in Windows utility, runas, is a simple wrapper around CreateProcessWithLogonW and the /NETONLY flag provides a native way to spawn a new process with different network-only credentials, as demonstrated below:
Figure 3 - Example of using the runas /NETONLY flag to spawn a new process as the user astro\cosmo but with different cached credentials.
In exactly the same way as previously described, the new command prompt appears locally to be running as the same user (i.e., the attributes cached in the token are the same for any local access checks; hence whoami returns ‘astro\cosmo’), but any remote authentication attempts will be performed using the stolen credentials for the ‘ASTRO\Administrator’ user.
These logon sessions can be viewed using SysInternals’ LogonSessions tool. Logon sessions that were created with the NewCredentials flag can be determined by the Logon type field as shown below:
Figure 4 - Example of a NewCredentials logon session which is typically generated by the NETONLY flag
Furthermore, anomalous NewCredentials logon sessions (e.g., produced via the NETONLY gadget) leave artifacts in the Windows event logs. These can be identified via the event id 4642 and a LogonType of 9. An example is shown in the image below:
Figure 5 - Example of a Windows Event Log for Event ID 4624 which is typically generated by the NETONLY flag
Note that the original user is shown by the SubjectUserName field and the specified network only credentials (e.g., the credentials passed) are displayed in the TargetOutboundUser/DomainName fields.22
Auto-elevation One further quirk from a local privilege escalation perspective is that for rid-500 accounts, CreateProcessWithLogonW will automatically elevate the returned token for interactive logons (e.g. it will ignore UAC). Therefore, CreateProcessWithLogonW can be passed a local/domain admin account in order to execute an elevated process from a medium/unelevated context.
This behavior can be verified using runas. For example, when runas is used to spawn a process using a local admin account (e.g., runas /user:"Administrator" cmd.exe), the resulting process will be elevated (e.g., high integrity). However, when a non rid-500 account is used (but which is still in the local administrators group) the resulting process will be unelevated (e.g., it will be a filtered token / medium integrity).
Notice that this behaviour is consistent with the permutations listed for LogonUserW in Table 1. Therefore, an unelevated attacker could also log on a (non rid-500) admin user as a network logon and receive an elevated token with all privileges enabled.
However, as per the impersonation rules previously outlined, the attacker should not actually be able to do anything with this token as any attempts to impersonate the elevated token should fail, as it has a higher integrity level than the caller. Nevertheless, it is actually possible to duplicate the elevated token, lower the integrity level of the copied token to medium (NB ‘isElevated’ is still true)23, and start impersonating the elevated token from an unelevated/medium integrity context24. Hence, from an impersonation token perspective, you can bypass the default Windows behaviour of only elevating certain accounts and impersonate an elevated token irrespective of whether the account is a rid-500 account or not.
Process creation Note, that by default, when you create a child process it inherits your primary token even if you are currently impersonating another security context25. For example, if you are impersonating a SYSTEM token and you call CreateProcess(), it will still inherit a copy of the primary process token (rather than inheriting the SYSTEM security context of the thread).26
Therefore, if an attacker wishes to spawn a new process in a different security context, they must either:
Autentifică-te pentru a adăuga comentarii
Alte posturi din acest grup
Version 7.17.27 of the Elastic Stack was released today. We recommend you upgrade to this latest version. We recommend 7.17.27 over the previous versi