长亭百川云 - 文章详情

「深蓝洞察」2023 年度最死而不僵的漏洞

DARKNAVY

90

2024-07-13

回顾 Android 的发展历程,各种反序列化相关的漏洞曾多次被公开披露和修复。

其中最具代表性的当属 Bundle Mismatch 相关的漏洞,它即是深蓝洞察去年发布的 2022 年度最“不可赦”的漏洞利用所揭示的,堪称史上最大规模的在野攻击所利用的核心漏洞。

Google 引入了新机制缓解此类漏洞,就当人们认为一切已经尘埃落定之时,新的绕过还是到来了。

以下为本期深蓝洞察年度安全报告的第五篇

05

Bundle, 是 Android 用于数据传递的一种特殊的键值对容器类型,通常用于 Android 进程间通信。

Bundle Mismatch 则利用序列化和反序列化不一致的差异绕过系统校验,获取以系统权限启动任意 Activity 的能力(LaunchAnyWhere)。

为了彻底解决 Bundle Mismatch 问题,Google 设计了一套名为 Lazy Bundle 的补丁。该补丁调整了不定长类型的序列化结构,在类型之后、具体数据之前,增加了一个数据块的长度字段。

__Lazy Bundle 的“lazy” 体现在,当 Bundle 反序列化遇到了不定长类型的值时,不直接对具体数据进行读取,而是创建一个 LazyValue 作为值,并记录下该数据在整个 parcel 中的偏移和数据的完整长度,然后跳过相应长度读取下一组键值对。

只有明确需要取出某个 LazyValue 的真实值时,才会跳回到记录的偏移处进行反序列化,最后将 LazyValue 替换为真实值。

AOSP 的源码注释中标注了相应的数据结构和 LazyValue 的参数以便人们理解,其中 mPositionmLength 即 LazyValue 记录的偏移和长度。

Lazy Bundle 于 2022 年推出的 Android 13 中实装。

如此一来,即使某个类型存在 Mismatch,Bundle 其他键值对的内容也不会受影响,缓解了 Bundle Mismatch 的利用。

Bundle Mismatch 利用的作者是 Michal Bednarski(@BednarTildeOne)。自 2017 年起,他在 GitHub 上分享了多种 Android 反序列化漏洞的利用和 writeup。

这一次,他不仅找到了 LazyValue 实现中存在的 UAF 漏洞,随后更是发现了一个可以绕过 Lazy Bundle 缓解措施的漏洞 "TheLastBundleMismatch (CVE-2023-21098、CVE-2023-45777)" ,在 Android 13 和 14 上成功重现了 LaunchAnyWhere 攻击。

通过分析 CVE-2023-45777 的补丁容易发现,TheLastBundleMismatch 漏洞在于 AccountManagerService 读取 intent 字段时并没有显式指定 getParcelable 的类型参数。

_Android 13 弃用了 Parcel 和 Bundle 中原先可以反序列化任意类型的一系列方法,取而代之的是增加了 clazz 参数限制类型的版本。_补丁即是使用更为健壮的函数替换了弃用版本。

`diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java   index 7a19d034c2c8..5238595fe2a2 100644   --- a/services/core/java/com/android/server/accounts/AccountManagerService.java   +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java   @@ -4923,7 +4923,7 @@ public class AccountManagerService                p.setDataPosition(0);                Bundle simulateBundle = p.readBundle();                p.recycle();   -            Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);   +            Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);                if (intent != null && intent.getClass() != Intent.class) {                    return false;                }   `

该漏洞的利用和 LazyValue 的实现弱点有关。

当一个 Bundle 重新序列化时,尚未被读出的 LazyValue 会使用 appendFrom直接将其指向的对应数据块(包括开头的 typelength 两个字段)复制到新的 parcel 中。

乍一看,这种处理方式似乎没有什么不妥之处,但如果攻击者有能力修改旧 parcel 中 typelength 两处数据,让 LazyValue 复制改动后的数据,事情就变得不一样了

在 Bundle 下一次被反序列化时,修改的 type 可以让这个 LazyValue 不再 “lazy”,从而改变读取后续键值对的位置,读出原本不存在的键,实现 Lazy Bundle 的绕过;修改 length 也可以达到同样的效果。

那么问题就转化为,是否存在这样一个 Parcelable 类型,其在反序列化的过程中会修改原始 parcel 的数据?

