TikTok安卓应用渗透测试案例:从WebView漏洞到远程代码执行

本文详细分析了TikTok安卓应用的安全漏洞链,包括WebView JavaScript注入、Intent解析绕过、Zip Slip漏洞利用等,最终实现远程代码执行。文章提供了完整的技术分析和渗透测试方法论。

从WebView到RCE:TikTok安卓应用渗透测试案例研究

入口点

WebView是寻找安全问题的丰富资源。开发者经常自定义WebView以实现特定目标,这种自定义正是我们需要评估的重点。

自定义评估包括:

  • JavaScript是否启用?
  • 是否有JavaScript接口,其实现是什么?
  • 是否允许文件方案?
  • 是否有WebViewClient,如果有,其方法的实现是什么?

tiktok://webview深度链接在加载URL参数之前实施了URL验证,以确保仅加载白名单中的主机名。乍一看我们无法进入,但稍作休息后重新审视,你会发现不同的视角。

进入WebView

虽然无法在TikTok WebView中加载我们的URL,但WebViewClient方法通过onPageStartedonPageFinishedshouldOverrideUrlLoading处理在WebView上加载的URL。即使无法在WebView上加载我们的主机名,也值得检查。

开发者实现了一个令人印象深刻的功能。有一些可以离线加载的文件称为"falcon"。当URL在WebView中加载时,如果它是"falcon" URL,它会被拦截以从离线缓存文件加载。

在拦截过程结束时,执行了以下代码行:

1
this.a.evaluateJavascript("JSON.stringify(window.performance.getEntriesByName(\'" + this.webviewURL + "\'))", v2);

这就足够了。如果falcon URL连接到在页面加载完成后执行的JavaScript代码,并且我们可以控制URL,我们就可以注入JavaScript,从而在WebView上加载的任何网站上实现跨站脚本(Universal XSS),我们就进入了WebView!

从WebView到Activity

WebViewClient上的JavaScript注入问题让我们进入了WebView,但我们没有审查WebView实现的其余部分和配置。WebViewClient的shouldOverrideUrlLoading方法的实现让我感到惊讶。

在检查其代码后,我发现如果URL具有intent方案,它会拦截URL,使用Intent.parseUri解析它,然后启动:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
av v1_1 = bl.D();
e.f.b.l.a(v1_1, "LegacyServiceUtils.getCrossPlatformLegacyService()");
try {
    v2 = Intent.parseUri(arg24, 1);
}catch(URISyntaxException unused_ex) {
    v2 = null;
}
Activity v6_1 = r.a(arg25.getContext());
PackageManager v6_2 = v6_1 == null ? null : v6_1.getPackageManager();
if(v6_2 != null && v2 == null ? null : v2.resolveActivity(v6_2) != null) {
    v2.addFlags(0x10000000);
    if(arg24 != null) {
        com.ss.android.ugc.aweme.crossplatform.b.c v10_1 = com.ss.android.ugc.aweme.crossplatform.b.c.h.a();
        com.ss.android.ugc.aweme.aa.a.l v1_2 = this.h;
        if(v1_2 != null) {
            v9 = (com.ss.android.ugc.aweme.aa.a.n)v1_2.a(com.ss.android.ugc.aweme.aa.a.n.class);
        }
        Uri v13 = Uri.parse(arg24);
        e.f.b.l.a(v13, "Uri.parse(it)");
        com.ss.android.ugc.aweme.crossplatform.b.c.a(v10_1, ((t)v9), "webview_safe_log", "filter_scheme", new w(v13, "intent_scheme_", null, 4, null).getFormatData(), null, null, 0x30, null);
    }
    arg25.getContext().startActivity(v2);
    return true;
}

因此,我可以使用这个小工具导航到受保护的组件。

绕过URL验证

aweme://wiki深度链接实施了URL验证,仅允许白名单中的主机名,但它不检查URL方案!我利用javascript方案在WebView上执行javascript,而无需操作URL的主机名段:javascript://m.tiktok.com/%0alaert(1)

我们可以利用:

  • tiktok://webview上的XSS
  • 使用ToutiaoJSBridge接口打开内部深度链接
  • 利用aweme://wiki深度链接上的URL验证绕过
  • 从那里启动受保护的组件

获取远程代码执行

