【CVE-2022-28282】Mozilla Firefox 资源管理错误漏洞

作者:Sec-Labs | 发布时间:

漏洞简介

Mozilla Firefox是美国Mozilla基金会的一款开源Web浏览器。
Mozilla Firefox存在安全漏洞,该漏洞源于DocumentL10n::TranslateDocument中存在释放后重用问题。

POC

https://github.com/Pwnrin/CVE-2022-28282

简单QL的老把戏

这是个简单的老把戏。
如果我们在Object的原型中定义了 "then "的Getter,当Promise->resolve获得Object作为参数时,就会触发用户的JS回调。

https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise.resolve

我写了几条简单而 "暴力 "的规则来搜索可能由这一技巧引起的UAF问题。
比如说。

  • 两个原始指针之间的回调
  • 在回调中释放了一个成员变量
  • ......
from Function magic_func,Function g_resolve,FunctionCall g_setva_fc,VariableAccess va,VariableAccess setnull_va,Expr e_tmp1,Expr e,FunctionCall fc,FunctionCall fc_tmp1,FunctionCall fc_tmp2,FunctionCall fc_tmp3,FunctionCall fc_tmp4,FunctionCall fc_tmp5
where g_resolve.getName().regexpMatch("ResolvePromiseInternal")
and g_resolve.getParentScope().toString().matches("js")
// mMember
and va.getControlFlowScope()=magic_func
and va.getEnclosingFunction() = magic_func
and va.getTarget().getName().regexpMatch("m([A-Z])((.)*)")
and not va.getTarget().getName().matches("mImpl")
// mMember->trigger()->js::ResolvePromiseInternal()
and e.getEnclosingFunction() = magic_func
and fc.getEnclosingFunction() = magic_func
and fc.getTarget().getName().matches("operator->")
and va.getParent() = fc
and fc.getParent() = fc_tmp1
and fc_tmp1.getTarget() = fc_tmp2.getEnclosingFunction()
and fc_tmp2.getTarget() = fc_tmp3.getEnclosingFunction()
and fc_tmp3.getTarget() = fc_tmp4.getEnclosingFunction()
and fc_tmp4.getTarget() = fc_tmp5.getEnclosingFunction()
and fc_tmp5.getTarget() = g_resolve
// exists: mMember = NULL
and setnull_va.getTarget()=va.getTarget()
and g_setva_fc.getTarget().getName().matches("operator=")
and setnull_va.getParent() = g_setva_fc
and e_tmp1.toString().matches("0")
and e_tmp1.getParent() = g_setva_fc
select magic_func,magic_func.getParentScope(),va,g_setva_fc,fc_tmp1

正如你所看到的:这个规则非常简单,有很多地方可以改进。

  • 搜索的暴力方式
  • 成员变量不必设置为空
    ......

(但这的确是我的一个QL的初始版本,通过改进这些规则,我发现了一些其他有趣的东西)。

通过调整FunctionCall递归的层数,我得到了一个结果的集合。
不幸的是,在初始结果集中只有一个元素。幸运的是,这个结果里有一些有趣的东西。
这个结果

1  OnParsingCompleted     Document  mDocumentL10n  ......
2  LocalizationLinkAdded  Document  mDocumentL10n  ......

分析

void Document::LocalizationLinkAdded(Element* aLinkElement) {
  if (!AllowsL10n()) {
    return;
  }
......
......
  if (!mDocumentL10n) {
    Element* elem = GetDocumentElement();
    MOZ_DIAGNOSTIC_ASSERT(elem);

    bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
    mDocumentL10n = DocumentL10n::Create(this, isSync);
    ......
  }

  mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));

  if (mReadyState >= READYSTATE_INTERACTIVE) {
    mDocumentL10n->TriggerInitialTranslation(); // **** 1 ****
  } else {
......
......
  }
}

void DocumentL10n::TriggerInitialTranslation() {

  ······
  ······
  nsTArray<RefPtr<Promise>> promises;

  ErrorResult rv;
  promises.AppendElement(TranslateDocument(rv));
  if (NS_WARN_IF(rv.Failed())) {
    InitialTranslationCompleted(false);
    mReady->MaybeRejectWithUndefined();
    return;
  }
  promises.AppendElement(TranslateRoots(rv));   // **** 2 ****
  Element* documentElement = mDocument->GetDocumentElement(); // // **** 3 ****
  ......
  ......
}

当一个HTMLLinkElement(with rel="localization")被添加到文档中时,该文档将创建一个DocumentL10n并触发TriggerInitialTranslation。在Document::LocalizationLinkAdded的(1)到DocumentL10n::TriggerInitialTranslation的(2)的路径中,只有文档在mDocumentL10n中持有DocumentL10n的引用。
在TranslateRoots(2)中,该函数将使用一个Promise来解决ErrorResult rv。如果我们之前在Object的原型中定义了 "then "的Getter,Promise->resolve将在TranslateRoots(2)中触发用户的JS回调。而且我们可以把HTMLLinkElement的rel设置为null来触发Document::LocalizationLinkRemoved。它将把mDocumentL10n设置为nullptr并删除DocumentL10n的引用,然后我们可以在gc中销毁该对象。当程序返回到TriggerInitialTranslation时,它将导致(3)中的Use-After-Free(访问成员变量:mDocument)。
漏洞

由于AllowsL10n中有一个页面权限检查,该漏洞需要通过Web扩展来触发。

标签:漏洞分享