2024漏洞挖掘

有幸拿到了今年的top chrome vrp researchers

记录2024年被带着挖的几个漏洞

漏洞挖掘历程

CVE-2024-5171&CVE-2024-5197

位于俩个基础库的堆溢出,由于是单独把库拉出来测的当时没有考虑到是否能在chrome内触发导致没有bug bounty。

但可能是由于libwebp事件过去不久外加这个库的应用范围广泛 cvss给了个10.0 critical评分拉满。其实当时发现chrome中无法触发应该考虑去刷其他src的。。。

CVE-2024-6992

angle oob,也是和之前一样的原因 只是单独把angle库拿出来测但是没有考虑到在chrome中的情形,虽然能在mac中触发(由于mac中的chrome有个flag被启用导致触发路径可以一致)但可惜是个null pointer deference,在angle库中的确是一个oob access。

到此问题就显而易见了。真实场景中和测试场景中的路径不一致导致很多时间被浪费。吸取经验后 后续在检测策略上做了调整

CVE-2024-9369

后面和师兄商量了下转变了策略开始扫整个chromium。

ipcz由于边界校验不全导致的有条件且有限的越界写。

正巧赶上chrome bug bounty调整,一个oob外加高质量报告给了$35000

CVE-2025-0437

共享内存 TOCTOU OOB ACCESS,写这篇博客的时候漏洞报告才上报不久,具体cve号后续更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::unique_ptr<HistogramBase> PersistentHistogramAllocator::GetHistogram(
Reference ref) {
PersistentHistogramData* data =
memory_allocator_->GetAsObject<PersistentHistogramData>(ref);[1]
const size_t length = memory_allocator_->GetAllocSize(ref);[2]

if (!data || data->name[0] == '\0' ||
reinterpret_cast<char*>(data)[length - 1] != '\0' ||
data->samples_metadata.id == 0 || data->logged_metadata.id == 0 ||
(data->logged_metadata.id != data->samples_metadata.id &&
data->logged_metadata.id != data->samples_metadata.id + 1) ||
[3]HashMetricName(data->name) != data->samples_metadata.id) {
return nullptr;
}
return CreateHistogram(data);
}

具体漏洞出在[1]和[2]check和use间有个时间差可以打

打[1]处检查了size的大小必须大于0x68外加上后续reinterpret_cast<char*>(data)[length - 1] != '\0'的check用于保证传入HashMetricName的字符串是以0结尾的,但是在[2]处真正取到length的时候仅仅检查了size是否大于0x10 然后返回size-0x10即得到的length值。因此这里可以条件竞争pass后续的check。

在进入函数HashMetricName前会对参数进行strlen,由于之前的pass被绕过,字符串可以特别长以至于填充到共享内存末尾,那么strlen就会oob access到下一块共享内存,这里分为三种情况。

1
2
3
如果下一块共享内存未分配即crash
如果下一块共享内存以0开头则仅仅oob了下没有大影响
如果下一块共享内存不是以0开头如sqlite数据库情形,那么就会返回一个大length,后续HashMetricName在计算hash的时候会memcpy,导致一个oob read

如果pass了[3]处的check那么就会进入CreateHistogram函数,这里是在计算字符串的hash和已有的hash进行比较,都是存储与共享内存中。所以‘理论上’这个check可以绕过,至于后续的CreateHistogram函数貌似没有多大影响就没看了,本想着能不能让这个oob read变成oob write的。

师兄要求太高了一定要写出完美的poc,尽可能的拿到high quality report,写出poc要做的就不止这些了,首先得找到共享内存的写入处然后分析逻辑,记录共享内存的base并写入对应的数据,然后找到句柄close处在通知broker进程处理完前创建出一个线程不断死循坏写入数据。这里还有个离谱的坑点,如果只写个死循坏写入 编译器不会报错但是会把你的代码改成int3,导致浏览器打开就卡死,加上sleep可以解决但是加上sleep后这种条件竞争的漏洞很难触发可以说几乎不可能。只能是把编译器的策略关闭才行。

也还好,写poc也能学到不少东西,有很多自己觉得很了解的东西只有在真正实践后才会发现原来还有这么多坑点。

由于是条件竞争+越界读Google给算成高度缓解,后续漏洞给了2k刀。正巧这2k刀的vrp reward直接跃升成 2024 top 20 chrome vrp researchers了。

漏洞复现历程

CVE-2024-3157

重新看了下cve-2024-3157,当初决定看mojom也是因为这个漏洞,简单来说是缺乏校验导致size的不一致造成oob,当时看到这个漏洞就提出了一个假设 mojom组件目前还是受类似的内存破坏漏洞影响。今天仔细分析了下感觉看的还是半知半解。不确定为什么触发oob时的栈回溯会那么的抽象。

整体的成因是因为image中的size和create出的shared memorysize没有校验

1
2
3
size_t byte_size = sk_bitmap.computeByteSize();
base::WritableSharedMemoryRegion region =
base::WritableSharedMemoryRegion::Create(byte_size);

