“WebUI “是一个术语,用于宽泛地描述用网络技术(即HTML、CSS、JavaScript)实现的Chrome浏览器的部分UI。
Chromium中的WebUI的例子。
- Settings (chrome://settings)
- History (chrome://history)
- Downloads (chrome://downloads)
关于webui具体怎么工作在这里将不展开,请参考官方文档详细阅读,本文将重点介绍webui中常见的几类漏洞模式。
https://chromium.googlesource.com/chromium/src/+/master/docs/webui_explainer.md
find but no check end
我们将以一个简单的漏洞模式来学习webui的数据流传递。
具体的说就是每个WebUI都会注册很多WebUIMessageHandler,而每个Handler上又会注册多个Message Callback,每个Message Callback都有一个对应的Message Name,可以通过这个Message Name来调用到对应的webui函数,并传入参数。
具体来说就是形如以下调用:
1 | chrome.send("recordNavigation",[1337,0]); |
case1: issue-1303614
由于该漏洞代码只存在于chromium dev,不存在发行版中,所以没有CVE,只有对应的issue编号。
Root Cause
让我们看一下代码,这里注册了一个名为recordNavigation的Message Callback,它将对应调用到HandleRecordNavigation函数,并处理传入的参数。
它将对传入的参数列表依次调用ConvertToNavigationView,将其强制转换为NavigationView类型的枚举值,分别得到from_view和to_view。
但由于这里并没有检查传入的参数是否小于NavigationView类型能处理的最大值,注意这里仅仅只有一个debug check,这个debug check在release发行版里是不存在的,所以可以试做没有检查。
这将导致在EmitScreenOpenDuration函数处理cast之后得到的from_view的时候, 触发一个堆溢出。
这里它将对kOpenDurationMetrics列表进行find,但是由于没有检查传入的参数是否小于NavigationView类型能处理的最大值,所以它将find不到。
我们知道在c++里,find如果找不到,迭代器iter将指向end,这其实代表的是指向容器的最后一个元素的下一个。
而这里同样也没有检查find找不到的情况,也就是没有检查iter是否指向end,就直接解引用了。它同样也是使用了一个Debug Check,但这其实是无用的。
所以对iter解引用将直接越界,造成buffer overflow。
1 | // content::WebUIMessageHandler: |
poc
browsing chrome://diagnostics
and open devtools
execute chrome.send("recordNavigation",[1337,0]);
in console.
patch
补丁就是加上了我刚刚提到的没有加的检查。
1 | auto* iter = kOpenDurationMetrics.find(screen); |
other case
https://bugs.chromium.org/p/chromium/issues/detail?id=1303613
unique_ptr double init
case1: CVE-2022-2859
https://chromium.googlesource.com/chromium/src/+/08b5eaecf33165cda178517fa4ba070d1f598e16
1 |
|
[0] 当我们调用两次EnableFakePhoneHubManager, fake_phone_hub_manager_字段将会被初始化两次, 又由于fake_phone_hub_manager_是一个unique_ptr,所以前一次创建的FakePhoneHubManager将会被后一次创建释放掉。
[1] 但是第一次创建的fake_phone_hub_manager_的raw ptr还保存在PhoneHubUiController的phone_hub_manager_字段里
[2] 这将导致第二次调用EnableFakePhoneHubManager的时候,沿着EnableFakePhoneHubManager->SetPhoneHubManager->CleanUpPhoneHubManager
路径,再次使用到前一次被保存到phone_hub_manager_里的被释放的FakePhoneHubManager,造成UAF。
cross-thread calback race
case1: CVE-2022-1311
https://bugs.chromium.org/p/chromium/issues/detail?id=1310717
https://chromium.googlesource.com/chromium/src.git/+/HEAD/docs/threading_and_tasks.md
Chrome将运行UI并管理所有网页和插件进程的主进程称为“浏览器进程”或“浏览器”,而每个网页都运行在一个单独的进程里,这个进程称为渲染进程。
鉴于渲染进程在单独的进程中运行,所以Chrome有机会通过沙箱限制其对系统资源的访问,所有渲染器对网络和文件资源的访问都通过IPC来通知浏览器进程来完成。
在一个进程中,往往有如下几种线程:
- 一个 main thread
- 在 Browser 进程中 (BrowserThread::UI):用于更新 UI
- 在 Render 进程中:运行Blink
- 一个 io thread
- 在 Browser 进程中(BrowserThread::IO): 用于处理 IPC 消息以及网络请求
- 在 Render 进程中:用于处理IPC消息
- 一些使用 base::Tread 创建的,有特殊用途的线程(可能存在)
- 一些在使用线程池时产生的线程(可能存在)
1 | void CrostiniUpgrader::Backup(const ContainerId& container_id, |
我介绍一个我挖掘的漏洞,首先我们要知道Chrome线程内部是怎么实现任务的同步的,其实是通过派发一个回调给一个处理线程的MessageLoop,然后MessageLoop会调度该回调以执行其操作。
这个漏洞就是这么产生的,ThreadPool::PostTaskAndReplyWithResult是UI线程向线程池里的线程发送一个PathExists函数的回调,然后线程池会检查backup路径是否存在,然后当线程池执行完任务PathExists,它会向UI线程发送一个OnRestorePathChecked函数的回调,一个回调其实和一个闭包是相似的,它会包括一个函数指针和它使用的函数参数。
在这个过程中就可能产生条件竞争。因为OnRestorePathChecked的参数里包括了一个原始指针web_contents,这样的指针是没有被保护的,所以如果我们在线程池里正在执行PathExists的同时,我们在UI线程这边通过关闭网页把web_contents释放掉,从而当OnRestorePathChecked被发回到UI线程执行的时候,此时web_contents已经被释放掉了,解引用它的指针就会触发UAF。
other case
https://bugs.chromium.org/p/chromium/issues/detail?id=1320624
https://bugs.chromium.org/p/chromium/issues/detail?id=1322744
https://bugs.chromium.org/p/chromium/issues/detail?id=1311701
https://bugs.chromium.org/p/chromium/issues/detail?id=1304145
Listener no check destroyed
case1: issue-1315102
https://bugs.chromium.org/p/chromium/issues/detail?id=1315102
SupportToolMessageHandler::HandleStartDataExport
会创建一个 select_file_dialog_
[1] 并显示一个 SelectFileDialog对话框。
当 [1] 被调用时,this
原始指针被传递给ui::SelectFileDialog::Create
,并且传递的this
原始指针被保存在listener_
[2] 中。
当用户选择一个文件夹时,listener_->FileSelected(paths[0], index, params);
[3]被调用来处理用户的文件夹选择。
但是,SupportToolMessageHandler::~SupportToolMessageHandler
[4] 是默认析构函数,不会调select_file_dialog_->ListenerDestroyed();
将listener_
置为nullptr。
如果用户在 SupportToolMessageHandler
被释放后选择了一个文件夹(即 listener_
被释放),UAF 将在 [3] 中触发。
因此,我们可以构建以下 UAF 链:
- 通过chrome.send调用
SupportToolMessageHandler::HandleStartDataExport
- 通过关闭webui网页来释放
SupportToolMessageHandler
- 在SelectFileDialog里选择一个文件,在[3]中触发UAF。
1 | scoped_refptr<ui::SelectFileDialog> select_file_dialog_; |
- patch
1 | SupportToolMessageHandler::~SupportToolMessageHandler() { |
other case
https://bugs.chromium.org/p/chromium/issues/detail?id=1305068
https://bugs.chromium.org/p/chromium/issues/detail?id=1306391
https://bugs.chromium.org/p/chromium/issues/detail?id=1304884