盘点并分析2019年发现的Chromium IPC漏洞 - 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com

盘点并分析2019年发现的Chromium IPC漏洞

41yf1sh 漏洞 2020-01-21 10:00:00
收藏

导语:在本篇文章中,我们将深入对2019年新发现的Chromium IPC漏洞进行研究,期望能揭示Chrome IPC攻击面中反复出现漏洞的成因。

一、概述

在本篇文章中,我们将深入对2019年新发现的Chromium IPC漏洞进行研究。我们将探讨一些新的发现,以及来自更广泛的安全研究领域的最新发现,期望能揭示Chrome IPC攻击面中反复出现漏洞的成因。感谢Ned Williamson针对IPC接口与libprotobuf-mutator之间的模糊测试的研究,以及Mark Brand关于如何调用、如何从Javascript进行模糊测试方面的工作,这些研究成果共同证明,Chromium IPC攻击面是导致许多漏洞的源头。在我们本次的研究中,共涉及六个新漏洞,分别是:CVE-2019-13688(995964)、CVE-2019-5876(997190)、CVE-2019-13700(998431)、CVE-2019-13687(998548)、CVE-2019-13699(1001503)和CVE-2019-13695(1004730)。

所有这些漏洞均已披露,并且已经在2019年9月至10月之间的Chromium版本(M77、M78)中实现修复。这些漏洞的严重程度属于高危,如果被攻击者成功利用,将允许受感染的渲染器实现Chromium沙箱逃逸。

由于Chromium的安全问题在14周以后受到限制,并且关于漏洞的详细信息可以公开找到,因此我在本文中将不会详细介绍每个漏洞的详细细节,而是向大家展现我是如何找到这些漏洞的,以及说明我如何利用CodeQL来帮助发现这些漏洞。

如果各位读者对漏洞的详情感兴趣,大家可以前往Chromium网站的Issue页面查看。除了ID为1001503的漏洞(CVE-2019-13699)以外,所有这些问题均是在CodeQL的帮助下通过人工代码审计发现。

首先,我要介绍Chromium的沙箱模型,以及这些IPC问题的影响。之后,我将研究这种类型的几个典型漏洞,并将其分为不同的类别。这样的分类将帮助我集中精力去发现新的漏洞。

二、Chromium的多进程结构

Chromium的多进程体系结构已经在官方文档中得到了充分的说明,因此在这里我只做简要的介绍。Chromium浏览器在不同的进程中运行,每个进程都具有不同的特权,并负责不同的任务。这种架构不进位浏览器提供了更高的稳定性,例如:渲染器崩溃将不会影响浏览器或任何其他渲染器,并且还通过使用操作系统级的沙箱将沙箱应用于不同的进程,从而提供了更为精细的特权模型。不同的进程通过IPC消息相互通信,低特权进程可以通过发送IPC消息来请求高特权进程执行特定任务。

从安全的角度来看,有两个主要的进程上下文:渲染器进程池和浏览器进程。渲染器进程池是一组低特权进程,其中运行v8和blink等等。通常,渲染器进程在所有Chromium进程中具有最低的特权,并且它们被大量沙箱化。通常情况下,渲染器进程中的远程执行代码漏洞需要与浏览器进程中的另一个漏洞共同利用,以实现沙箱逃逸。但是也有例外,例如,在Android上,Android binder进程和一些Android服务都可以在渲染器沙箱内部访问,并且这些进程中的漏洞也可以用于沙箱逃逸,可以参考这里这里这里。另一个值得关注的例外是在2019年3月披露的漏洞,该漏洞允许渲染器直接利用Windows 7上win32k.sys内核驱动程序中的漏洞。

在本文中,我将重点介绍通过IPC通道触发的漏洞。

三、Chromium IPC接口

在Chrome的进程之间,有两种主要的IPC通道。一种是Mojo接口,这种方式较新且较为常见。另一种是一个旧IPC接口,该接口并不常见但是仍在使用。

3.1 Mojo接口

有关Mojo接口的详细信息可以在这里找到。在本节中,我将重点放在代码中如何表示接口,以及在哪里寻找接口,在这里不会过多地重复文档中已有的内容。

Mojo接口在Chromium源代码的.mojom文件中定义。在构建Chromium时,这些文件用于生成C++源代码和JavaScript绑定。在构建后,可以在Chromium检出的src/out/\

这些Mojo接口在浏览器进程中的实现主要位于content/browser目录下。但是有一些例外,例如PaymentRequest接口就位于/src/components/payments中。

