daiki0508'足跡

Androidアプリのアクセス制御不備の脆弱性 In Kotlin

2021/6/25にLACの社内ブログより以下の記事が公開された。

www.lac.co.jp

この記事ではAndroidアプリの「アクセス制御不備」の中のディープリンクを使用してリクエストされた、任意のURLにアクセスしてしまう脆弱性についてJavaで解説されています。

なので私はこの記事を参考にKotlin版を解説していこうと思います。

目次

ディープリンクとは

モバイルアプリにおけるディープリンクとは主に、あるウェブページから他のウェブサイトのトップページ以外の各コンテンツに直接ハイパーリンクを張ったり、他のアプリから特定のリンクを使うことで、アプリの特定コンテンツを呼び出すことが出来るリンクのことです。

筆者の環境

PC:Windows10
エミュレータ API:27
IDE:AndroidStudio4.2.1
使用言語:Kotlin version 1.5.2
Tool:adb(Android Debug Bridge)

脆弱性の実践

ここからは実際にコードを書いて脆弱なディープリンクの実装と実践を行います。
(※注意:これから実装するコードを用いたアプリを実機にインストールするのは危険ですので、必ずエミュレータ等の環境でお試しください。実機にインストールすることで何らかの不利益が生じたとしても筆者は一切の責任を負いかねます。)

今回実装する脆弱なコードの全文はこちらにあります。

Androidディープリンクの実装

まず今回のサンプルアプリを起動すると、以下のような画像のアクティビティが起動します。

f:id:daiki0508:20210625174832p:plain

そしてアクティビティをもう一つ作りますが、そのアクティビティはディープリンクでのみ起動すると仮定して、MainActivityからの遷移は実装しません。

そしてAndroidManifest.xmlに下記のコードを記述することでディープリンクを実装できます。

<activity android:name=".WebViewActivity" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data
                    android:host="webview"
                    android:scheme="daiki0508" />
            </intent-filter>
</activity>

webサイトからのアクティビティ起動

次に、アプリの起動手段のテストとして簡単にブラウザからアプリを起動させてみましょう。
(※adbの環境がある人はここは流し読みでも大丈夫です。)

ちなみに、HTMLコードは以下のような感じでOKです。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>VulnDeepLink</title>
    </head>
    <body>
        <h1>Hello World!!</h1>
        <p><a href="lacapp://webview/DeepLink">リンクはこちら!!</a></p>
    </body>
</html>

後はこのコードをxampの様なツールでapacheを起動してlocalhostでアクセスしましょう。
(※ちなみにAndroidエミュレートの場合のlocalhostは10.0.2.2です)

f:id:daiki0508:20210625182502p:plain

そしてリンクはこちら!!をクリックするとアプリが起動して以下のアクティビティが表示されるはずです。

f:id:daiki0508:20210625175831p:plain

adbを利用したアクティビティ起動

次に、adbを使用してアクティビティを起動してみます。

adb shell am start -a android.intent.action.VIEW -d daiki0508://webview/DeepLink

成功すると、以下の様なアクティビティが起動すると思います。

f:id:daiki0508:20210625175831p:plain

アクセス制御不備の脆弱性の実装

今回のテーマであるディープリンクを使用してリクエストされた、任意のURLにアクセスしてしまう脆弱性は主に、WebViewと同時に実装されている場合に起こる可能性が高いです。

そして以下が脆弱性のある実装です。

class WebViewActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_web_view)

        val webView = findViewById<WebView>(R.id.webView)
        val uri = intent.data
        val query = uri?.getQueryParameter("data")
        webView.loadUrl(query!!)
    }
}

この処理でやってることは簡単です。

1. WebViewActivity起動時にIntentを取得
2. その際にURL情報から「data」というクエリパラメータを取得
3. 2で受け取ったクエリパラメータをWebViewで表示

ブラウザからの正規なアクティビティ呼び出し

まずは、開発者が想定しているサイトをWebViewで表示させられるかをブラウザからアプリを起動してテストしましょう。
ちなみに、開発者が想定する表示サイトは「HatenaBlog」の公式サイトとしましょう。
(※adbの環境がある人はここは軽く読むだけで大丈夫です)

