Skip to content

Commit 194c6a2

Browse files
authored
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](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.
1 parent 1b009dd commit 194c6a2

File tree

6 files changed

+83
-3
lines changed

6 files changed

+83
-3
lines changed

android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import android.net.Uri;
1414
import android.os.Build;
1515
import android.os.Environment;
16+
import android.os.Message;
1617
import android.os.SystemClock;
1718
import android.text.TextUtils;
1819
import android.util.Log;
@@ -187,6 +188,7 @@ protected WebView createViewInstance(ThemedReactContext reactContext) {
187188
settings.setBuiltInZoomControls(true);
188189
settings.setDisplayZoomControls(false);
189190
settings.setDomStorageEnabled(true);
191+
settings.setSupportMultipleWindows(true);
190192

191193
settings.setAllowFileAccess(false);
192194
settings.setAllowContentAccess(false);
@@ -252,6 +254,11 @@ public void setJavaScriptEnabled(WebView view, boolean enabled) {
252254
view.getSettings().setJavaScriptEnabled(enabled);
253255
}
254256

257+
@ReactProp(name = "setSupportMultipleWindows")
258+
public void setSupportMultipleWindows(WebView view, boolean enabled){
259+
view.getSettings().setSupportMultipleWindows(enabled);
260+
}
261+
255262
@ReactProp(name = "showsHorizontalScrollIndicator")
256263
public void setShowsHorizontalScrollIndicator(WebView view, boolean enabled) {
257264
view.setHorizontalScrollBarEnabled(enabled);
@@ -875,7 +882,7 @@ public void onReceivedSslError(final WebView webView, final SslErrorHandler hand
875882
// This is desired behavior. We later use these values to determine whether the request is a top-level navigation or a subresource request.
876883
String topWindowUrl = webView.getUrl();
877884
String failingUrl = error.getUrl();
878-
885+
879886
// Cancel request after obtaining top-level URL.
880887
// If request is cancelled before obtaining top-level URL, undesired behavior may occur.
881888
// Undesired behavior: Return value of WebView.getUrl() may be the current URL instead of the failing URL.
@@ -1073,6 +1080,17 @@ public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
10731080
this.mWebView = webView;
10741081
}
10751082

1083+
@Override
1084+
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
1085+
1086+
final WebView newWebView = new WebView(view.getContext());
1087+
final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
1088+
transport.setWebView(newWebView);
1089+
resultMsg.sendToTarget();
1090+
1091+
return true;
1092+
}
1093+
10761094
@Override
10771095
public boolean onConsoleMessage(ConsoleMessage message) {
10781096
if (ReactBuildConfig.DEBUG) {

docs/Reference.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ This document lays out the current public properties and methods for the React N
7272
- [`ignoreSilentHardwareSwitch`](Reference.md#ignoreSilentHardwareSwitch)
7373
- [`onFileDownload`](Reference.md#onFileDownload)
7474
- [`autoManageStatusBarEnabled`](Reference.md#autoManageStatusBarEnabled)
75+
- [`setSupportMultipleWindows`](Reference.md#setSupportMultipleWindows)
7576

7677
## Methods Index
7778

@@ -782,7 +783,7 @@ A Boolean value indicating whether JavaScript can open windows without user inte
782783

783784
### `androidHardwareAccelerationDisabled`[](#props-index)<!-- Link generated with jump2header -->
784785

785-
**Deprecated.** Use the `androidLayerType` prop instead.
786+
**Deprecated.** Use the `androidLayerType` prop instead.
786787

787788
| Type | Required | Platform |
788789
| ---- | -------- | -------- |
@@ -792,7 +793,7 @@ A Boolean value indicating whether JavaScript can open windows without user inte
792793

793794
### `androidLayerType`[](#props-index)<!-- Link generated with jump2header -->
794795

795-
Specifies the layer type.
796+
Specifies the layer type.
796797

797798
Possible values for `androidLayerType` are:
798799

@@ -1282,6 +1283,21 @@ Example:
12821283
<WebView autoManageStatusBarEnabled={false} />
12831284
```
12841285

1286+
### `setSupportMultipleWindows`
1287+
1288+
Sets whether the WebView supports multiple windows. See [Android documentation]('https://developer.android.com/reference/android/webkit/WebSettings#setSupportMultipleWindows(boolean)') for more information.
1289+
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.
1290+
1291+
| Type | Required | Default | Platform |
1292+
| ------- | -------- | ------- | -------- |
1293+
| boolean | No | true | Android |
1294+
1295+
Example:
1296+
1297+
```javascript
1298+
<WebView setSupportMultipleWindows={false} />
1299+
```
1300+
12851301
## Methods
12861302

12871303
### `extraNativeComponentConfig()`[](#methods-index)<!-- Link generated with jump2header -->

example/App.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Uploads from './examples/Uploads';
1818
import Injection from './examples/Injection';
1919
import LocalPageLoad from './examples/LocalPageLoad';
2020
import Messaging from './examples/Messaging';
21+
import NativeWebpage from './examples/NativeWebpage';
2122

2223
const TESTS = {
2324
Messaging: {
@@ -84,6 +85,14 @@ const TESTS = {
8485
return <LocalPageLoad />;
8586
},
8687
},
88+
NativeWebpage: {
89+
title: 'NativeWebpage',
90+
testId: 'NativeWebpage',
91+
description: 'Test to open a new webview with a link',
92+
render() {
93+
return <NativeWebpage />;
94+
},
95+
},
8796
};
8897

8998
type Props = {};
@@ -166,6 +175,11 @@ export default class App extends Component<Props, State> {
166175
title="Messaging"
167176
onPress={() => this._changeTest('Messaging')}
168177
/>
178+
<Button
179+
testID="testType_nativeWebpage"
180+
title="NativeWebpage"
181+
onPress={() => this._changeTest('NativeWebpage')}
182+
/>
169183
</View>
170184

171185
{restarting ? null : (

example/examples/NativeWebpage.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React, {Component} from 'react';
2+
import {View} from 'react-native';
3+
4+
import WebView from 'react-native-webview';
5+
6+
type Props = {};
7+
type State = {};
8+
9+
export default class NativeWebpage extends Component<Props, State> {
10+
state = {};
11+
12+
render() {
13+
return (
14+
<View style={{height: 400}}>
15+
<WebView
16+
source={{uri: 'https://infinite.red'}}
17+
style={{width: '100%', height: '100%'}}
18+
// setSupportMultipleWindows={false}
19+
/>
20+
</View>
21+
);
22+
}
23+
}

src/WebView.android.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class WebView extends React.Component<AndroidWebViewProps, State> {
6363
androidHardwareAccelerationDisabled: false,
6464
androidLayerType: 'none',
6565
originWhitelist: defaultOriginWhitelist,
66+
setSupportMultipleWindows: true,
6667
};
6768

6869
static isFileUploadSupported = async () => {

src/WebViewTypes.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ export interface AndroidNativeWebViewProps extends CommonNativeWebViewProps {
295295
onRenderProcessGone?: (event: WebViewRenderProcessGoneEvent) => void;
296296
overScrollMode?: OverScrollModeType;
297297
saveFormDataDisabled?: boolean;
298+
setSupportMultipleWindows?: boolean;
298299
textZoom?: number;
299300
thirdPartyCookiesEnabled?: boolean;
300301
messagingModuleName?: string;
@@ -799,6 +800,13 @@ export interface AndroidWebViewProps extends WebViewSharedProps {
799800
*/
800801
saveFormDataDisabled?: boolean;
801802

803+
/**
804+
* Boolean value to set whether the WebView supports multiple windows. Used on Android only
805+
* The default value is `true`.
806+
* @platform android
807+
*/
808+
setSupportMultipleWindows?: boolean;
809+
802810
/**
803811
* Used on Android only, controls whether the given list of URL prefixes should
804812
* make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a

0 commit comments

Comments
 (0)