feat(android): Introduce setSupportMultipleWindows to mitigate CVE-2020-6506 (#1747 by @mrcoinbase and @kelset -- THANK YOU!)

BREAKING CHANGE:

This release introduces the `setSupportMultipleWindows` prop for Android. This sets the underlying Android WebView setting `setSupportMultipleWindows`. This prop defaults to `true` (previously `false`), and serves to mitigate the security advisory [CVE-2020-6506](https://github.com/react-native-webview/react-native-webview/security/advisories/GHSA-36j3-xxf7-4pqg).

The primary way this new behavior changes existing React Native WebView implementations on Android is that links that open in new tabs/windows (such as `<a target="_blank">`) will now prompt to open in the system browser, rather than re-using the current WebView.

If this behavior is not desirable, you can set this new prop to `false`, but be aware that this exposes your app to the security vulnerability listed above. Make sure you have read and understand the whole advisory and relevant links.

iOS & Windows are unaffected.

```jsx
<WebView
  // ...
  setSupportMultipleWindows={true} // default: true
/>
```

Thanks to @mrcoinbase, @kelset, and @Titozzz for their work on this.
This commit is contained in:
Lorenzo Sciandra 2020-11-24 17:15:19 +00:00 committed by GitHub
parent 1b009dd746
commit 194c6a2335
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 3 deletions

View File

@ -13,6 +13,7 @@ import android.net.http.SslError;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Message;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
@ -187,6 +188,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
settings.setBuiltInZoomControls(true);
settings.setDisplayZoomControls(false);
settings.setDomStorageEnabled(true);
settings.setSupportMultipleWindows(true);
settings.setAllowFileAccess(false);
settings.setAllowContentAccess(false);
@ -252,6 +254,11 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
view.getSettings().setJavaScriptEnabled(enabled);
}
@ReactProp(name = "setSupportMultipleWindows")
public void setSupportMultipleWindows(WebView view, boolean enabled){
view.getSettings().setSupportMultipleWindows(enabled);
}
@ReactProp(name = "showsHorizontalScrollIndicator")
public void setShowsHorizontalScrollIndicator(WebView view, boolean enabled) {
view.setHorizontalScrollBarEnabled(enabled);
@ -875,7 +882,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
// This is desired behavior. We later use these values to determine whether the request is a top-level navigation or a subresource request.
String topWindowUrl = webView.getUrl();
String failingUrl = error.getUrl();
// Cancel request after obtaining top-level URL.
// If request is cancelled before obtaining top-level URL, undesired behavior may occur.
// Undesired behavior: Return value of WebView.getUrl() may be the current URL instead of the failing URL.
@ -1073,6 +1080,17 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
this.mWebView = webView;
}
@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
final WebView newWebView = new WebView(view.getContext());
final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(newWebView);
resultMsg.sendToTarget();
return true;
}
@Override
public boolean onConsoleMessage(ConsoleMessage message) {
if (ReactBuildConfig.DEBUG) {

View File

@ -72,6 +72,7 @@ This document lays out the current public properties and methods for the React N
- [`ignoreSilentHardwareSwitch`](Reference.md#ignoreSilentHardwareSwitch)
- [`onFileDownload`](Reference.md#onFileDownload)
- [`autoManageStatusBarEnabled`](Reference.md#autoManageStatusBarEnabled)
- [`setSupportMultipleWindows`](Reference.md#setSupportMultipleWindows)
## Methods Index
@ -782,7 +783,7 @@ A Boolean value indicating whether JavaScript can open windows without user inte
### `androidHardwareAccelerationDisabled`[](#props-index)<!-- Link generated with jump2header -->
**Deprecated.** Use the `androidLayerType` prop instead.
**Deprecated.** Use the `androidLayerType` prop instead.
| Type | Required | Platform |
| ---- | -------- | -------- |
@ -792,7 +793,7 @@ A Boolean value indicating whether JavaScript can open windows without user inte
### `androidLayerType`[](#props-index)<!-- Link generated with jump2header -->
Specifies the layer type.
Specifies the layer type.
Possible values for `androidLayerType` are:
@ -1282,6 +1283,21 @@ Example:
<WebView autoManageStatusBarEnabled={false} />
```
### `setSupportMultipleWindows`
Sets whether the WebView supports multiple windows. See [Android documentation]('https://developer.android.com/reference/android/webkit/WebSettings#setSupportMultipleWindows(boolean)') for more information.
Setting this to false can expose the application to this [vulnerability](https://alesandroortiz.com/articles/uxss-android-webview-cve-2020-6506/) allowing a malicious iframe to escape into the top layer DOM.
| Type | Required | Default | Platform |
| ------- | -------- | ------- | -------- |
| boolean | No | true | Android |
Example:
```javascript
<WebView setSupportMultipleWindows={false} />
```
## Methods
### `extraNativeComponentConfig()`[](#methods-index)<!-- Link generated with jump2header -->

View File

@ -18,6 +18,7 @@ import Uploads from './examples/Uploads';
import Injection from './examples/Injection';
import LocalPageLoad from './examples/LocalPageLoad';
import Messaging from './examples/Messaging';
import NativeWebpage from './examples/NativeWebpage';
const TESTS = {
Messaging: {
@ -84,6 +85,14 @@ const TESTS = {
return <LocalPageLoad />;
},
},
NativeWebpage: {
title: 'NativeWebpage',
testId: 'NativeWebpage',
description: 'Test to open a new webview with a link',
render() {
return <NativeWebpage />;
},
},
};
type Props = {};
@ -166,6 +175,11 @@ export default class App extends Component<Props, State> {
title="Messaging"
onPress={() => this._changeTest('Messaging')}
/>
<Button
testID="testType_nativeWebpage"
title="NativeWebpage"
onPress={() => this._changeTest('NativeWebpage')}
/>
</View>
{restarting ? null : (

View File

@ -0,0 +1,23 @@
import React, {Component} from 'react';
import {View} from 'react-native';
import WebView from 'react-native-webview';
type Props = {};
type State = {};
export default class NativeWebpage extends Component<Props, State> {
state = {};
render() {
return (
<View style={{height: 400}}>
<WebView
source={{uri: 'https://infinite.red'}}
style={{width: '100%', height: '100%'}}
// setSupportMultipleWindows={false}
/>
</View>
);
}
}

View File

@ -63,6 +63,7 @@ class WebView extends React.Component<AndroidWebViewProps, State> {
androidHardwareAccelerationDisabled: false,
androidLayerType: 'none',
originWhitelist: defaultOriginWhitelist,
setSupportMultipleWindows: true,
};
static isFileUploadSupported = async () => {

View File

@ -295,6 +295,7 @@ export interface AndroidNativeWebViewProps extends CommonNativeWebViewProps {
onRenderProcessGone?: (event: WebViewRenderProcessGoneEvent) => void;
overScrollMode?: OverScrollModeType;
saveFormDataDisabled?: boolean;
setSupportMultipleWindows?: boolean;
textZoom?: number;
thirdPartyCookiesEnabled?: boolean;
messagingModuleName?: string;
@ -799,6 +800,13 @@ export interface AndroidWebViewProps extends WebViewSharedProps {
*/
saveFormDataDisabled?: boolean;
/**
* Boolean value to set whether the WebView supports multiple windows. Used on Android only
* The default value is `true`.
* @platform android
*/
setSupportMultipleWindows?: boolean;
/**
* Used on Android only, controls whether the given list of URL prefixes should
* make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a