如何利用10年前被修复的Windows漏洞

 

0x00 前言

在上一篇文章中,我介绍了如何在具备模拟(impersonation)特权的情况下,将权限提升至SYSTEM。此外我们也以微软10年前对Service Tracing漏洞的修复措施为例,演示了如何修复这类漏洞。然而这种安全机制实际上可以被绕过,在本文中,我们将研究如何让有10年历史的漏洞焕发生机。

 

0x01 背景

大约10年之前,Cesar Cerrudo(@cesarcer)发现攻击者可以使用Windows的Service Tracing功能,通过命名管道来捕捉SYSTEM令牌。一旦我们具备SeImpersonatePrivilege权限,就可以在该用户的安全上下文中执行任意代码。微软当时确认该问题属于漏洞范畴,分配的CVE ID为CVE-2010-2554

这里我们以RASMAN服务对应的Service Tracing键值为例。操作过程非常简单,我们首先需要启动一个本地命名管道服务器,然后在注册表中将目标日志文件目录的路径设为该命名管道的路径。

如上图所示,目标目录为\\localhost\pipe\tracing。随后,一旦EnableFileTracing值被设置为1,服务就会使用\\localhost\pipe\tracing\RASMAN.LOG作为路径来尝试打开日志文件。因此,如果我们创建名为\\.\pipe\tracing\RASMAN.LOG的命名管道,就可以收到连接请求,调用ImpersonateNamedPipeClient函数来模拟服务账户。由于RASMANNT AUTHORITY\SYSTEM权限运行,因此我们最终能够拿到SYSTEM模拟令牌。

注意:在不同版本的Windows系统上,将EnableFileTracing设置为1后系统可能不会立即打开日志文件。由于低权限用户可以通过服务控制管理器(SCM)来启动RASMAN,因此为了稳定触发该事件,我们可以手动启动该服务。

当然,如果我们尝试在较新版本的Windows上(发布年限不到10年)执行该操作,我们就会看到上图所示的问题。如果我们想在被模拟用户的安全上下文中执行任意代码,我们将会收到1346错误,也就是“指定的模拟级别无 效, 或所提供的模拟级别无效”错误。此外我们还会看到错误代码5,也就是“访问被拒绝”错误,这是因为微软在Windows系统中部署了相应的防护机制。

我在前一篇文章中详细描述了这种安全更新机制。简而言之,系统现在会在CreateFile函数调用中指定SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION标志,而该函数负责获取目标日志文件的初始句柄。在这些标志的影响下,我们现在获取的令牌的模拟级别为SecurityIdentification(等级2/4)。然而为了完整模拟目标用户,我们必须获取的模拟级别为SecurityImpersonation(等级3/4)或者SecurityDelegation(等级4/4)。

备注:在上述命令提示符中,我们看到的打印信息为Unknown error 0x80070542,而不是实际的Win32错误。这是因为我在模拟用户时想获取错误代码对应的错误信息。由于模拟级别受限,因此我们无法查找代码与消息的对应关系。

那么这是否意味着我们将无功而返呢?我们获得的令牌的确用处不大,但我们可以继续阅读下文,寻找一线生机。

 

0x02 UNC路径

根据前文描述,如果我们将日志文件目录设为命名管道名称,就能拿到模拟令牌,但无法利用该令牌创建新进程。然而这里我其实隐藏了一个重要的细节:我们如何设置命名管道名称?

首先我们需要了解最终使用的日志文件路径的计算方式。这是非常简单的字符串拼接操作:<DIRECTORY>\<SERVICE_NAME>.LOG,其中<DIRECTORY>读取自注册表(FileDirectory键值)。因此,如果我们为名为Bar的服务设置输出的目录为C:\Temp,那么该服务就会使用C:\Temp\BAR.LOG作为日志文件的路径。

在漏洞利用过程中,我们通过UNC路径来指定管道名称,而不是常规的目录。在Windows系统上我们有许多方法来设置路径,UNC路径是其中一种(这里我们就不展开讲)。为了成功利用漏洞,我们的确需要用到UNC(通用命名约定),但需要稍加改动。

UNC路径通常用来访问本地网络中的远程共享。比如,如果我们想访问DUMMY主机FOO卷上的BAR文件,那么可以使用UNC路径\\DUMMY\FOO\BAR。在这种情况下,客户端主机会连接目标服务器的TCP 445端口,使用SMB协议来交换数据。

然而我们也可以使用\\DUMMY@4444\FOO\BAR,通过任意端口(而不是默认的TCP 445端口)来访问远程共享(这里为4444端口)。尽管这种路径与默认路径差别很小,但带来的影响非常巨大。效果上最明显的区别在于,这种情况下系统不再使用SMB协议,客户端会使用HTTP协议的扩展版本,也就是WebDAV(Web Distributed Authoring and Versioning)。

在Windows系统上,WebDAV由WebClient服务负责处理。如果我们查看该服务的描述,可以找到如下信息:

(该服务)使基于Windows的程序可以创建、访问并修改基于Internet的文件。如果停止该服务,这些功能将不再可用。如果该服务被禁用,那么以来该服务的其他服务将无法启动。

虽然WebDAV使用的是完全不同的协议,但有个因素始终不变:身份认证。因此,如果我们创建一个本地WebDAV服务器,然后使用该路径作为输出目录时会出现什么情况?

 

0x03 拿到模拟令牌

首先我们需要编辑注册表,修改FileDirectory的值。这里我们不再使用命名管道路径,而是使用\\127.0.0.1@4444\tracing

我们可以使用netcat打开socket,在本地TCP 4444端口上监听。启用服务跟踪功能后,我们可以收到如下信息:

