Androidアプリのアクセス制御不備の脆弱性 In Kotlin
2021/6/25にLACの社内ブログより以下の記事が公開された。
この記事ではAndroidアプリの「アクセス制御不備」の中の「ディープリンクを使用してリクエストされた、任意のURLにアクセスしてしまう脆弱性」についてJavaで解説されています。
なので私はこの記事を参考にKotlin版を解説していこうと思います。
目次
ディープリンクとは
モバイルアプリにおけるディープリンクとは主に、あるウェブページから他のウェブサイトのトップページ以外の各コンテンツに直接ハイパーリンクを張ったり、他のアプリから特定のリンクを使うことで、アプリの特定コンテンツを呼び出すことが出来るリンクのことです。
筆者の環境
PC:Windows10 エミュレータ API:27 IDE:AndroidStudio4.2.1 使用言語:Kotlin version 1.5.2 Tool:adb(Android Debug Bridge)
脆弱性の実践
ここからは実際にコードを書いて脆弱なディープリンクの実装と実践を行います。
(※注意:これから実装するコードを用いたアプリを実機にインストールするのは危険ですので、必ずエミュレータ等の環境でお試しください。実機にインストールすることで何らかの不利益が生じたとしても筆者は一切の責任を負いかねます。)
今回実装する脆弱なコードの全文はこちらにあります。
Androidのディープリンクの実装
まず今回のサンプルアプリを起動すると、以下のような画像のアクティビティが起動します。
そしてアクティビティをもう一つ作りますが、そのアクティビティはディープリンクでのみ起動すると仮定して、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です)
そしてリンクはこちら!!
をクリックするとアプリが起動して以下のアクティビティが表示されるはずです。
adbを利用したアクティビティ起動
次に、adbを使用してアクティビティを起動してみます。
adb shell am start -a android.intent.action.VIEW -d daiki0508://webview/DeepLink
成功すると、以下の様なアクティビティが起動すると思います。
アクセス制御不備の脆弱性の実装
今回のテーマである「ディープリンクを使用してリクエストされた、任意の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です)
ブラウザからの不正なアクティビティ呼び出し
続いて、以下の様な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です)
つまり、第三者の任意のURLをWebViewで表示出来たということです。
adbを使用した正規のアクティビティ呼び出し
adbでのテストは簡単です。
adb shell am start -a android.intent.action.VIEW -d daiki0508://webview/index?data=https://hatenablog.com/
成功すると、開発者の想定する「HatenaBlog」の公式サイトがWebViewで表示されたと思います。
adbを使用した不正のアクティビティ呼び出し
adb shell am start -a android.intent.action.VIEW -d daiki0508://webview/fake?data=https://github.com/daiki0508
開発者が想定してないサイトである筆者のGithubページがWebViewで表示されたと思います。
対策版の実装
それでは今度は先ほどの脆弱性を修正した処理を実装していきます。
(※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() } } } }
テスト
先ほどの脆弱性版でテストしたコードで試してみると
無事、弾かれましたね!!
まとめ
最近はずっと開発の勉強をしていたので、今回は久しぶりにAndroidSecurityに触れる良い機会になりました。
また、LACの社内ブログがとても分かりやすく書かれてあったのでとても簡単に実装出来ましたし、現実にあるアプリにもこういった脆弱性は結構ありそうだと感じました。