Introduction
If you are a pentester and enjoy tinkering with Windows, you have probably come across the following post by Raphael Mudge:
In this post, he explains how the Windows program runas works and how the netonly flag allows the creation of processes where the local identity differs from the network identity (the local identity remains the same, while the network identity is represented by the credentials used by runas).
Cobalt Strike provides the make_token command to achieve a similar result to runas /netonly.
If you are familiar with this command, you have likely experienced situations in which processes created by Beacon do not “inherit” the new token properly. The inner workings of this command are fairly obscure, and searching Google for something like “make_token cobalt strike” does not provide much valuable information (in fact, it is far more useful to analyse the implementations of other frameworks such as Sliver or Covenant).
In Raphael Mudge’s video Advanced Threat Tactics (6 of 9): Lateral Movement we can get more details about the command with statements like:
“If you are in a privileged context, you can use make_token in Beacon to create an access token with credentials”
“The problem with make_token, as much as steal_token, is it requires you to be in an administrator context before you can actually do anything with that token”
Even though the description does not mention it, Raphael states that make_token requires an administrative context. However, if we go ahead and use the command with a non-privileged user… it works! What are we missing here?
This post aims to shed more light on how the make_token command works, as well as its capabilities and limitations. This information will be useful in situations where you want to impersonate other users through their credentials with the goal of enumerating or moving laterally to remote systems.
It’s important to note that, even though we are discussing Cobalt Strike, this knowledge is perfectly applicable to any modern C2 framework. In fact, for the purposes of this post, we took advantage of the fact that Meterpreter did not have a make_token module to implement it ourselves.
An example of the new post/windows/manage/make_token module can be seen below:
You can find more information about our implementation in the following links:
- Pr #18022 – Add update_token to MSF + make_token post-ex module
- PR #648 – Add update_token function to stdapi
- Metasploit Weekly Wrap Up 15
Windows Authentication Theory
Let’s begin with some theory about Windows authentication. This will help in understanding how make_token works under the hood and addressing the questions raised in the introduction.
Local Security Context Network Security Context?
Let’s consider a scenario where our user is capsule.corp\yamcha and we want to interact with a remote system to which only capsule.corp\bulma has access. In this example, we have Bulma’s password, but the account is affected by a deny logon policy in our current system.
If we attempt to run a cmd.exe process with runas using Bulma’s credentials, the result will be something like this:
The netonly flag is intended for these scenarios. With this flag we can create a process where we remain Yamcha at the local level, while we become Bulma at the network level, allowing us to interact with the remote system.
In this example, Yamcha and Vegeta were users from the same domain and we could circumvent the deny log on policy by using the netonly flag. This flag is also very handy for situations where you have credentials belonging to a local user from a remote system, or to a domain user from an untrusted domain.
The fundamental thing to understand here is Windows will not validate the credentials you specify to runas /netonly, it will just make sure they are used when the process interacts with the network. That’s why we can bypass deny log on policies with runas /netonly, and also use credentials belonging to users outside our current system or from untrusted domains.
Now… How does runas manage to create a process where we are one identity in the local system, and another identity in the network?
If we extract the strings of the program, we will see the presence of CreateProcessWithLogonW.
$ strings runas.exe | grep -i createprocess CreateProcessAsUserW CreateProcessWithLogonW
A simple lookup of the function shows that runas is probably using it to create a new process with the credentials specified as arguments.
Reading the documentation, we will find a LOGON_NETCREDENTIALS_ONLY flag which allows the creation of processes in a similar way to what we saw with netonly. We can safely assume that this flag is the one used by runas when we specify /netonly.
The Win32 API provides another function very similar to CreateProcessWithLogonW, but without the process creation logic. This function is called LogonUserA.
LogonUserA is solely responsible for creating a new security context from given credentials. This is the function that make_token leverages and is commonly used along with the LOGON32_LOGON_NEW_CREDENTIALS logon type to create a netonly security context (we can see this in the implementations of open source C2 frameworks).
To understand how it is possible to create a process with two distinct “identities” (local/network), it is fundamental to become familiar with two important components of Windows authentication: logon sessions and access tokens.
Logon Sessions Access Tokens
When a user authenticates to a Windows system, a process similar to the image below occurs. At a high level, the user’s credentials are validated by the appropriate authentication package, typically Kerberos or NTLM. A new logon session is then created with a unique identifier, and the identifier along with information about the user is sent to the Local Security Authority (LSA) component. Finally, LSA uses this information to create an access token for the user.
Regarding access tokens, they are objects that represent the local security context of an account and are always associated with a process or thread of the system. These objects contain information about the user such as their security identifier, privileges, or the groups to which they belong. Windows performs access control decisions based on the information provided by access tokens and the rules configured in the discretionary access control list (DACL) of target objects.
An example is shown below where two processes – one from Attl4s and one from Wint3r – attempt to read the “passwords.txt” file. As can be seen, the Attl4s process is able to read the file due to the second rule (Attl4s is a member of Administrators), while the Wint3r process is denied access because of the first rule (Wint3r has identifier 1004).
Regarding logon sessions, their importance stems from the fact that if an authentication results in cached credentials, they will be associated with a logon session. The purpose of cached credentials is to enable Windows to provide a single sign-on (SSO) experience where the user does not need to re-enter their credentials when accessing a remote service, such as a shared folder on the network.
As an interesting note, when Mimikatz dumps credentials from Windows authentication packages (e.g., sekurlsa::logonpasswords), it iterates through all the logon sessions in the system to extract their information.
The following image illustrates the relationship between processes, tokens, logon sessions, and cached credentials:
The key takeaways are:
- Access tokens represent the local security context of an authenticated user. The information in these objects is used by the local system to make access control decisions
- Logon sessions with cached credentials represent the network security context of an authenticated user. These credentials are automatically and transparently used by Windows when the user wants to access remote services that support Windows authentication
What runas /netonly and make_token do under the hood is creating an access token similar to the one of the current user (Yamcha) along with a logon session containing the credentials of the alternate user (Bulma). This enables the dual identity behaviour where the local identity remains the same, while the network identity changes to that of the alternate user.
As stated before, the fact that runas netonly or make_token do not validate credentials has many benefits. For example we can use credentials for users who have been denied local access, and also for accounts that the local system does not know and cannot validate (e.g. a local user from other computer or an account from an untrusted domain). Additionally, we can create “sacrificial” logon sessions with invalid credentials, which allows us to manipulate Kerberos tickets without overwriting the ones stored in the original logon session.
However, this lack of validation can also result in unpleasant surprises, for example in the case of a company using an authenticated proxy. If we make a mistake when inserting credentials to make_token, or create sacrificial sessions carelessly, we can end up with locked accounts or losing our Beacon because it is no longer able to exit through the proxy!
Administrative Context or Not!?
Raphael mentioned that, in order to use a token created by make_token, an administrative context was needed.
“The problem with make_token, as much as steal_token, is it requires you to be in an administrator context before you can actually do anything with that token”
Do we really need an administrative context? The truth is there are situations where this statement is not entirely accurate.
As far as we know, the make_token command uses the LogonUserA function (along with the LOGON32_LOGON_NEW_CREDENTIALS flag) to create a new access token similar to that of the user, but linked to a new logon session containing the alternate user’s credentials. The command does not stop there though, as LogonUserA only returns a handle to the new token; we have to do something with that token!
Let’s suppose our goal is to create new processes with the context of the new token.
Creating Processes with a Token
If we review the Windows API, we will spot two functions that support a token handle as an argument to create a new process:
Reading the documentation of these functions, however, will show the following statements:
“Typically, the process that calls the CreateProcessAsUser function must have the SE_INCREASE_QUOTA_NAME privilege and may require the SE_ASSIGNPRIMARYTOKEN_NAME privilege if the token is not assignable.”
“The process that calls CreateProcessWithTokenW must have the SE_IMPERSONATE_NAME privilege.”
This is where Raphael’s statement makes sense. Even if we can create a token with a non-privileged user through LogonUserA, we will not be able to use that token to create new processes. To do so, Microsoft indicates we need administrative privileges such as SE_ASSIGNPRIMARYTOKEN_NAME, SE_INCREASE_QUOTA_NAME or SE_IMPERSONATE_NAME.
When using make_token in a non-privileged context and attempting to create a process (e.g., shell dir \dc01.capsule.corp\C$), Beacon will silently fail and fall back to ignoring the token to create the process. That’s one of the reasons why sometimes it appears that the impersonation is not working properly.
As a note, agents like Meterpreter do give more information about the failure:
As such, we could rephrase Raphael’s statement as follows:
“The problem with make_token is it requires you to be in an administrator context before you can actually create processes with that token”
The perceptive reader may now wonder… What happens if I operate within my current process instead of creating new ones? Do I still need administrative privileges?
Access Tokens + Thread Impersonation
The Windows API provides functions like ImpersonateLoggedOnUser or SetThreadToken to allow a thread within a process to impersonate the security context provided by an access token.
In addition to keeping the token handle for future process creations, make_token also employs functions like these to acquire the token’s security context in the thread where Beacon is running. Do we need administrative privileges for this? Not at all.
As can be seen in the image below, we meet point number three:
This means that any command or tool executed from the thread where Beacon is running will benefit from the security context created by make_token, without requiring an administrative context. This includes many of the native commands, as well as capabilities implemented as Beacon Object Files (BOFs).
Closing Thoughts
Considering all the information above, we could do a more detailed description of make_token as follows:
The make_token command creates an access token similar to the one of the current user, along with a logon session containing the credentials specified as arguments. This enables a dual identity where nothing changes locally (we remain the same user), but in the network we will be represented by the credentials of the alternate user (note that make_token does not validate the credentials specified). Once the token is created, Beacon impersonates it to benefit from the new security context when running inline capabilities.
The token handle is also stored by Beacon to be used in new process creations, which requires an administrative context. If a process creation is attempted with an unprivileged user, Beacon will ignore the token and fall back to a regular process creation.
As a final note, we would like to point out that in 2019 Raphael Mudge released a new version of his awesome Red Team Ops with Cobalt Strike course. In the eighth video, make_token was once again discussed, but this time showing a demo with an unprivileged user. While this demonstrated that running the command did not require an administrative context, it did not explain much more about it.
We hope this article has answered any questions you may have had about make_token.
Sources
- https://www.cobaltstrike.com/blog/windows-access-tokens-and-alternate-credentials
- https://www.youtube.com/watch?v=9h4MWM9jtEI
- https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithlogonw
- https://learn.microsoft.com/es-es/windows/win32/api/winbase/nf-winbase-logonusera
- https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw
- https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera
- https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-impersonateloggedonuser
- https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadtoken
- https://docs.microsoft.com/en-us/windows/desktop/secauthn/lsa-logon-sessions
- https://docs.microsoft.com/en-us/windows/desktop/secauthz/access-tokens
- https://attl4s.github.io/assets/pdf/Understanding_Windows_Lateral_Movements_2023.pdf