这种类型(至少在 AOSP 中)并不存在。

不过 @BednarTildeOne 找到了一个等效的组合android.os.PooledStringWriterandroid.content.pm.PackageParser$Activity

  • 前者并非 Parcelable 类型,但是却可以接受 Parcel 类型作为构造函数的参数。作为一个 “writer”,它向传入的 parcel 调用了 writeInt(0) 的写入操作。

  • 后者是一个 Parcelable,在反序列化时可以使用反射调用任意类型的构造函数,并传入 parcel 对象,实现了和 PooledStringWriter 的串联。

如果构造数据让写入的 0 覆盖 LazyValue 的 type,就能让 Bundle 下一次反序列化时将这个 LazyValue 解释成字符串类型,绕过缓解措施。

美中不足的是,PackageParser$Activity 会将反射构造的结果转换为 IntentInfo 类型,不可避免地导致类型转换异常。因此需要寻找另一个包含异常处理的 Parcelable,在它反序列化的 try-catch 流程中进行 PackageParser$ActivityPooledStringWriter 的反序列化。

这一次 AOSP 既不存在这样的 Parcelable,也不存在其他的等效组合。这可能要归功于上文提到的 Android 13 新增限制类型的 Parcel 方法,即使有少数几个 Parcelable 使用了异常处理,也无法控制它们去反序列化我们想要的类型。

AOSP 中不存在,不代表其他厂商 OEM 系统中不存在

在 2022 年度最“不可赦”的漏洞利用 中,我们介绍了一个三星 OEM 的漏洞利用链。这次 @BednarTildeOne 也是在三星中发现了 SemImageClipData 类型完美满足利用要求。

事情结束了吗?

还差最后一小步。 

如果直接在 intent 字段放一个 SemImageClipData 类型,反序列化的结果会被 AccountManagerService 强制转换为 Intent,再次出现异常。bundle.getParcelable 内部实际上有一层类型转换的错误处理,只要把 SemImageClipData 套一层数组成为 Parcelable[],函数最终就可返回null

至此,整个攻击利用链大功告成,其复杂程度和精妙程度令人叹为观止。

只可惜 SemImageClipData 这种类型不可多得。DARKNAVY 在尝试对国内品牌的旗舰机型复现漏洞时,并不能找到相似的替代品。

考虑到漏洞的核心在于反序列化时对 parcel 的修改操作,我们放弃 PooledStringWriterPackageParser$Activity 的组合拳,去寻找反序列化时直接修改 parcel、并且不会触发异常的类型。

最终我们找到一个 Parcelable,直接调用 unmarshall 修改了整个 parcel 的数据,借此成功完成了复现。

在复现中,我们利用漏洞分别启动了修改密码的页面和 Android 14 的彩蛋页面。这两个页面分别是设置应用和安卓系统本身的未导出 Activity。

该机型最新版本上已经修复此漏洞。值得一提的是,我们利用的这个 Parcelable 并非第一次出现在该品牌的手机中。在其他机型的 Android 12 上,我们就发现过它的存在。

而,TheLastBundleMismatch 在 Android 12 及之前并不能被利用:

原本 parcel 的读取是线性的,如果读取的途中修改了 parcel 数据,也只能在新旧数据之间二选一读到。LazyValue 的出现改变了这种线性,也让这个潜藏多年的类型能够被利用。

在 2014 年,为了修复 LaunchAnyWhere 漏洞,Android 增加了对 intent 的校验,于是 2017 年出现了 Bundle Mismatch 绕过校验。

2022 年新的 Lazy Bundle 修复 Bundle Mismatch,又在一年后被 TheLastBundleMismatch 绕过。

细心的读者可能发现,TheLastBundleMismatch 有两个 CVE 编号。

这是因为,第一次的补丁在修复另外一个漏洞时无意间被去除,导致漏洞死而复生。

旧的攻击面的修复却引入了新的攻击面,只是每次的利用越来越复杂、有越来越多的限制。

我们相信在持续不断的攻防对抗中系统会变得更安全,但:TheLastBundleMismatch 真的会是 "The Last"  吗?

参  考:

[1] https://github.com/michalbednarski/TheLastBundleMismatch

明日,请继续关注《深蓝洞察 | 2023 年度安全报告》第六篇

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2