在gpu进程中会调用如上代码通过image的长宽高计算size然后create sharedmemory,如果gpu进程被控即可伪造较小的共享内存创建

在broker进程中调用到bool StructTraits<viz::mojom::BitmapInSharedMemoryDataView, SkBitmap>::Read反序列化函数时没有做校验导致oob

CVE-2024-9954

UAF 原理是RenderFrameHost和AIManagerKeyedService生命周期的不一致加上没有miracle ptr防护。

看倒是能看懂 但是自己能不能挖出来还不好说 包括poc的编写 自己对于mojojs这块的了解还是不够深入。

iframe被创建出后首先bind mojo interface如下

let aiManager = new blink.mojom.AIManagerPtr(e.handle);

这个会触发add操作,renderframehost会被保存在broker中

后续poc中removeChild然后进行对应mojoapi调用aiManager.createTextSession(AITextSession_receiver, undefined, undefined, []);来触发uaf。

除此之外可以看到CreateSummarizer,reateWriter等函数也对receivers_.current_context()有引用

针对该漏洞的patch如下
https://chromium-review.googlesource.com/c/chromium/src/+/5872369
将receivers_生命周期与renderframehost绑定

1
2
3
4
context_bound_object_set->AddContextBoundObject(
std::make_unique<AIManagerReceiverRemover>(
base::BindOnce(&AIManagerKeyedService::RemoveReceiver,
weak_factory_.GetWeakPtr(), receiver_id)));

当renderframehost释放时对应receiver也会释放。

后续跟进了一波发现chrome删除了这部分代码,原因是keredservice生命周期是与BrowserContext关联,超出单个document所以很容易出现uaf,外加上目前有替代方案所以直接删除了。

CVE-2021-37973

uaf同样的renderframehost

从报告来看 主要问题在于CreateChildFrame函数的frame_owner_element_type参数会判断是否为kPortal 或 kFencedframe,如果是则会认为当前为一个虚拟帧。后续的RenderFrameCreated不会被调用即不会被标记为kcreate状态 后续析构时也不会通知其WebContents对象,导致其无法感知renderframehost被销毁,但是BrowserInterfaceBroker仍可以被调用用于bind接口如WakeLockService,从而导致了uaf。
对于这个漏洞的patch,开发人员在OnCreateChildFrame函数中增加了check,检查type是否为kPortal 或 kFencedframe。之所以要这样做是为了防止攻击者从这一条路径去创建该类frame,

正常情况下应该是通过const portal = document.createElement('portal')去创建,这个后续会调用到addframe,而CreateChildFrame一样,如果从CreateChildFrame出发去创建就会导致uaf,所以patch单纯是在OnCreateChildFrame中做校验。

issue41486859

容器溢出 逻辑洞,dcheck相关,后续补丁改为了check。

个人感觉这类漏洞的挖掘思路是寻找异步的api,这类api可能会存在一些条件竞争的问题。具体案例复现的还不够多,可能在多来几个能总结出一些共性。

对于该漏洞就是异步api检查不够全 是 dcheck导致在release发布版中不存在使得可以出现find找不到情况出现容器溢出。以该漏洞为例

close为异步函数 定义其的mojom文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface FileSystemAccessAccessHandleHost {
// Promises to the browser that the file and AccessHandle were closed. This
// allows the browser to release the lock held on the owning
// FileSystemFileHandle.
//
// Well-behaved renderers will call this method after closing the associated
// file descriptor received by the
// FileSystemAccessFileHandle.OpenAccessHandle().
//
// A compromised renderer may lie and call this method without closing its
// corresponding file descriptor. This may allow it to observe writes from
// other renderers to the same (origin-scoped) file, so no cross-origin data
// would be leaked.
[Sync]
Close() => ();
};

c++后端实现如下

1
2
3
4
5
6
7
8
9
10
11
void FileSystemAccessAccessHandleHostImpl::Close(CloseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!close_callback_);

// Run `callback` when this instance is destroyed, after capacity allocation
// has been released.
close_callback_ = base::ScopedClosureRunner(std::move(callback));

// Removes `this`.
manager_->RemoveAccessHandleHost(this);
}

首先把callback复制然后remove this,this被删除后自动执行callback函数。

但是需要注意的是 这个函数是异步的所以前端可以多次调用,比如像poc里那样写成如下格式

1
2
access_handle_host.close();
access_handle_host.close();

后端实现时确实写了check DCHECK(!close_callback_);,该check不允许close api被多次调用 会检查close_callback_值是否不为空,但这是DCHECK,release打包后会消失。现在分析这样的影响,查看后续函数调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void FileSystemAccessManagerImpl::DidCleanupAccessHandleCapacityAllocation(
FileSystemAccessAccessHandleHostImpl* access_handle_host) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(access_handle_host);

