Chrome extension: send postMessage from iframe to parent window [duplicate] - javascript

I'm doing a plugin to do some transformations to the interface. I keep getting unsafe javascript attempt to access frame with url.... Domains, protocols and ports must match (typical cross site issue)
But being an extension it should have access to the iframe's content http://code.google.com/chrome/extensions/content_scripts.html ...
Doesn anyone know how to access it's contents so they can be capturable?

There's generally no direct way of accessing a different-origin window object. If you want to securely communicate between content scripts in different frames, you have to send a message to the background page which in turn sends the message back to the tab.
Here is an example:
Part of manifest.json:
"background": {"scripts":["bg.js"]},
"content_scripts": [
{"js": ["main.js"], "matches": ["<all_urls>"]},
{"js": ["sub.js"], "matches": ["<all_urls>"], "all_frames":true}
]
main.js:
var isTop = true;
chrome.runtime.onMessage.addListener(function(details) {
alert('Message from frame: ' + details.data);
});
sub.js:
if (!window.isTop) { // true or undefined
// do something...
var data = 'test';
// Send message to top frame, for example:
chrome.runtime.sendMessage({sendBack:true, data:data});
}
Background script 'bg.js':
chrome.runtime.onMessage.addListener(function(message, sender) {
if (message.sendBack) {
chrome.tabs.sendMessage(sender.tab.id, message.data);
}
});
An alternative method is to use chrome.tabs.executeScript in bg.js to trigger a function in the main content script.
Relevant documentation
Message passing c.runtime.sendMessage / c.tabs.sendMessage / c.runtime.onMessage
MessageSender and Tab types.
Content scripts
chrome.tabs.executeScript

I understand that this is an old question but I recently spent half a day in order to solve it.
Usually creating of a iframe looks something like that:
var iframe = document.createElement('iframe');
iframe.src = chrome.extension.getURL('iframe-content-page.html');
This frame will have different origin with a page and you will not be able to obtain its DOM. But if you create iframe just for css isolation you can do this in another way:
var iframe = document.createElement('iframe');
document.getElementById("iframe-parent").appendChild(iframe);
iframe.contentDocument.write(getFrameHtml('html/iframe-content-page.html'));
.......
function getFrameHtml(htmlFileName) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", chrome.extension.getURL(html/htmlFileName), false);
xmlhttp.send();
return xmlhttp.responseText;
}
.......
"web_accessible_resources": [
"html/htmlFileName.html",
"styles/*",
"fonts/*"
]
After that you can use iframe.contentDocument to access to iframe's DOM

Related

Making message passing work in Chrome With Injected Javascript

I am trying to build a Chrome plugin (76.0.3809.132 version of Chrome) that:
Injects a JavaScript block in a web page (done)
Get the injected block to pass back message to the chrome plugin using the latest Chrome runtime functionality (problem)
I initially referred to the Chrome developer reference at: https://developer.chrome.com/extensions/messaging#external-webpage
Accordingly, my Manifest is:
{
"manifest_version": 2,
"name": "Message Passing",
"description": "Simple messaging plugin",
"version": "1.1",
"externally_connectable": {
"matches": ["*://*.myexample.com/*"]
},
"permissions": [
"activeTab",
"storage",
"tabs",
"*://*.myexample.com/*"
],
"content_scripts": [
{
"matches": ["*://*.myexample.com/*"],
"run_at": "document_start",
"js": ["myContent.js"]
}
],
"web_accessible_resources": ["injected.js"]
}
My content script:
var s = document.createElement('script');
s.src = chrome.extension.getURL('injected.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
//injecting correctly till this point
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
console.log("In plugin");//never gets called
});
JavaScript within the web page that makes the call to plugin:
//the function called by my web page, sending a JSON or text message
async function sendMessageToPlugin(dataToSend){
console.log('Send to plugin');//this is getting printed
var extID = 'abcdefghijklmnoabcdefhijklmnoabc';
chrome.runtime.sendMessage(extID, dataToSend);
}
No error, no result. I have tried different things, trying to create a background script to listen instead of listening in content script, trying to create a port for persistent messages instead, using "onMessage" instead of "onMessageExternal" per various older posts on the forum, but nothing works :(. What am I doing wrong? Can somebody at least point me towards any actually working example of a web page successfully communicating with the Chrome plugin? Would be good to know what Manifest was used and which part of the extension listened to the message successfully (content script or background). Thank you for your help!
Update 1:
I had some success with using the method described at https://developer.chrome.com/extensions/content_scripts#host-page-communication. Is this the only way of doing this? I would prefer to do it using externally connected functionality, but happy to keep testing this method and run with it if it works.
Update 2:
This code finally works for me:
In my content script-
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
//if (event.data.type && (event.data.type == "FROM_PAGE")) {
console.log("Content script received: ");
console.log(event.data);
//}
}, false);
And in my injected script:
async function sendMessageToPlugin(dataToSend) {
console.log('Send to plugin');
window.postMessage(dataToSend, "*");
}
I am sure Jafar's solution will work as well (I will try it and update!), but for now I am sticking with this one. Thank you Jafar for taking out time to reply!