当你在Android上安装包含本地库的APK时,Android通常将这些本地库提取到只读路径/data/app/com.package.name。即使是应用程序本身也无法写入或修改那里的本地库。但有时,应用程序需要更新这些库,而不需要通过Play商店更新整个应用程序,因此应用程序将本地库存储在应用程序的可写保护数据目录/data/data/com.package.name中。

通过观察/data/data/com.zhiliaoapp.musically目录,我发现了一个app_lib目录,其中包含TikTok启动时加载的本地库。

发现关键Activity

在搜索代码时,我在一个名为split_df_miniapp.apk的split APK中发现了一个名为TmaTestActivity的Activity。

该Activity将Intent中的Data Uri传递给以下方法:

1
2
3
4
5
6
7
public final void handleTmaTestAsync(Context arg4, Uri arg5, TmaTestCallback arg6) {
    Uri v5 = Uri.parse(Uri.decode(arg5.toString()));
    String v0 = v5.getQueryParameter("action");
    if(StringUtils.isEqual(v0, "sdkUpdate")) {
        this.updateJssdk(arg4, v5, arg6);
    }
}

它从URL获取新的SDK信息(sdkUpdateVersionsdkVersionlatestSDKUrl),然后调用DownloadBaseBundleHandler实例,将下一个处理程序设置为ResolveDownloadHandler,然后是SetCurrentProcessBundleVersionHandler

Zip Slip漏洞

让我们分析文件下载后的流程,从ResolveDownloadHandler类开始:

1
2
3
4
5
6
7
8
public BundleHandlerParam handle(Context arg13, BundleHandlerParam arg14) {
    if(arg14.isIgnoreTask) {
        return arg14;
    }
    if((arg14.isLastTaskSuccess) && arg14.targetZipFile != null && (arg14.targetZipFile.exists())) {
        arg14.bundleVersion = BaseBundleFileManager.unZipFileToBundle(arg13, arg14.targetZipFile, "download_bundle", false, v0);
    }
}

它提取zip文件,虽然它有路径遍历缓解,但这种缓解被禁用,因为IOUtils.a的布尔参数arg7始终为false,而缓解需要arg7为true:

1
2
3
if((arg7) && !TextUtils.isEmpty(v1) && (v1.contains("../"))) {
    // arg7是最后一个参数,它总是false!路径遍历检查不会发生!
}

当提取这样的ZIP文件时:

1
2
libran_a1ef01b09a3d9400b77144bbf9ad59b1.zip
Path: ../../../../../../../../../data/data/com.zhiliaoapp.musically/app_lib/df_rn_kit/df_rn_kit_a3e37c20900a22bc8836a51678e458f7/arm64-v8a/libjsc.so

它将覆盖/data/data/com.zhiliaoapp.musically/app_lib/df_rn_kit/df_rn_kit_a3e37c20900a22bc8836a51678e458f7/arm64-v8a/libjsc.so文件!

漏洞链总结

让我们欣赏连接这些错误所需的创造力:

  1. 通用XSS(URL片段注入)
  2. 打开内部深度链接(通过ToutiaoJSBridge openSchema)
  3. 绕过URL验证(javascript://方案)
  4. 触发intent方案处理程序(Intent.parseUri小工具)
  5. 启动受保护的TmaTestActivity
  6. 利用Zip Slip(禁用的路径遍历检查)
  7. 覆盖本地库
  8. 应用程序重启时实现RCE

经验教训

  1. 坚持到底

    • tiktok://webview上找到安全URL验证?发现了JavaScript注入。
    • 无法在没有点击的情况下执行intent方案?搜索其他小工具。
    • 没有命令注入漏洞?转向本地库覆盖。
    • 两周后没有找到任何东西?意识到我错过了split APK。
  2. “休息一下"的方法论

    • URL编码阻止了你的XSS?稍后回来 → 记住片段不会被编码。
    • 再次安全URL验证?你发现它错过了验证方案。
    • /data/data中没有本地库?稍后回来 → 意识到应用程序可能仍尝试从那里加载。
    • 基础APK没有小工具?稍后回来 → 记住检查split APK。
  3. 不要只深入挖掘 — 也要向外挖掘

    • 不要只是:“让我跟随这个深度链接,看看它去哪里”
    • 也要尝试:“让我搜索Intent.parseUri,看看什么调用它”
  4. 挑战你的假设

    • “这个应用程序不从/data/data加载本地库” → 但它尝试吗?
    • “我无法在WebView中加载我的URL” → 但当其他URL加载时,我可以注入JS吗?
    • “base.apk没有漏洞” → split APK呢?
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计