I just finished playing with the Rotten Potato C# exploit in order to get it work standalone that the author @breenmachine released the C++ standalone version of “Rotten Potato”. He really did a great job! https://github.com/foxglovesec/RottenPotatoNG
Time for me to play with this new version, dig deeper in some “obscure” Windows API calls and access tokens in order to adjust code for my needs..
The modified VS 2015 C++ project can be found here: https://github.com/decoder-it/lonelypotato along with exe file, so if you are only interested in pentesting there is no need to move forward 😉
But if you are la curious guy, just like me, keep on reading! My goal is to understand better the Windows access tokens and impersonation techniques. This can be very helpful in Windows Privilege Escalation.
I will assume that you already know about the Rotten Potato exploit and have read all the posts. I will also assume that you are aware about Windows sessions, stations and desktops and that you have read my previous posts about the “Lonely Potato” here and here
Environment is the same as in the first post : Windows 2016 Server+ IIS + MSSQL Express.
Very short recap: with Rotten Potato exploit, we get an “admin” access token and if we have the privilege to impersonate the token we can run a process in this (very) high context -> SYSTEM. Typically, LocalService accounts (IIS, MSSQL) have the SeImpersonatePrivilege and SeAssignPrimaryToken privilege so they are able to impersonate a different user via the 2 Windows API CreatProcessAsUser() and CreateProcessWithTokenW().
First, we will modify the main() function in order to pass our arguments:
We want to test the 2 CreateProcess..() functions and invoke a program by command line (as usual a reverse powershell)
We can specifiy which API CreateProcess..() functions we want to check and with the jolly character we instruct the program to first test CreateProcessAsUser() and if it fails check the second one.
Please notice that main() changed in wmain() in order to use wide-character variables.
I won’t explain what the program does etc. .. just point out some facts.
OK, let’s go on…
whoami /priv does not show the SeImpersonatePrivilege in a standard cmd shell
You can test it on your own: create a regular user and assign him the “Impersonate Client after Authentication” and “Replace process level token” via “gpedit.msc”.
Now logon with this user, open a standard cmd shell and an elevated one (run as administrator)
Do you notice the difference? SeImperosonatePrivilege is present in the elevated session, SeAssignPrimaryTokenPrivilege not, good to know!
Margin note: if you don’t have SeImpersonatePrivilege and are a regular user, you cannot run an elevated cmd shell becuase UAC will ask you for the administrator’s password and not yours.
How can I check if my process runs in elevated mode?
A simple example is worth a thousand words:
We get the token of our process and then pass it to GetTokenIformation(). This function can do a lot of queries on our token, in this case we will ask if we if we run in elevated mode.
So, if the statement is true, our process is elevated. Don’t worry, “Local Service” processes always run in elevated mode but in other circumstances it could be useful in order to “abuse” from other privileges…
What are the privileges needed by the 2 CreateProcess..() API calls?
MS says that CreateProcessAsUser() only needs the the SeAssignPrimaryTokenPrivilege , whereas CreateProcessWithTokenW only needs the SeImpersonatePrivilege.
If you don’t have the right privlege, the API call with fail:
As you can see, I launched the CreateProcessWithTokenW() without the necessary privilege and got an error.
The SeAssignPrimaryToken privilege is present but not enabled, how can I enable it?
Normally SeImpersonatePrivilege is enabled, SeAssignPrimaryToken not. Again, with the right API calls it’s very easy task:
All we need now is enable the privilege:
The return value of these API calls is sometimes misleading, they can return true even if the privilege is not present. Therefore I would suggest to check the GetLastError() Windows function.
Which type of access token are needed by the CreateProcess..() functions?
Quick & dirty: There are two type of access tokens:
- Primary Tokens: for creating new processes
- Impersonation Tokens: for creating new threads
What type of token is the “elevated_token”?
With the Rotten Potato exploit we get our elevated token:
Is it a primary or impersonate token?
If token type is 1, we have a primary token, otherwise an impersonation token. In our case, we have to deal with an impersonation token:
[+] Elevated Token type:2
How can I change my access token to primary?
No problem, we can duplicate the “elevated_token” in a primary token:
If everything works fine, our “duped_token” will be of type:1 (primary)
Instead of calling a new process, can I start a new thread impersonating the elevated token?
Yes of course:
All we have to do is replace the thread’s token with the elevated one (remember, “elevated_token” is of type 2).
That’s why we started the thread in suspended state and then replaced the token with the elevated one via SetThreadToken().
What can we do inside the thread? Here we are SYSTEM, so imagination is the only limit…for example add the current user to administrators group:
CreateProcessWithTokenW() seems not to work if called in Session 0 (typically a remote shell spawned by IIS or MSSQL)
I noticed this in testing the C# version but did not investigate too much…
The function call returns true but nothing happens, strange behavior!
First of all what are the main differences between this versions?
We know that CreateProcessWithTokenW() needs the SeImpersonatePrivilege . In fact, when we use this function, we make a call to the “Secondary Logon Service” which then internally should call the CreateProcessAsUser() function?
After some tests, I discovered that the only way to get CreateProcessWithTokenW() working in session 0 is to set an interactive session.
si.lpDesktop = L"Winsta0\\default";
where “Winsta0\default” is the default interactive session.
That’s why our process without Station/Desktop gets killed by Windows desktop manager.
This is strange, I even read somewhere that it was not possible to create an interactive session when calling CreateProcessWithTokenW() from a Local Service.
It’s not clear why CreateProcessAsUser() works in Session 0 even if we do not set the Windows Station/Desktop. Need to investigate better, but for now that’s all we need 😉
Do I really need a primary token for the CreateProcess..() functions?
No! You can use indifferently a primary or impersonation token, it works the same. This is not true for threads where you need and impersonation token.
Do I really need to enable the SeAssignPrimaryToken Privilege?
Again, no! Surpisingly, during my tests, I discovered that it was not necessary to enable the privilege!
Will the Rotten Potato exploit work if I change the default accounts running the service (IIS, MSSQL)?
Well, I’ll talk about that in a future post 😉