要使用JavaScript访问这些接口,可以使用Mojo.bindInterface方法。例如,在Mark Brand的项目中,PaymentRequest接口的用法如下:

var payment_request = new payments.mojom.PaymentRequestPtr();
    Mojo.bindInterface(payments.mojom.PaymentRequest.name,
                       mojo.makeRequest(payment_request).handle);

其中,第一行创建了PaymentRequestPtr,它作为渲染器端的代理,可以绑定到在浏览器过程中实现的PaymentRequest。在第二行中,Mojo.bindInterface用于将payment_request绑定到在浏览器进程中运行的payments.mojom.PaymentRequest接口。之后,可以从JavaScript调用此接口定义的方法。例如:

      payment_request.init(
        payment_request_client,
        [],
        payment_details,
        payment_options);

这样一来,将在PaymentRequest中调用init方法。

测试或模糊测试IPC接口的另一种方法,可以借助例如Fuzz Harness的小型单元测试来实现与之直接交互。例如,Ned Williamson编写的AppCacheFuzzer使用libprotobuf-mutator创建IPC消息,直接对组件进行模糊测试。通常情况下,这要通过编写针对目标组建的模糊工具来完成,在模糊工具中还要设置正确的环境,然后提供一个.proto文件进行定义,改文件定义了用于对接口进行模糊处理的消息。例如,对于AppCache,命令定义了mojo接口中公开的不同方法:

