如何滥用SMB挂载点绕过客户端符号链接保护策略

 

0x00 概述

本文简要介绍了SMBv2中的一个有趣特性,可能用于横向渗透或者红方行动中。之前我曾专门花时间研究Windows上的符号链接(symbolic link)攻击,当时我仔细研究过SMB服务器。从SMBv2开始,该协议就已支持符号链接(特别是NTFS 重解析点(Reparse Point)格式)。如果SMB服务器在共享目录中遇到NTFS符号链接,则会提取REPARSE_DATA_BUFFER结构,然后遵循SMBv2协议中相应规范将该信息返回给客户端。

客户端操作系统负责解析REPARSE_DATA_BUFFER数据,然后再从本地访问。这意味着符号链接只能引用客户端已经能够访问的文件。实际上,虽然默认情况下Windows没有启用符号链接本地解析功能,我还是找到了绕过客户端策略的一种方法,能够本地解析符号链接。目前微软拒绝修复这个绕过问题,如果大家感兴趣可以访问此处了解官方回复。

 

0x01 问题描述

我发现有一点非常有趣,虽然IO_REPARSE_TAG_SYMLINK会在客户端上处理,但如果服务器遇到IO_REPARSE_TAG_MOUNT_POINT重解析点,则会到服务器上去解析。因此,如果我们能在共享目录中设置挂载点(mout point),就可以访问服务器上的任意固定位置(即使该位置没有直接共享出来)。这种场景在横向渗透中非常有用,但问题在于,我们如何在无法本地访问硬盘的情况下添加挂载点?

 

0x02 具体分析

首先我们尝试一下通过UNC路径创建挂载点,可以在CMD中使用MKLINK命令,结果如下所示:

输出信息表示系统不支持在远程服务器上设置挂载点。这一点也能够理解,因为在远程驱动器上设置挂载点可能会导致不可预期后果。我们可以猜测一下,要么该协议不支持设置重解析点,要么做了些限制,只允许符号链接。如果想了解协议具体支持的功能,我们可以查看协议规范。设置重解析点需要向某个文件发送FSCTL_SET_REPARSE_POINT IO控制代码(control code),因此我们可以参考SMB2 IOCTL命令,查看其中是否存在与控制代码有关的信息。

一番搜索后,我们可以看到协议的确支持FSCTL_SET_REPARSE_POINT,并且协议规范中有如下描述(§3.3.5.15.13):

当服务器收到包含包含SMB2头部的请求,并且Command值等于SMB2 IOCTL、CtlCode等于FSCTL_SET_REPARSE_POINT时,那么消息处理过程如下:

根据[MS-FSCC] section 2.3.65规范,如果FSCTL_SET_REPARSE_POINT中的ReparseTag字段不等于IO_REPARSE_TAG_SYMLINK,那么服务器应该验证调用方的确有权限执行这个FSCTL。如果调用方不具备所需的权限,那么服务器必须拒绝该调用,返回STATUS_ACCESS_DENIED错误代码。

根据上述文字,貌似服务器只需要显示检查IO_REPARSE_TAG_SYMLINK即可,如果不匹配该标签,则会执行其他检查操作判断请求是否允许,但并没有提到服务器会设置另一个标签来显式禁止请求。也许系统内置的MKLINK工具不能处理这种场景,换个工具试试?这里我们可以尝试下CreateMountPoint工具(来自于我的symboliclink-testing-tools项目),看能不能成功。

CreateMountPoint工具并没有显示之前的错误(“只支持本地NTFS卷”),但返回了拒绝访问错误。这与§3.3.5.15.13中的描述相符,如果隐式检查失败,应当返回拒绝访问错误。当然协议规范中并没有表明需要执行哪些检查,我认为这时候应该派上反编译工具,分析一下SMBv2驱动(srv2.sys)的具体实现。

我使用IDA来查找IO_REPARSE_TAG_SYMLINK对应的立即数(immediate value,这里为0xA000000C),根据分析结果,貌似系统在查找其他标志时,会先会查找这个值。在Windows 10 1809系统的驱动中,我只在Smb2ValidateIoctl找到一处匹配值,相关代码大致如下:

NTSTATUS Smb2ValidateIoctl(SmbIoctlRequest* request) {
  // ...
  switch(request->IoControlCode) {
    case FSCTL_SET_REPARSE_POINT:
      REPARSE_DATA_BUFFER* reparse = (REPARSE_DATA_BUFFER*)request->Buffer;
      // Validate length etc.
      if (reparse->ReparseTag != IO_REPARSE_TAG_SYMLINK &&
          !request->SomeOffset->SomeByteValue) {
          return STATUS_ACCESS_DENIED;
      }

      // Complete FSCTL_SET_REPARSE_POINT request.
    }
}

上述代码首先从IOCTL请求中提取数据,如果标志不等于IO_REPARSE_TAG_SYMLINK并且请求中某些字节值不等于0,那么就会返回STATUS_ACCESS_DENIED错误。如果想跟踪这个值的来源,有时候会比较棘手,但其实我只需要在IDA中将变量偏移值作为立即数来搜索,通常就能得出结论。这里对应的立即数为0x200,我们可以只搜索MOV指令。最终我在Smb2ExecuteSessionSetupReal中找到了一条指令:MOV [RCX+0x200], AL,这似乎就是我们想要的结果。系统会使用Smb2IsAdmin函数的返回值来设置该变量,而该函数只会检查调用方令牌中是否包含BUILTIN\Administrators组。因此貌似只要我们是主机上的管理员,就可以在远程共享上设置任意重解析点。我们需要验证这一点:

以管理员账户测试时我们能创建挂载点,并且dir UNC路径时,我们也能看到相应的Windows目录。虽然我测试的是本地admin共享,但这适用于其他共享,并且也能访问指向远程服务器的挂载点。

 

0x03 总结

这种技巧是否有用武之地?这种方法需要管理员访问权限,因此这并不是一种权限提升技术。此外,如果我们具备远程管理员访问权限,那么我们肯定会利用该权限执行其他操作。然而,如果目标主机禁用了admin共享,或者目标环境中有些监控机制,可以监控ADMIN$或者C$,但我们具备有些共享的写入权限,那么横向渗透中我们就可以使用这种方法完全控制其他驱动器。

我发现之前没有人分析过这一点,或者也有看可能是我搜索不够全面,毕竟在网上搜索SMB以及挂载点时,得到的结果大多与SAMBA配置有关。有时候系统出于安全考虑,不会公开某些处理逻辑,我们可以大胆假设小心求证,这个例子就是非常典型的一次实验。虽然MKLINK之类的工具提示我们无法设置远程挂载点,但进一步分析、查看代码后,我们自己可以找到更为有趣的一些细节。

(完)