非常有趣!我们收到了一个HTTP OPTIONS请求。根据User-Agent头部字段,我们可知这是一个WebDAV请求,但目前我们得不到更多信息。基于这些现象,我们知道该服务可以使用WebDAV来通信,因此我们应该回复并发送身份认证请求。

为了完成该任务,我创建了一个简单的PowerShell脚本,使用System.Net.Sockets.TcpListener对象在任意端口上监听,然后向连接该socket的第一个客户端发送一个硬编码的HTTP响应。我们准备发送给客户端的HTTP响应数据如下:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: NTLM

如果大家非常熟悉Web应用渗透测试,那么应该经常会遇到带有Basic值的WWW-Authenticate头(比如Apache htpasswd),但不一定经常碰到带有NTLM的值。通过这个值,我们能够要求客户端使用3次NTLM认证方案来进行身份认证。这里顺便提一下,如果大家想了解关于NTLM的背景知识,可以参考@HackAndDo发表的关于NTLM中继的研究文章。

脚本运行后,我们能看到如下结果:

初步分析,目标服务似乎同意发起NTLM认证过程,向我们发送NTLM NEGOTIATE请求。我们可以通过Wireshark来确认这一点:

这是个不错的开头,表示我们的研究方向没有偏差,但我们还需要确认最后一个因素:“协商身份”标志。

关于NTLM认证协议的文档非常详细,我们可以看到该协议中使用的每条信息的详细结构。其中有个文档解释了NEGOTIATE消息中NegotiateFlags值的计算方式。在官方文档中,我们可以找到关于Identify标志的一段话:

因此,如果设置了该标志位,客户端会通知服务端,表示自己只能请求模拟级别为SecurityIdentification的令牌。也就是说,如果设置了该标志位,我们将无功而返,无法拿到合适的模拟令牌。

在Wireshark中我们可以检查该标志位的设置情况:

事实证明,Identify标志位并没有被设置!

这意味着我们可以绕过补丁,拿到模拟令牌,使用该令牌在NT AUTHORITY\SYSTEM上下文执行任意代码。接下来我们只需要完成NTLM认证过程即可,以便将原始的NTLM数据转换成可以使用的令牌。这里我就不展开分析,相关内容可以参考各种Potato漏洞利用工具的原理。

我开发了一个PoC,运行结果如下图所示:

 

0x04 一些小细节

为了完整理解漏洞原理,我们还需要做一些工作,详细分析不再赘述,这里我想与大家分享一些小细节。

首先大家可能会注意到一点:服务正在请求的资源为/tracing,而不是/tracing/RASMAN.LOG,那么为什么会出现这种情况?

由于我们将\\127.0.0.1@4444\tracing设置为目录路径,因此可能会认为目标服务会使用\\127.0.0.1@4444\tracing\RASMAN.LOG路径,并且会请求/tracing/RASMAN.LOG。如果我们分析rtutils.dll中的TraceCreateClientFile函数,可以看到在打开日志文件前,该函数会检查TraceCreateClientFile值中指定的目录是否存在。

如下图所示,如果CreateDirectory成功执行(即目标目录不存在,且被成功创建),那么该函数会立即返回。在这种情况下,日志文件永远不会被打开,我们需要禁用跟踪功能,重新启用服务。也就是说,如果想让TraceCreateClientFile成功完成,CreateDirectory必须返回失败值。

我们WebDAV服务器收到的第一个HTTP请求实际上对应的正是这个初始化检查过程。此外,CreateDirectory实际上只是NtCreateFile函数的一个封装函数(在Windows上同样所有对象都是文件)。

CreateDirectory函数使用起来非常方便,但有一个问题:该函数并不能设置自定义标志,比如用来限制令牌模拟级别的标志,这也解释了为什么我们能够通过这种技巧拿到可用的模拟令牌。

BOOL CreateDirectoryW(
  LPCWSTR               lpPathName,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

那么如果服务端通过强化版的CreateFile文件来请求/tracing/RASMAN.LOG时会出现什么情况?为了回答该问题,我编译了如下代码:

HANDLE hFile = CreateFile(
    argv[1], 
    GENERIC_READ | GENERIC_WRITE, 
    0, 
    NULL, 
    OPEN_EXISTING, 
    SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, // <- Limited token
    NULL);
if (hFile)
{
    wprintf(L"CreateFile OK\n");
    CloseHandle(hFile);
}
else
{
    PrintLastErrorAsText(L"CreateFile");
}

然后我们可以不必等待RASMAN服务连接,而是以低权限用户身份,通过该测试应用来触发WebDAV访问行为。测试结果如下:

尽管我们在CreateFile函数调用中设置了SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION标志,我们还是能够拿到带有SecurityImpersonation模拟级别的令牌!因此我们可以得出结论:WebClient服务同样存在缺陷。

 

0x05 总结

微软在早期部署的安全补丁可以阻止恶意服务器应用程序利用Service Tracing功能获取可用的模拟令牌,在本文中,我们介绍了如何轻松绕过这种安全机制。这里我还得感谢另一个小伙伴,他几年前写的MS16-075利用代码给了我不少启发。

后续我不一定发布利用工具,由于许多Service Tracing键值可以被触发,因此想开发出一款可用的工具还需要一些工作要处理。此外如果我们想利用这种方式,还需满足一个前提条件:目标系统上必须安装并启用WebClient服务。尽管这是Workstation版系统上的默认设置,但并不适用于Server版系统。在Server系统上,我们需要通过附加功能来安装/启用WebDAV组件。

这里我们还可以继续研究,比如我们可以去分析为什么来自WebClient的请求并没有在调用CreateFile时考虑到SECURITY_IDENTIFICATION标志。在我看来这应该算是一个漏洞,但官方不一定也这么认为。

 

0x06 参考资料

(完)