Can Chrome content and background scripts share access to blob: URLs?

I'm creating a getUserMedia stream in a Chrome extension content script, and I'd like to pass it to the background script.
You can't pass non-JSON'able data between them, so my strategy is to pass instead the generated blob URL to the stream.
Content script:
function get_stream() {
navigator.mediaDevices.getUserMedia({video: 1}).then(stream => {
chrome.runtime.sendMessage({action: 'got_stream', params: {stream_url: URL.createObjectURL(stream)}});
});
Background script:
chrome.runtime.onMessage.addListener(function(data) {
switch (data.action) {
case 'got_stream': got_stream(data.params); break;
}
});
function got_stream(params) {
let vid = document.createElement('video');
alert(params.stream_url); //blob:http://...
vid.src = params.stream_url; //error - file not found
}
This is fine... until I try to apply it to a generated <video /> element, at which point the console says the file is not found.
I assume it's because the background and content scripts are in sandboxed environments.
Is there any way around this without having to do something nuclear like transfer the stream literally via WebRTC or something?
I figured out this is an origins issue.
The content script runs in the context of the present webpage, whereas the background script runs in the context of the extension.
Blob URLs are grouped by origin, so, in the same way you can't ordinarily AJAX from one domain to another, two domains also can't share blob URLs.
This is solved by running the content script not in the current webpage (so not specified in the manifest under content_scripts) but in a new tab or pop-up.
Background:
window.open('content-page.html');
Content page:
<script src='content-script.js'></script>
Then, any blob URL generated by content-script.js will be readable to the background, as they are now both running in the context of the extension, i.e. a shared origin.
[EDIT]
If you don't like the idea of a pop-up window (after all, on Mac these are rendered as full tabs), you could instead inject an iframe into the current tab and run your content script from there.
To do this, call a content script from your manifest:
{
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content-script-curr-tab.js"]
}]
}
Then in that:
let ifr = document.createElement('iframe');
ifr.setAttribute('allow', 'microphone; camera'); //necessary for cross-origin frames that request permissions
ifr.style.display = 'none';
ifr.src = chrome.runtime.getURL('page-to-inject-into-iframe.html');
document.body.appendChild(ifr);
Note chrome.runtime.getURL() - that's the key to hosting and running a page in the context of the extension not the webpage.
Then, finally, in page-to-inject-into-iframe.html:
<script src='script-to-inject-into-iframe.js'></script>
Then do your thing in there!
blob urls are likely bound to the origin so I don't think this will work. See some discussion here in the adapter repository.
Have you tried creating a RTCPeerConnection between content and background script and send the stream that way? Not ideal for a number of reasons but better than nothing.

Browser still leaking real user agent

