首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用 WPAD/PAC 和 JScript在win11中进行远程代码执行3

使用 WPAD/PAC 和 JScript在win11中进行远程代码执行3

原创
作者头像
franket
发布2022-04-23 19:23:09
1.9K0
发布2022-04-23 19:23:09
举报
文章被收录于专栏:技术杂记技术杂记

请注意,当元素的名称小于 4 个字节时,它与 VAR(元素值)存储在相同的结构中。否则,将有一个指向元素名称的指针。名称长度 <=4 对我们来说就足够了,所以我们不需要详细说明。

对象哈希表是一个很好的覆盖对象,因为:

  • 我们可以通过访问相应的对象成员来控制它的哪些元素被取消引用。我们用我们无法控制的数据覆盖的元素将永远不会被访问。
  • 我们通过控制相应对象有多少成员来限制对哈希表大小的控制。例如,哈希表以 1024 字节开头,但如果我们向对象添加超过 512 个元素,哈希表将重新分配到 8192 字节。
  • 通过用指向我们控制的数据的指针覆盖哈希表指针,我们可以在我们控制的数据中创建假的 JScript var,并通过访问相应的对象成员来访问它们。

为了可靠地执行覆盖,我们执行以下操作:

  1. 分配和释放大量大小为 8192 的内存块。这将打开低碎片堆以分配大小为 8192。这将确保我们溢出的缓冲区以及我们溢出的哈希表将被分配LFH。这很重要,因为这意味着附近不会有其他大小的分配来破坏利用尝试(因为 LFH 存储桶只能包含特定大小的分配)。这反过来又确保我们将以高可靠性准确覆盖我们想要的内容。
  2. 创建 2000 个对象,每个对象包含 512 个成员。在这种状态下,每个对象都有一个 1024 字节的哈希表。但是,仅向其中一个对象添加一个元素将导致其哈希表增长到 8192 字节。
  3. 将 513 元素添加到前 1000 个对象,导致 1000 次分配 8192 字节哈希表。
  4. 使用长度为 300 和 170 个元素的数组触发 Array.sort。这会分配一个大小为 (170+1)*48=8208 字节的缓冲区。由于 LFH 粒度,该对象将被分配在与 8192 字节哈希表相同的 LFH 桶中。
  5. 立即(在第一个数组元素的 toString() 方法中)将第 513 个元素添加到第二个 1000 个对象。这使我们非常确定,到目前为止,排序缓冲区与哈希表之一相邻。在同一个 toString() 方法中,还会向数组添加更多元素,这将导致它超出范围。

图 5 显示了围绕排序缓冲区地址(红线)的堆可视化。您可以看到排序缓冲区被大小相似的分配包围,这些分配都对应于对象哈希表。您还可以观察到 LFH 随机性,因为后续分配不一定在后续地址上,但这对我们的漏洞利用没有影响。

图 5:溢出缓冲区周围的堆可视化

如前所述,我们以这样一种方式设计了溢出,即不幸的 JScript 对象的一些哈希表指针将被指向我们控制的数据的指针覆盖。现在,我们最终在这些数据中添加了什么:我们精心设计了它,使其包含 5 个(假)JavaScript 变量:

  • 变量 1 只包含数字 1337。
  • 变量 2 是特殊类型 0x400C。这种类型基本上告诉 JavaScript 实际 VAR 由偏移量 8 处的指针指向,并且在读取或写入此变量之前应该取消引用此指针。在我们的例子中,这个指针指向变量 1 之前的 16 个字节。这基本上意味着变量 2 的最后 8 字节 qword 和变量 1 的第一个 8 字节 qword 重叠。
  • 变量 3、变量 4 和变量 5 是简单整数。它们的特别之处在于它们的最后 8 个字节中分别包含数字 5、8 和 0x400C。

溢出后损坏对象的状态如图 6 所示。

图 6:溢出后的对象状态。红色区域表示发生溢出的位置。底行中的每个框(标记为“...”的框除外)对应 8 个字节。为清楚起见,省略了“...”框中的数据

我们可以通过简单地访问正确索引处的损坏对象(我们称之为 index1)来访问变量 1,对于变量 2-5 也是如此。事实上,我们可以通过访问所有对象的 index1 并查看哪个对象的值现在为 1337 来检测我们损坏了哪个对象。

将变量 1 和变量 2 重叠的效果是,我们可以将变量 1 的类型(第一个 WORD)更改为 5(双精度)、8(字符串)或 0x400C(指针)。为此,我们读取变量 2、3 或 4,然后将读取的值写入变量 2。例如语句