HTMLコードは以下のような感じでOKです。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>VulnDeepLink</title>
    </head>
    <body>
        <h1>Hello World!!</h1>
        <p><a href="daiki0508://webview/index?data=https://developer.android.com/?hl=ja">リンクはこちら!!</a></p>
    </body>
</html>

実際にこのコードをxampの様なツールでapacheを起動してlocalhostでアクセスした後にリンクはこちら!!をタップしてください。
(※ちなみにAndroidエミュレートの場合のlocalhostは10.0.2.2です)

f:id:daiki0508:20210625193903p:plain

ブラウザからの不正なアクティビティ呼び出し

続いて、以下の様なHTMLコードを記述してください。
(※adbの環境がある人はここは軽く読むだけで大丈夫です)

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>VulnDeepLink</title>
    </head>
    <body>
        <h1>Hello World!!</h1>
        <p><a href="daiki0508://webview/fake?data=https://github.com/daiki0508">リンクはこちら!!</a></p>
    </body>
</html>

このコードもxampの様なツールでapacheを起動してlocalhostでアクセスした後にリンクはこちら!!をタップしてみると、筆者のGithubが表示されたと思います。
(※ちなみにAndroidエミュレートの場合のlocalhostは10.0.2.2です)

f:id:daiki0508:20210625235202p:plain

つまり、三者の任意のURLをWebViewで表示出来たということです。

adbを使用した正規のアクティビティ呼び出し

adbでのテストは簡単です。

adb shell am start -a android.intent.action.VIEW -d daiki0508://webview/index?data=https://hatenablog.com/

成功すると、開発者の想定する「HatenaBlog」の公式サイトがWebViewで表示されたと思います。

f:id:daiki0508:20210625235202p:plain

adbを使用した不正のアクティビティ呼び出し

adb shell am start -a android.intent.action.VIEW -d daiki0508://webview/fake?data=https://github.com/daiki0508

開発者が想定してないサイトである筆者のGithubページがWebViewで表示されたと思います。

f:id:daiki0508:20210625235202p:plain

対策版の実装

それでは今度は先ほどの脆弱性を修正した処理を実装していきます。
(※MainActivityのレイアウトファイルや処理ファイルは脆弱性版と特に変わりません)
対策として最も簡単に実装できるのは「表示されるページを開発者が想定したものに限定する」ことです。

対策版の実装コードの全文はこちらにあります。

ホワイトリストの記述

まずstrings.xmlに許可するホストのリストを記載しておきます。

<resources>
    <string name="app_name">ResiDeepLink_Kotlin</string>
    <string-array name="allow_url_list">
        <item>hatenablog.com</item>
    </string-array>
</resources>

整合性チェックの実装

次に、取得したdataパラメータのスキーマとホストが開発者が指定したものと一致するかどうかを判断する処理を記述します。

class WebViewActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_web_view)

        val allowList = resources.getStringArray(R.array.allow_url_list)

        val webView = findViewById<WebView>(R.id.webView)
        val uri = intent.data
        val query = Uri.parse(uri?.getQueryParameter("data"))

        for (url: String in allowList){
            if (query.scheme.equals("https") && query.host.equals(url)){
                webView.loadUrl(query.toString())
            }else{
                Toast.makeText(this, "指定されたURLはリストにありません。", Toast.LENGTH_LONG).show()
            }
        }
    }
}

テスト

先ほどの脆弱性版でテストしたコードで試してみると

f:id:daiki0508:20210625230941p:plain

無事、弾かれましたね!!

まとめ

最近はずっと開発の勉強をしていたので、今回は久しぶりにAndroidSecurityに触れる良い機会になりました。
また、LACの社内ブログがとても分かりやすく書かれてあったのでとても簡単に実装出来ましたし、現実にあるアプリにもこういった脆弱性は結構ありそうだと感じました。

参考・引用

https://www.lac.co.jp/lacwatch/people/20210625_002645.html