Skip to content

Commit 29ded70

Browse files
authored
Merge pull request #60 from MetaMask/feature/detect_iframes_android
feat: Inject Javascript to detect all iFrames inside the current page and report them to the React app. We currently have an anti-fishing warning which is displayed when user opens a malicious website. But there is a way to bypass this warning if you load a malicious website via iFrame. Example of the malicious website: https://coin-qr.to/ Example of the website with a malicious iFrame: https://lol-au4.pages.dev/cb (opens coin-qr.to inside in the iFrame) We can inject a JS script in the mobile browser that will check webpage's iFrames and report their URLs to the browser in MetaMask mobile app.
2 parents 3be76c0 + 79da8f1 commit 29ded70

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
4848
import com.reactnativecommunity.webview.events.TopMessageEvent;
4949
import com.reactnativecommunity.webview.extension.file.BlobFileDownloader;
50+
import com.reactnativecommunity.webview.extension.file.IFrameDetectorKt;
5051

5152
import org.json.JSONException;
5253
import org.json.JSONObject;
@@ -343,6 +344,10 @@ public void injectBlobFileDownloaderScript() {
343344
evaluateJavascriptWithFallback(BlobFileDownloader.Companion.getBlobFileInterceptor());
344345
}
345346

347+
public void injectIFrameDetectorScript() {
348+
evaluateJavascriptWithFallback(IFrameDetectorKt.getIFrameDetectorScript());
349+
}
350+
346351
public void callInjectedJavaScriptBeforeContentLoaded() {
347352
if (getSettings().getJavaScriptEnabled() &&
348353
injectedJSBeforeContentLoaded != null &&

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ public void onPageFinished(WebView webView, String url) {
7676

7777
reactWebView.injectBlobFileDownloaderScript();
7878

79+
reactWebView.injectIFrameDetectorScript();
80+
7981
emitFinishEvent(webView, url);
8082
}
8183
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.reactnativecommunity.webview.extension.file
2+
3+
/**
4+
* JavaScript code to detect all iFrames in the page and report their URLs
5+
* This script runs after page load and also monitors for dynamically added iFrames
6+
*/
7+
fun getIFrameDetectorScript(): String = """
8+
(function() {
9+
if (window.iframeDetectorInjected) return;
10+
window.iframeDetectorInjected = true;
11+
12+
function collectIFrameUrls() {
13+
const iframes = document.getElementsByTagName('iframe');
14+
const urls = [];
15+
16+
for (let i = 0; i < iframes.length; i++) {
17+
const iframe = iframes[i];
18+
const src = iframe.src;
19+
20+
if (src && src.trim() !== '' && (src.startsWith('http://') || src.startsWith('https://') || src.startsWith('//'))) {
21+
const normalizedUrl = src.startsWith('//') ? 'https:' + src : src;
22+
urls.push(normalizedUrl);
23+
}
24+
}
25+
26+
return urls;
27+
}
28+
29+
function reportIFrames() {
30+
try {
31+
const urls = collectIFrameUrls();
32+
if (urls.length > 0) {
33+
const message = {
34+
type: 'IFRAME_DETECTED',
35+
iframeUrls: urls
36+
};
37+
38+
if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {
39+
window.ReactNativeWebView.postMessage(JSON.stringify(message));
40+
}
41+
}
42+
} catch (e) {
43+
console.error('Error reporting iFrames:', e);
44+
}
45+
}
46+
47+
// Initial check for iFrames
48+
if (document.readyState === 'loading') {
49+
document.addEventListener('DOMContentLoaded', reportIFrames);
50+
} else {
51+
// Document already loaded
52+
setTimeout(reportIFrames, 100);
53+
}
54+
55+
// Monitor for dynamically added iFrames
56+
const observer = new MutationObserver(function(mutations) {
57+
let shouldCheck = false;
58+
mutations.forEach(function(mutation) {
59+
if (mutation.type === 'childList') {
60+
mutation.addedNodes.forEach(function(node) {
61+
if (node.nodeType === Node.ELEMENT_NODE) {
62+
if (node.tagName === 'IFRAME' || node.querySelector('iframe')) {
63+
shouldCheck = true;
64+
}
65+
}
66+
});
67+
}
68+
});
69+
70+
if (shouldCheck) {
71+
setTimeout(reportIFrames, 100);
72+
}
73+
});
74+
75+
observer.observe(document.body || document.documentElement, {
76+
childList: true,
77+
subtree: true
78+
});
79+
80+
// Also check periodically as a fallback
81+
setInterval(reportIFrames, 5000);
82+
})();
83+
""".trimIndent()

0 commit comments

Comments
 (0)