损坏的对象index2 = 损坏的对象index4;

效果是变量 1 的类型将更改为字符串 (8),而变量 1 的所有其他字段将保持不变。

这种布局为我们提供了几个非常强大的利用原语:

  • 如果我们写入一些包含指向变量 1 的指针的变量,我们可以通过将变量 1 的类型更改为双精度 (5) 并将其读出来公开该指针的值
  • 我们可以通过在该地址伪造一个字符串来公开(读取)任意地址的内存。我们可以通过首先将对应于我们要读取的地址的双精度值写入变量 1,然后将变量 1 的类型更改为字符串 (8) 来完成此操作。
  • 我们可以写入任意地址,首先将与地址对应的数值写入变量 1,然后将变量 1 的类型更改为 0x400C(指针),最后将一些数据写入变量 1。

使用这些漏洞利用原语,通常执行代码会非常简单,但由于我们正在利用 Windows 10,我们首先需要绕过控制流防护 (CFG)。

第 3 阶段:CFG 旁路

我们可能在这里使用了其他已知的绕过方法,但事实证明,有一些非常方便的绕过方法(一旦攻击者拥有读/写原语)特定于 jscript.dll。我们将利用以下事实:

  • 返回地址不受 CFG 保护
  • 一些 Jscript 对象具有指向本机堆栈的指针

具体来说,每个 NameTbl 对象(在 Jscript 中,所有 JavaScript 对象都从 NameTbl 继承)在偏移量 24 处保存一个指向 CSession 对象的指针。CSession 对象,在偏移量 80 处持有一个指向本机堆栈顶部附近的指针。

因此,通过任意读取,通过跟踪来自任何 JScript 对象的指针链,可以检索到本地堆栈的指针。然后,通过任意写入,可以绕过 CFG 覆盖返回地址。

第 4 阶段:将代码执行作为本地服务

有了所有的漏洞利用元素,我们现在可以继续执行代码了。我们按以下步骤进行:

  1. 从任何 JScript 对象的 vtable 中读取 jscript.dll 的地址
  2. 通过读取jscript.dll的导入表读取kernel32.dll的地址
  3. 通过读取kernel32.dll的导入表读取kernelbase.dll的地址
  4. 扫描 kernel32.dll 寻找我们需要的 rop gadgets
  5. 从kernel32.dll的导出表中获取WinExec的地址
  6. 泄漏堆栈地址,如上一节所述
  7. 准备 ROP 链并将其写入堆栈,从最接近我们泄露的堆栈地址的返回地址开始。

我们使用的 ROP 链如下所示:

RET 的地址 //需要将堆栈对齐到 16 个字节

POP RCX地址;RET //将第一个参数加载到rcx中

要执行的命令地址

POP RDX地址;RET //将第二个参数加载到rdx

1

WinExec的地址

通过执行这个 ROP 链,我们使用我们指定的命令调用 WinExec。例如,如果我们运行命令“cmd”,我们将看到一个命令提示符正在生成,作为本地服务运行(相同的用户 WPAD 服务运行)。

不幸的是,从作为本地服务运行的子进程中,我们无法与网络通信,但我们可以做的是将我们的权限提升有效负载从内存中删除到本地服务可以从那里写入和执行它的磁盘位置。

阶段 5:权限提升

虽然本地服务帐户是服务帐户,但它没有管理权限。这意味着漏洞利用在系统上可以访问和修改的内容非常有限,特别是在利用后或系统重新启动后持续存在。虽然在 Windows 中总是可能存在未修复的权限提升,但我们不需要找到新的漏洞来提升我们的权限。相反,我们可以滥用内置功能从本地服务升级到系统帐户。让我们看一下 WPAD 的服务帐户已被授予的权限:

图 7:服务访问令牌的特权显示模拟特权

我们只有三个特权,但突出显示的特权 SeImpersonatePrivilege 很重要。此权限允许服务模拟本地系统上的其他用户。该服务具有模拟特权的原因是它接受来自本地系统上所有用户的请求,并且可能需要代表他们执行操作。但是,只要我们能够获得要模拟的帐户的访问令牌,我们就可以获得令牌用户帐户的完全访问权限,包括 SYSTEM ,这将为我们提供本地系统的管理员权限。