// We cannot destroy `access_handle_host` by erasing it from the
// `access_handle_host_receivers_` set.
//
// The destruction of a `FileSystemAccessAccessHandleHostImpl` can trigger the
// creation of another. This means that if we directly erase
// `access_handle_host` from the set, `access_handle_host_receivers_` `erase`
// could call into `access_handle_host_receivers_` `insert` (in
// `CreateAccessHandleHost`) which is undefined behavior. Instead, we'll move
// it out of the set before erasing and then destroying.
size_t initial_size = access_handle_host_receivers_.size();
auto iter = access_handle_host_receivers_.find(access_handle_host);
auto access_handle_host_receiver = std::move(*iter);
access_handle_host_receivers_.erase(iter);

size_t count_removed = initial_size - access_handle_host_receivers_.size();
DCHECK_EQ(1u, count_removed);
}

由于存在条件竞争的可能当第二次close被调用时access_handle_host大概率已经被erase,所以

auto iter = access_handle_host_receivers_.find(access_handle_host);找不到 返回容器末尾,后续的move操作出现容器溢出。

下面看看patch

1
2
3
4
5
6
7
8
9
10
11
12
13

void FileSystemAccessAccessHandleHostImpl::Close(CloseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (close_callback_) {
receiver_.ReportBadMessage("Close already called on SyncAccessHandle.");
return;
}
// Run `callback` when this instance is destroyed, after capacity allocation
// has been released.
close_callback_ = base::ScopedClosureRunner(std::move(callback));
// Removes `this`.
manager_->RemoveAccessHandleHost(this);
}

DCHECK删除改check 外加取access_handle_host时进行一次判断

1
2
auto iter = access_handle_host_receivers_.find(access_handle_host);
CHECK(iter != access_handle_host_receivers_.end());

不确定为什么要这么个修法 感觉不是很优雅的样子

CVE-2024-6988

同样是异步调用出现的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void DownloadManagerTabHelper::SetCurrentDownload(
std::unique_ptr<web::DownloadTask> task) {
// If downloads are persistent, they cannot be lost once completed.
if (!task_ || (task_->GetState() == web::DownloadTask::State::kComplete &&
!WillDownloadTaskBeSavedToDrive())) {
// The task is the first download for this web state.
DidCreateDownload(std::move(task));
return;
}

__block std::unique_ptr<web::DownloadTask> block_task = std::move(task);
[delegate_ downloadManagerTabHelper:this
decidePolicyForDownload:block_task.get()
completionHandler:^(NewDownloadPolicy policy) {
if (policy == kNewDownloadPolicyReplace) {
DidCreateDownload(std::move(block_task));
}
}];
}

downloadManagerTabHelper生命周期和页面相绑定 如果异步函数被调用时页面已经被关闭则会触发uaf

patch整体的逻辑没变主要是用了weakptr保证了生命周期的同步

base::WeakPtrFactory<DownloadManagerTabHelper> weak_ptr_factory_{this};

issue40057634

一样的漏洞 一样的原理 一样的patch 这俩个都是mac|ios端特有的问题,感觉是后续开发人员在将代码移植到objective c时一些编程习惯疏忽导致的一系列类似的漏洞。

这类漏洞的重点在于理解生命周期。像这个poc如下

1
<input type="color"/>

触发流程首先点击触发异步调用 然后快速切换桌面 切换桌面是为了触发free,然后再切回后点击触发use,从而出现uaf。

原始函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EyeDropperViewMac::EyeDropperViewMac(content::EyeDropperListener* listener)
: listener_(listener) {
if (!listener_)
return;
if (@available(macOS 10.15, *)) {
color_sampler_.reset([[NSColorSampler alloc] init]);
[color_sampler_ showSamplerWithSelectionHandler:^(NSColor* selectedColor) {
if (!selectedColor) {
listener_->ColorSelectionCanceled();
} else {
listener_->ColorSelected(skia::NSSystemColorToSkColor(selectedColor));
}
}];
}
}

patch和之前类似 头文件中EyeDropperViewMac使用weakptr来维护+原漏洞函数逻辑不变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
EyeDropperViewMac::EyeDropperViewMac(content::EyeDropperListener* listener)
: listener_(listener), weak_ptr_factory_(this) {
if (!listener_)
return;
if (@available(macOS 10.15, *)) {
color_sampler_.reset([[NSColorSampler alloc] init]);
// Used to ensure that EyeDropperViewMac is still alive when the handler is
// called.
base::WeakPtr<EyeDropperViewMac> weak_this = weak_ptr_factory_.GetWeakPtr();
[color_sampler_ showSamplerWithSelectionHandler:^(NSColor* selectedColor) {
if (!weak_this)
return;
if (!selectedColor) {
listener_->ColorSelectionCanceled();
} else {
listener_->ColorSelected(skia::NSSystemColorToSkColor(selectedColor));
}
}];
}
}


2024漏洞挖掘
http://www.psbazx.com/2024/11/11/2024漏洞挖掘/
Beitragsautor
皮三宝
Veröffentlicht am
November 11, 2024
Urheberrechtshinweis