在介绍PrintSpoofer之前,笔者会先详细介绍Windows下的权限控制以及Windows RPC远程过程调用。
Windows的访问控制模型有两个主要的组成部分,访问令牌 (Access Token) 和安全描述符 (Security Descriptor),它们分别是访问者和被访问者拥有的东西。通过访问令牌和安全描述符的内容,Windows可以确定持有令牌的访问者能否访问持有安全描述符的对象。
笔者在写 Windows认证协议 这篇文章的时候提到了 Windows Access Token (访问令牌)
,这个Access Token
会在用户创建进程或线程的时候被拷贝使用,Access Token
用来指明当前进程或线程的权限。所以,Windows下的安全对象需要一个用来判断来访问对象权限的数据结构,这个就是安全描述符。
Windows的安全对象包括:
安全描述符的数据结构如下:
typedef struct _SECURITY_DESCRIPTOR {
BYTE Revision;
BYTE Sbz1;
SECURITY_DESCRIPTOR_CONTROL Control;
PSID Owner;
PSID Group;
PACL Sacl;
PACL Dacl;
} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
安全描述符主要包括以下重要安全信息:
下图当中的安全选项就指明了哪些用户或组能够访问,以及对应用户或组的权限
安全标识符是标识用户、组和计算机账户的唯一的号码。每个账户都有一个由权威机构 (例如,Windows域控制器) 颁发的唯一SID,并存储在安全数据库中。每次用户登陆时,系统都会从数据中检索该用户的SID,并将其放入访问令牌中。在于Windows安全性相关的所有后续交互中,系统使用访问令牌中的SID识别用户。当SID用作用户或组的唯一标识符时,就不能再使用它来标识另一个用户或组。
SID的组成:
S-[修订级别]-[权值]-[标识符]
SID分为两种,1. 内置SID;2. 自动分配SID。内置SID有:
RID的组成:
S-[修订级别]-[权值]-[标识符]-[相对标识符]
例如:
每个Windows进程都拥有一个线程,当程序想要访问某个安全对象时,系统会提取当前线程的访问令牌,然后将访问令牌的权限和被访问的安全对象DACL进行比较。
当一个线程访问安全对象时,操作系统会将访问令牌的属性与被访问对象安全描述符中的DACL进行检查,检查的条目就是访问控制条目 (Access control entries,ACE),最先检查的ACE优先级越高。
NOTE
系统访问控制列表主要涉及的是关于ACE的日志,当审核对象的ACE被允许或拒绝的时候,系统就会产生相应的日志。
访问令牌包括两种:
默认情况下,系统会在线程中使用主令牌与安全对象交互。
一个令牌主要包括会话ID,用户和组列表,特权列表,令牌类型,模拟令牌等级和默认DACL等。
特权列表
SeAssignPrimaryTokenPrivilege
SeAuditPrivilege
SeBackupPrivilege
SeChangeNotifyPrivilege
SeCreateGlobalPrivilege
SeCreatePagefilePrivilege
SeCreatePermanentPrivilege
SeCreateSymbolicLinkPrivilege
SeCreateTokenPrivilege
SeDebugPrivilege
SeEnableDelegationPrivilege
SeImpersonatePrivilege
SeIncreaseBasePriorityPrivilege
SeIncreaseQuotaPrivilege
SeIncreaseWorkingSetPrivilege
SeLoadDriverPrivilege
SeLockMemoryPrivilege
SeMachineAccountPrivilege
SeManageVolumePrivilege
SeProfileSingleProcessPrivilege
SeRelabelPrivilege
SeRemoteShutdownPrivilege
SeRestorePrivilege
SeSecurityPrivilege
SeShutdownPrivilege
SeSyncAgentPrivilege
SeSystemEnvironmentPrivilege
SeSystemProfilePrivilege
SeSystemtimePrivilege
SeTakeOwnershipPrivilege
SeTcbPrivilege
SeTimeZonePrivilege
SeTrustedCredManAccessPrivilege
SeUndockPrivilege
SeUnsolicitedInputPrivilege
这些特权并不会都出现在令牌中。不在令牌出现的特权,是没有办法再次添加到令牌当中。
令牌模拟级别
模拟级别 | 说明 |
---|---|
SecurityAnonymous | 无法获取有关客户端的表示信息且无法模拟客户端 |
SecurityIdentification | 可以获取有关客户端的信息(比如安全标识符和特权)但是无法模拟客户端 |
SecurityImpersonation | 可以在本地模拟客户端但无法在远程系统上模拟客户端 |
SecurityDelegation | 可以在本地和远程系统上模拟客户端 |
三个通过用户身份创建进程的函数:
函数 | 需要特权 | 输入 |
---|---|---|
CreateProcessWithLogon | NULL | 域/用户名/密码 |
CreateProcessWithToken | SeImpersonatePrivilege | Primary令牌 |
CreateProcessAsUser | SeAssignPrimaryTokenPrivilege和SeIncreaseQuotaPrivilege | Primary令牌 |
RPC (Remote Procedure Call),远程过程调用其实本质上来说也是一种进程间通信,但是相比于传统的进程间通信,RPC机制提供了一种开发者不必显示的区分本地调用和远程调用,从而实现允许本地程序调用另一个地址空间的过程或函数。
RPC框架
RPC调用过程
所以RPC机制对开发者来说,隐藏了2,3,4,7,8,9步骤,使得调用远程函数和本地函数一样。
如何在windows实现RPC,笔者这里不会去写,有想了解的师傅可以参考以下链接:
https://www.cnblogs.com/wanghaiyang1930/p/4469222.html
上面提到Windows上的访问令牌有两种,一种是主令牌,另一种是模拟令牌。模拟令牌可以使得当前用户以另一个用户的身份创建进程,那么如果可以窃取高权限用户(比如 NT AUTHORITY\SYSTEM)的访问令牌,低权限用户就可以模拟高权限用户从而完成提权。
但是通过模拟令牌创建进程需要当前用户有SeImpersonatePrivilege
或SeAssignPrimaryTokenPrivilege
,而拥有这两个权限的账户是服务账户,比如 IIS、SQL Server。
进程令牌模拟流程:
而窃取令牌的方式一般是利用命名管道。因为命名管道服务端提供模拟客户端的功能,使得服务端可以调用ImpersonateNamedPipeClient获取客户端的访问令牌,并且Windows RPC中也提供了相同的功能RpcImpersonateClient
。所以,可以通过创建一个命名管道服务端,然后系统中高权限账户来连接命名管道从而使得服务端可以模拟高权限账户的模拟令牌。
关键问题是,如何让高权限用户连接攻击者创建的命名管道。
Windows的MS-RPRN协议用于打印客户机和打印服务器之间的通信,默认情况下是启用的。Printer Spooler服务暴露RPC接口RpcRemoteFindFirstPrinterChangeNotificationEx()
,这样客户端可以调用创建一个远程更改通知对象,该对象监视对打印机对象的更改,并将更改通知发送到打印机。
DWORD RpcRemoteFindFirstPrinterChangeNotificationEx(
/* [in] */ PRINTER_HANDLE hPrinter,
/* [in] */ DWORD fdwFlags,
/* [in] */ DWORD fdwOptions,
/* [unique][string][in] */ wchar_t *pszLocalMachine,
/* [in] */ DWORD dwPrinterLocal,
/* [unique][in] */ RPC_V2_NOTIFY_OPTIONS *pOptions)
并且,这个通知是通过命名管道发送的,而这个命名管道是\\.\pipe\spooless
。所以,如果能够控制连接的命名管道是攻击者创建的,那就可以窃取该服务的访问令牌了。但是,又存在一个问题,这个命名管道是NT AUTHORITY\SYSTEM
账户控制的,攻击者不能创建同名的命名管道。
接下来,就需要想办法创建另一个命名管道且符合路径检查。当尝试将传入的\\server_name
改为\\server_name\hack
时,会因为路径验证检查而失败。
后来PrintSpoofer作者了解到,如果路径里包含/
,将会通过路径检查,并且在连接命名管道的时候会将/
转换为\
。这意味着,传入的pszLocalMachine
为\\server_name/hack
时,命名管道的路径就会拼接为\\server_name\hack\pipe\spoolss
从而通过验证并且与规定的命名管道不同。
利用代码写的非常清晰,调用该RPC接口后窃取访问令牌的操作都是相同的。
切换成服务账户可以使用PsExec,指定对应的服务账户名称即可。
3 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!