滥用模拟是 Windows 安全模型的一个已知问题(您可以通过搜索Token Kidnapping找到更多详细信息)。微软试图让特权用户更难获得访问令牌,但实际上不可能关闭所有可能的路线。例如,James 在 Windows 的 DCOM 实现中发现了一个漏洞,该漏洞允许任何用户访问 SYSTEM 访问令牌。虽然微软修复了直接权限提升漏洞,但他们没有,或者可能无法修复令牌绑架问题。我们可以滥用此功能来捕获 SYSTEM 令牌,冒充令牌,然后彻底破坏系统,例如安装特权服务。

有一个通过 DCOM ( RottenPotato )实现令牌绑架的现有实现,但是该实现是为与我们没有使用的 Metasploit 框架的getsystem命令一起使用而设计的。因此,我们在 C++ 中实现了我们自己的更简单的版本,它使用CreateProcessWithToken API直接生成带有 SYSTEM 令牌的任意进程。作为奖励,我们能够将其编译为 11KiB 大小的可执行文件,比 RottenPotato 小得多,这使得从 ROP 有效负载拖放到磁盘和运行变得更容易。

将它们捆绑在一起

当 WPAD 服务查询 PAC 文件时,我们提供利用 WPAD 服务并运行 WinExec 以删除并执行权限提升二进制文件的漏洞利用文件。然后这个二进制文件作为 SYSTEM 执行一个命令(在我们的例子中是硬编码的 'cmd')。

该漏洞在我们的实验中运行得非常可靠,但有趣的是,不需要 100% 可靠的漏洞 - 如果漏洞导致 WPAD 服务崩溃,当客户端从 WPAD 发出另一个请求时,将生成一个新实例服务,所以攻击者可以再试一次。UI 中不会显示 WPAD 服务已崩溃,但 Window Error Reporting 可能会发现崩溃并将其报告给 Microsoft,前提是用户没有禁用它。

事实上,我们的漏洞利用并没有优雅地清理,一旦它运行它的有效负载就会崩溃 WPAD 服务,所以如果我们在服务被利用后继续提供漏洞利用 PAC 文件,它只会再次被利用。您可以在图 7 中看到它的效果,这是在让漏洞利用服务器运行几分钟并在受害机器中发出大量 HTTP 请求后拍摄的。

图 7:我们是否让漏洞利用运行时间过长?

我们将很快在问题跟踪器中发布漏洞利用源代码。

结论

执行不受信任的 JavaScript 代码是危险的,在非沙箱进程中执行它更危险。即使它是由相对紧凑的 JavaScript 引擎(例如 jscript.dll)完成的,也是如此。我们在其中发现了 7 个安全漏洞,并成功地展示了从本地网络(及其他网络)对安装了 Fall Creators Update 的完全修补(在撰写本文时)Windows 10 64 位的可靠代码执行。

既然已经修复了错误,这是否意味着我们已经完成并且可以回家了?不太可能。尽管我们花费了大量的时间、精力和计算能力来查找 jscript.dll 错误,但我们并没有声称我们找到了所有这些错误。事实上,如果有 7 个错误,则很可能有第 8 个。因此,如果某些事情没有改变,我们很可能有一天会看到这样的链在野外使用(当然,乐观地假设攻击者还没有这种能力)。

那么,微软可以做些什么来让未来的攻击变得更加困难:

  • 默认禁用 WPAD。事实上,虽然其他操作系统都支持 WPAD,但 Windows 是唯一默认启用它的操作系统。
  • 将 JScript 解释器沙箱化到 WPAD 服务中。由于解释器需要执行具有明确定义的输入的 JavaScript 函数并返回输出字符串,因此沙盒应该非常简单。鉴于输入-输出模型的简单性,如果微软引入了一个与 seccomp-strict 具有相当限制性的沙箱,那就太好了:有些进程真的不需要比“接收一点数据”、“执行一点计算”更多的权限”、“返回一点数据”。

如果您想自行采取行动,使用目前未知的新漏洞防止此类攻击的唯一方法似乎是完全禁用 WinHttpAutoProxySvc 服务。由于其他服务依赖于 WPAD,有时这无法在服务 UI 中完成(“启动类型”控件将显示为灰色),但可以通过相应的注册表项完成。在“HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinHttpAutoProxySvc”下,将“Start”的值从 3(手动)更改为 4(禁用)。

这些是搜索“禁用 WPAD”时在网上常见的一些建议,这些建议在我们的实验中无法阻止攻击:

  • 在控制面板中关闭“自动检测设置”
  • 设置“WpadOverride”注册表项
  • 将“255.255.255.255 wpad”放在主机文件中(这将停止 DNS 变体,但可能不会停止 DHCP 变体)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档