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:

rotten2

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”.

Screenshot from 2018-01-14 15-47-41

 

Screenshot from 2018-01-14 15-48-06.png

Now logon with this user, open a standard cmd shell and an elevated one (run as administrator)

priv1

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:

tokenelev

 

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:

tokenscreen1

 

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:

enablepriv

All we need now is enable the privilege:

EnablePriv(hToken, SE_ASSIGNPRIMARYTOKEN_NAME);

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:

QuerySecurityContextToken(test->negotiator->phContext, &elevated_token)

Is it a primary or impersonate token?

tokentype.PNG

 

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:

duptoken.PNG

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:

thread.PNG

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:

 

thread2

 

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 😉

 

 

 

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s