I am writing a privacy extension which requires me to spoof the user agent property of the browser aka navigator.userAgent (yes I already know about the User-Agent HTTP header and have already dealt with that).
My issue is that a page might not just have a main frame but also a variable about of iframes as well. In my manifest file i am using all_frames: true to inject my content script into all frames and match_about_blank: true to inject into frames with a url of "about:blank".
I am using BrowserLeaks to test my extension it seems to spoof the user agent correctly using the window option but when using the iframe.contentWindow method it shows the real user agent.
I believe it might be because the iframe is sandboxed and you are not allowed to inject into sandboxed iframes. This would be a huge problem since sites could evade extensions and deny them access to a sandboxed iframe.
This is the error I get on Chromium:
Blocked script execution in 'about:blank' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
From Chrome Developer
match_about_blank:
Optional. Whether to insert the content script on about:blank and about:srcdoc. Content scripts will only be injected on pages when their inherit URL is matched by one of the declared patterns in the matches field. The inherit URL is the URL of the document that created the frame or window.
Content scripts cannot be inserted in sandboxed frames.
Defaults to false.
Or perhaps the script is running in all iframes including the sandboxed ones but the script is not running quickly enough i.e. not run_at: document_start.
From MDN
match_about_blank:
match_about_blank is supported in Firefox from version 52. Note that in Firefox, content scripts won't be injected into empty iframes at "document_start" even if you specify that value in run_at.
My title says chrome extension however it is going to be for Firefox too. I posted documentation from both MDN and Chrome since their wording is different. On Chrome when I test this on say github.com I get errors regarding sandboxing on iframe however on Firefox I get no errors of such, however it still doesn't spoof the property inside the iframe like I want it to. Any ideas?
manifest.json
{
"name": "Shape Shifter",
"version": "0.0.1",
"description": "Anti browser fingerprinting web extension. Generates randomised values for HTTP request headers and javascript API's.",
"manifest_version": 2,
"icons": {
"16": "icons/crossed_eye_16x16.png",
"32": "icons/crossed_eye_32x32.png",
"48": "icons/crossed_eye_48x48.png",
"128": "icons/crossed_eye_128x128.png"
},
"background": {
"persistent": true,
"scripts": ["js/background.js"]
},
"browser_action": {
"default_title": "Shape Shifter",
"default_icon": {
"16": "icons/crossed_eye_16x16.png",
"32": "icons/crossed_eye_32x32.png"
},
"default_popup": "html/popup.html"
},
"content_scripts": [
{
"all_frames": true,
"match_about_blank": true,
"run_at": "document_end",
"matches": ["<all_urls>"],
"js": ["js/inject.js"]
}
],
"permissions": [
"webRequest",
"webRequestBlocking",
"<all_urls>"
],
"web_accessible_resources": [
"js/lib/seedrandom.min.js",
"js/random.js",
"js/api/document.js",
"js/api/navigator.js",
"js/api/canvas.js",
"js/api/history.js",
"js/api/battery.js",
"js/api/audio.js",
"js/api/element.js"
]
}
inject.js (My content script)
console.log("Content Script Running ...");
function inject(filePath, seed) {
// Dynamically create a script
var script = document.createElement('script');
// Give the script a seed value to use for spoofing
script.setAttribute("data-seed", seed);
// Give the script a url to the javascript code to run
script.src = chrome.extension.getURL(filePath);
// Listen for the script loading event
script.onload = function() {
// Remove the script from the page so the page scripts don't see it
this.remove();
};
// Add the script tag to the DOM
(document.head || document.documentElement).appendChild(script);
}
function getSeed(origin) {
// Get a Storage object
var storage = window.sessionStorage;
// Try to get a seed from sessionStorage
var seed = storage.getItem(origin);
// Do we already have a seed in storage for this origin or not?
if (seed === null) {
// Initialise a 32 byte buffer
seed = new Uint8Array(32);
// Fill it with cryptographically random values
window.crypto.getRandomValues(seed);
// Save it to storage
storage.setItem(origin, seed);
}
return seed;
}
var seed = getSeed(window.location.hostname);
inject("js/lib/seedrandom.min.js", seed);
console.log("[INFO] Injected Seed Random ...");
inject("js/random.js", seed);
console.log("[INFO] Injected Random ...");
inject("js/api/document.js", seed);
console.log("[INFO] Injected Document API ...");
inject("js/api/navigator.js", seed);
console.log("[INFO] Injected Navigator API ...");
inject("js/api/canvas.js", seed);
console.log("[INFO] Injected Canvas API ...");
inject("js/api/history.js", seed);
console.log("[INFO] Injected History API ...");
inject("js/api/battery.js", seed);
console.log("[INFO] Injected Battery API ...");
inject("js/api/audio.js", seed);
console.log("[INFO] Injected Audio API ...");
inject("js/api/element.js", seed);
console.log("[INFO] Injected Element API ...");
I was able to get around this issues in the following way:
From my content script ...
var UAScript = "document.addEventListener('DOMContentLoaded', function(event) { var iFrames = document.getElementsByTagName('iframe'); "+
"for (i=0; i<iFrames.length; i++) try { Object.defineProperty(iFrames[i].contentWindow.clientInformation, 'userAgent', { value:'Custom' }); } catch(e) {} }); "+
"try { Object.defineProperty(clientInformation, 'userAgent', { value:'Custom' }); } catch(e) {}";
var script = document.createElement("script");
script.type = "text/javascript";
script.textContent = UAScript;
document.documentElement.appendChild(script);
This changes both the userAgent on the document, plus the iFrames after the DOMContentLoaded triggers.
Question is ..., is there a better way of doing this?
(Because you can still get around this spoof by adding an iframe inside an iframe, etc.)