// Based on blink::mojom::AppCacheBackend and blink::mojom::AppCacheHost
// interfaces.
// See third_party/blink/public/mojom/appcache/appcache.mojom
message Command {
  oneof command {
    RegisterHost register_host = 1; //<-- AppCacheBackend::RegisterHost

对于每个命令,在另一条名称相同的消息中定义了明确的使用方式:

message RegisterHost {
  required HostId host_id = 1; //<-- Takes an argument of type HostId
}

有了这样的定义,libprotobuf-mutator将能够生成带有正确参数类型的调用来实现对组件的模糊测试。

3.2 旧IPC接口

尽管旧的IPC接口如今已经不是那么普遍,并且许多接口已经迁移到Mojo接口,但它们仍然还在使用中。事实上,我这次找到的一个漏洞就是通过旧接口实现的。

通常,使用该接口的类将定义一个OnMessageReceived方法,在该方法中,不同的消息将映射到不同的处理程序:

MyClass::OnMessageReceived(const IPC::Message& message) {
  ...
  IPC_MESSAGE_HANDLER(ViewHostMsg_MyMessage, OnMyMessage); //<-- OnMyMessage is the handler
  ...
}

OnMyMessage处理程序的行为类似于Mojo接口中的方法,该方法处理来自另一个进程的IPC消息。

要将消息从渲染器发送到另一个进程,可以使用Send方法:

Send(new ViewHostMsg_MyMessage());

我不知道任何JavaScript接口,我通常会选择修补渲染器以发送消息并测试这些接口。

四、历史漏洞分析

在本章中,我将研究今年上半年报告的一些漏洞。在这里,将重点讨论由原始指针和唯一指针之间的交互所引发的简单问题,以及与回调有关的问题。Ned Williamson在2018年发现了共享指针和原始指针之间的相互作用,同时还有其他一些相当复杂的问题,但它们不在本文的讨论范围之内。

我将重点回顾并分析以下漏洞:

Mark Brand(Project Zero):P0_1730、P0_1735、P0_1743、P0_1755、P0_1754、P0_1767、P0_1803

Brendon Tiszka:977462

Guang Gong(Qihoo 360 Alpha):956597

Gengming Liu,Jianyu Chen,Zhen Feng,Jessica Liu(Tencent Keen Security Lab):941746

从表面上看,其中许多问题都是对象生命周期管理问题。原始指针的引用被(存在漏洞的)渲染器释放,然后尝试在浏览器中使用。但是,这样的分类过于笼统,并不能帮助我们真正寻找到漏洞。因此,我决定将问题分类为更小和更有针对性的类别。由于我们在这里并不是重点分析其根本原因,因此我也没有按照问题的根本原因进行分类,所使用的分类方式将有助于我更好地理解问题并找到漏洞。

4.1 类别1:非平凡的原始指针字段管理

这是一个范围非常广泛的分类。它本身并不是Bug,当然也不是漏洞。但是,由于在Use-After-Free场景中可能经常会遇到复杂的情况,因此我们没有找到更满意的方式来进一步描述这种类型的漏洞,所以我最终使用了这个分类的名称。

为了进一步说明我所指的“非平凡(Non-trivial)”与“平凡”(Trivial)的原始指针字段管理的含义,下面是一些我认为是平凡或直接的原始指针字段管理场景的示例。

1、原始指针指向所有者

这种场景非常常见。实际上,原始指针字段通常指向对象的所有者,在这种情况下,不太可能遇到对象生存周期的问题,例如:

class A {
  ...
  A() {
    b_ = std::make_unique(this);
  }
  std::unique_ptr b_;
}
 
class B {
  ...
  B(A* a) {
    a_ = a;
  }
 
  A* a_;
}

如果B的所有实例都是在A的构造函数中构造的,那么a_很可能指向该对象的所有者,除非重新分配了该对象,这是非常少见的。

2、原始指针类的析构函数删除其引用

这种情况也很常见。通常,保存原始指针的类可以“观察”指向对象的生命周期,并且在删除该对象时,原始指针也会被删除。例如:

class A extends BObserver {
  ...
  A(B* b) {
    b_ = b;
    b_->AddObserver(this);
  }
 
  ~A() {
    if (b_) {
      b_->RemoveObserver(this);
    }
  }
 
  void OnBDestroyed(B* b) {
    if (b == b_) {
      b_ = nullptr;
    }
  }
  B* b_
}
 
class B {
  ...
  ~B() {
    for (BObserver* observer : observers_) {
      observer->OnBDestroyed(this);
    }
  }
}

此类保护方法的一个示例是FrameServiceBase,它具有指向RenderFrameHost的原始指针,但会观察其生命周期以防止Use-After-Free。

未被保护的原始指针字段通常由更加复杂的逻辑来管理,但大多数情况下都是可行的。然而,清理逻辑有时可能会存在缺陷,从而导致出现漏洞。

例如,P0_1735。在这个例子中,PaymentSheetViewController类包含两个原始指针字段spec_和state_。在这种情况下,spec_和state_属于PaymentRequest对象,如果不删除PaymentSheetViewController,就不能删除它们。但是,这不足以保证PaymentRequest中的spec和state字段无法被重置。实际上,可以在PaymentRequest::Init方法中重置这些字段,该方法可以直接从mojo接口调用。因此,受感染的渲染器可以轻松释放spec_和state_的后备对象,从而导致Use-After-Free。

该类别中的其他已知问题包括:P0_1754(涉及整数溢出)、P0_1767(同样涉及整数溢出)、P0_1803、941746、956597,我发现997190、995964、1004730也属于此类问题。

4.2 类别2:回调存储原始指针

在IPC相关代码中,回调被广泛使用。Chrome中的回调通常由base::BindOnce或base::BindRepeating函数创建。创建回调时,可以将参数绑定到回调。不同类型的绑定指定绑定状态如何管理。其中,最为危险的是base::Unretained绑定:

base::Bind(&MyClass::Foo, base::Unretained(ptr));

这表明该回调函数不拥有ptr,并且在执行该回调函数时,调用者需要保证ptr仍然有效。尽管非常危险,但这是一个众所周知的问题,开发人员也通常会意识到其后果。Unretained的许多用法都包含详细的注释。该类问题的两个示例是P0_1743和P0_1755,而我的发现(998548)也属于这类问题。

4.3 类别3:RenderFrameHost的生命周期问题

RenderFrameHost在浏览器进程中用于表示框架。这是一个长期存在的对象,它比许多IPC服务的生命周期都要更长。但是,在某些情况下,绑定服务的服务或方法可能会超过RenderFrameHost的生命周期。例如,在P0_1730中,使用BindRepeating创建的回调通过RenderFrameHost的未保留原始指针发布到IO线程。由于在UI线程中创建并销毁了RenderFrameHost,这导致了竞争条件,其中RenderFrameHost在UI线程中被销毁,而回调在销毁后仍会在IO线程中访问它。另一个示例是977462,其中一个超过RenderFrameHost生命周期的服务将保留指向它的原始指针。

五、总结

在本篇文章中,我回顾了2019年发现的一些Chromium IPC漏洞。同时,也说明了在我们的研究过程中如何利用特定的QL库和查询。希望这篇文章能对大家的漏洞发现过程有所帮助!

本文翻译自:https://securitylab.github.com/research/chromium-ipc-vulnerabilities/如若转载,请注明原文地址
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论