WebView - Android's most convenient footgun
Retro iframe security issues making a comeback
Imagine you’re a web developer who’s got a little bit of Android experience. You’ve built a progressive web app—it’s beautiful, functional, responsive. Everything the user sees was meticulously placed and crafted with purpose. You’re proud of what you’ve done. Why wouldn’t you be? Now. How do you take this web-centric experience and apply an Android veneer? Like most devs, you’ll probably find yourself reaching for a WebView. It presents a web page as the app’s interface, along with a basket of other features. Incredibly convenient!
Lets say your site has an interface that’s constantly being updated. Would you want to deploy a new build for each change? Of course not. Pull that shit in dynamically with a WebView. This way, your users get the benefit of a native platform, with the update speed of a live website.
WebViews offer complete customization over the UI. To get the same level of control, the alternative would be to either use the built-in components that Android provides, combine them, or create your own from scratch. Basically, with WebViews:
- No need to redeploy for every change.
- Quick testing with common tools that web developers are already used to.
- Native app can utilize web app functionality which may not have proper API’s (yet).
- Customizability with lower effort.
As you might expect, this doesn’t come for free. When implemented incorrectly, there could be extensive security implications. WebView ushers in convenience, but it also significantly increases attack surface. Security researchers have wasted no time exploring attack vectors believed to be mitigated on browsers, but are still viable within WebViews.
In this article, we explore a paper by GuangLiang Yang, Jeff Huang, and Guofei Gu presented at USENIX 2019. They investigate the previously well-understood topic of
<iframe> security, but within the context of a WebView. The paper focuses on what they’re naming Differential Context Vulnerabilities (DCV). A web page running within a WebView, as opposed to running in a browser, can introduce vulnerabilities given the differences of the context it may run in. They discovered that although most vulnerabilities have had safeguards introduced in traditional browsers, those same mitigations are either ineffective or impossible to apply within the context of WebViews.
We’ll take a look at some of the attacks they propose.
WebViews are an interesting target for two primary reasons: their compact design, and their wealth of settings ripe for misconfiguration.
All attacks take advantage of the fact that unlike traditional browsers, WebViews do not provide enough context for the end user whenever navigation occurs. Browsers have address, navigation and status bars, in addition to tab controls. It’s easy for a user to tell what page they’re on and when they’re navigating away from it. This is not the case for WebViews, which simply renders the topmost page within its rendering queue.
The authors do not claim that all WebViews are insecure. Rather, they focus on an insecure combination of settings that an app developer may utilize:
|Popups are rendered in new WebViews|
|Adds a new view to the top of a ViewGroup|
|Allows custom implementation of how popup creation is handled|
Not all settings need to be set in order to deem an application vulnerable. Each setting is potentially dangerous on its own, but even more so when combined with others. We’ll explore how attacks make use of vulnerabilities brought about by misconfiguration.
For the following examples, we’ll assume this page structure is loaded into a WebView:
- Parent page is loaded into a WebView via the
WebView.load*()family of methods.
- This parent page embeds an
<iframe>from a trusted source
<iframe>from an untrusted source
https://ads.commay (un)intentionally serve up a malicious
<iframe>, depending on the ad content.
This simple attack capitalizes on WebView’s lack of navigation elements. On click, a link redirects the top frame to a malicious page. An untrusted
<iframe> could embed a link that navigates the parent frame to a malicious page:
Once clicked, the WebView is redirected with no clear indication. The user is none the wiser.
Privileged Navigation Attack
In addition to the navigation attack, which applies to all browsers, Android is vulnerable to a variant that can bypass sandbox
_top navigation restrictions if the
<iframe> is allowed to create popups (no sandbox, or sandbox sets
If a frame opens a popup, Android will always try to select a WebView to render the popup. If
SupportMultipleWindows is set to
false, then no window is created for the popup. Therefore, Android selects the current WebView, essentially navigating the parent frame anyway.
true, then the frame can also create a popup with
Like the privileged navigation attack, an overlay attack takes advantage of Android’s ViewGroup rendering mechanics and WebView misconfiguration. A WebView is susceptible if:
SupportMultipleWindowsis set to
WebChromeClientis attached to the WebView.
onCreateWindow()is overwritten, utilizing
ViewGroup.addView()without an index (or with
index = -1).
by default, whenever a popup is created and
true, Android will create a new WebView and add it to the same parent ViewGroup as the current WebView. It’ll become the last child of the parent. When Android renders views within a ViewGroup, it starts by drawing the first child. It then iterates through the ViewGroup’s children, rendering each view on top of the last. Because the popup WebView is the last child of the ViewGroup, it’ll be the topmost rendered view. It’s the only thing the user can see. To gain full control of the UI, a misbehaving
<iframe> only needs to create a popup!
What if the app uses
ViewGroup.addView(view, 0) as part of it’s
onCreateWindow handler? This is in fact a mitigation for overlay attacks, but the app could still be in danger. If
WebChromeClient.onCloseWindow() is overwritten and
true, then the app is susceptible to closure attacks. They work like this:
- Frame creates a popup. Android creates and appends a new WebView containing the popup to the ViewGroup.
- Popup calls
window.close()on the original parent frame.
onCloseWindow()has a naive implementation—it doesn’t check whether the parent WebView is being closed, and closes it anyway.
- Parent WebView is removed from ViewGroup. Popup WebView is all that remains.
- The user only sees the popup. The UI has been hijacked.
_top frame. That would require both frames to share the same origin.
Origins are equivalent whenever their protocol, host, and port match. Two
null origins are also equivalent—but when is an origin
null? According to the HTML specification, browsers are required to treat data URL origins as opaque values. This is handy in instances where media needs to be loaded without violating the SOP. In summary, if the parent frame has a
null origin, then any child frame with a
null origin is part of the same origin, and is no longer in violation of the SOP.
For general browsing, it’s rare to find a legitimate use case where a parent page needs be loaded in via a data URL. However, in an Android application it’s acceptable to initialize a WebView with an inline HTML snippet instead of retrieving it from a remote URL.
Consider this example:
webView is initialized with an HTML snippet describing a simple widget which pulls in an
<iframe> from a trusted site. Because
webView was initialized with inline data, the parent frame’s origin is
null. Therefore, any child frame sourced from a data url that
trusted.com or its children produce will have the same origin as the parent frame. Any
null origin child frame is essentially granted full control.
truein the parent WebView.
<iframe> has full control over it’s parent, the simplest thing to do with this power is to navigate the parent frame to a malicious page. Since WebView does not make it apparent when navigation occurs, phishing pages become much more convincing.
Let’s assume that
<iframe src="https://ads.com>. Unbeknownst to
ads.com is hosting a malicious ad. The malicious ad wants to navigate the parent to
evil.com. The malicious ad is running inside the
ads.com frame, so it’s origin is
https://ads.com. According to the SOP, it can’t navigate the parent. What it can do, however, is create another child frame from a data URL. This child frame shares the same origin with the parent frame, so it can do whatever it likes!
First, let’s look at the source of this malicious child frame:
It is then converted to a base64 data URL:
Then the malicious ad embeds it as an
As soon as this ad loads, the parent frame is navigated to
evil.com automatically. Here’s what it looks like slightly slowed down. In this example the malicious ad hijacks the parent frame and loads a Facebook phisher:
Consider the case where the parent frame needs to communicate with a trusted child frame via the
postMessage API. The parent issues a message like the following:
trusted.com frame listens for messages. It knows the parent frame has a
null origin, so it checks for this whenever it receives a message and only acts if the message comes from an expected origin:
With this scheme,
ads.com may not
trusted.com because any message sent will have an unacceptable
https://ads.com origin. However, as described earlier, a malicious ad can embed a child frame with a
null origin. Similar to frame hijacking, the child frame effectively becomes the parent frame. If the parent can communicate with
trusted.com, then so can the child. This could be bad news if the trusted frame implements sensitive functionality.
Native Bridge Hijacking
In the example diagram, let’s assume that the app exposes some APIs to the web application:
createNotification()- Creates a native notification for the application.
getHistory()- Gets complete browsing history.
getCredential()- Gets a credential from the device’s credential store.
Each method does origin checking, but clearly this is insufficient. Here the parent frame creates a notification. The native bridge allows it because it expects a
null origin. Next, “for better ad personalization”, the
ads.com frame tries to get complete browser history. The bridge rejects it, citing an unacceptable origin. Unfortunately, because It’s malicious child shares the
null origin, the native bridge allows it to retrieve credentials!
There’s no doubting WebView’s utility. It facilitates rapid development, encourages cross-platform consistency, and could be used as a powerful stopgap on its own. But as we have seen, if implemented incorrectly, it could greatly increase an application’s vulnerable surface area. Google realizes this and provides more secure alternatives.
Chrome Custom Tabs
If you simply want to render a view from the web, and this view may redirect users outside your domain, opt for Chrome Custom Tabs. It’s a robust solution which addresses some of the design flaws explored above. Some features to highlight :
- A toolbar! Users know when navigation occurs. The application can even register a navigation callback before external navigation occurs.
- Shared sessions and cookie jar with the user’s default browser.
- Pre-warming in the background for pages that a user is likely to visit
Trusted Web Activities
If full screen web content is a must, and you don’t need to mix native and web components, then Trusted Web Activities (TWA) are the recommended approach. They verify that the page being loaded is in fact what the application expects. If a loaded page fails verification, the app falls back to showing Chrome Custom Tabs, making it clear that an external navigation occurred. If the application requires multiple origins, TWA has you covered.
If you need to serve up content from the web for your application, considering using trusted alternatives. If you absolutely must use a WebView, be wary of how you configure settings. Make sure all untrusted content is sandboxed.
This article summarizes findings from the excellent DCV paper presented at USENIX 2019. Not discussed is the automated vulnerability scanning tool they built, which they used to survey 17,000 of the most popular apps on the Google Play store. Of these, 4358 were found to be potentially vulnerable including Facebook, Instagram, Skype, Kayak, and other leading applications. I highly recommend you read it for yourself!
Of course, If you’ve got feedback for me, I’m only a tweet away 😊