Open chrome://newtab from a Chrome extension

I try to write a Google Chrome extension that simply opens a new tab when I click left-right in a short interval. The JavaScript is no problem but I implemented this as a "content_scripts" script.
In some other threads I read that I can't access the chrome.* APIs from content_scripts (except the chrome.extension API).
Even if it's not necessary to access the chrome.tabs API to open a new window (window.open should do the job) it seems I need it though for opening a new tab with the new tab page which obviously isn't possible via window.open.
So I can't really figure out what is the best way to do that. I could use a background page which I could call from the content_script but I think there should be a much more simple way to do that, I just don't get it.
Anyone have an idea?
I think your content script will have to send a message to your background page to invoke chrome.tabs.create - content scripts cannot use the chrome api, nor can they directly communicate with the background page.
Here's a reference about message passing inside Chrome extensions for further detail, but here's the example code ( modified from the example in said reference )
// in background
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
switch ( request.action) {
case 'newTab' : {
//note: passing an empty object opens a new blank tab,
//but an object must be passed
chrome.tabs.create({/*options*/});
// run callback / send response
} break;
}
return true; //required if you want your callback to run, IIRC
});
// in content script:
chrome.extension.sendMessage({action: "newTab"}, function(response) {
//optional callback code here.
});
simple and easy
document.body.onclick = function openNewWindow( ) {
window.location.href = 'javascript:void window.open( "chrome://newtab" )';
}
manifest:
,"permissions":[
"http://*/*"
,"https://*/*"
]
,"manifest_version": 2
,"content_scripts":[{
"matches":[
"http://*/*"
,"https://*/*"
]
,"js":[
"js/openWindow.js"
]
}]
alright i miss understanding the question... modified

How to filter out iframes in an Add-on SDK extension?

The main problem is that my extension is loading into every iframes on a target webpage. It puts buttons that appear inside the iframes as well. I want them to disappear. The window and document objects are shown as the parent's window and document objects. So it's impossible to check the document location for example because it shows the parent's location instead of the iframe's location.
You could write a user script which uses the #noframes metadata header key and include the user script in to your Jetpack with this user script package for the addon sdk.
Writing user scripts is much easier than writing Page Mods too.
Edit: now (Add-on SDK version 1.11 released) pagemod supports what you are looking for. See the docs and the new attachTo option.
The following information is outdated (can be used if you build against Add-on SDK 1.10 or previous:
Unfortunately pagemod injects your script in all the frames of the page, and not just once per page on the top frame as what you achieve with Chrome's content scripts. Of course, there are workarounds for stopping the execution of the script (see the other answers).
But if you really want to inject your script only once per page, on the top frame, you have to use tabs. With this solution you script only gets injected when it has to.
Bellow I provide and example for porting from pagemod to tabs, keeping all the message reception system with port working. It can easily be modified if you need to not just receive, but also send messages using that same port object. The script gets injected when we are in the domain youtube.com:
Old pagemod way:
var self = require("self");
var pagemod = require("page-mod");
pagemod.PageMod(
{
include: "*.youtube.com",
contentScriptFile: self.data.url("myContentScript.js"),
onAttach: function(worker)
{
worker.port.on("myMessageId", function(payload)
{
console.log("Message received: " + payload);
});
}
});
New tabs way:
var self = require("self");
var tabs = require("tabs");
tabs.on("ready", function(tab)
{
if (tab != undefined && tab.url != undefined && tab.url.split("/")[2] != undefined)
{
var domain = "youtube.com";
var host = tab.url.split("/")[2];
if (host == domain || host.substr(host.length - domain.length - 1) == "." + domain)
{
var worker = tab.attach({ contentScriptFile: self.data.url("myContentScript.js") });
worker.port.on("myMessageId", function(payload)
{
console.log("Message received: " + payload);
});
}
}
});
One workaround is to put something like this in your content script:
if (window.frameElement === null){
// I'm in the topmost window
// Add buttons and things to the page.
}else{
// I'm in an iFrame... do nothing!
}
The content script will still be added to every page, but it's a relatively simple and lightweight check for iFrames.

Categories