Closed kekkc closed 4 years ago
FireMonkey uses the userScripts API which was specially created for security and privacy with Xray vision to prevent malware userscripts gaining access to privileged functions via the extension.
Scripts can use any JS function in the page-scope. Why does the content script require exportFunction()
& cloneInto()
?
For interacting with page JS you can also check out: Interacting With The Page
Why does the content script require exportFunction() & cloneInto()?
Because I need to rewrite some functions, e.g.:
exportFunction(function (){return true;}, a, {defineAs:'confirm'});
exportFunction(function (){return false;}, a, {defineAs:'opener'});
Reason for that is to prevent that my tabs get "hacked": https://mathiasbynens.github.io/rel-noopener/
Where is your userscript?
Where? It's my own local script, I can PM it to you. But it currently doesn't contain much else other than a list of these functions that I nullify.
Have you tried window.eval(......)
?
exportFunction()
& cloneInto()
are not JS. They are Firefox internal functions not suitable for content script.
This seems to be because the userscript requires the extension to make this available for the actual user, which was described by Luca in a bugzilla bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1516356#c1
That is to make GM* functions available not to make the exportFunction()
& cloneInto()
available to users. That would be a great security risk if user-script can interact with internal browser functions. Other script managers should not make these function available to user-scripts.
eval doesn't work for me reliably.
exportFunction() & cloneInto() are not JS. They are Firefox internal functions not suitable for content script.
FM is a FF extension ;)
But seriously: your linked GM documention leads to https://wiki.greasespot.net/Content_Script_Injection where it's cleary stated: "Warning: The contents of this page are not accurate when used with Greasemonkey 4.0." In GM 4.0 exportFunction() & cloneInto() are the only way to exchange data with the website, because unsafeWindow was disabled (I guess this includes eval).
That would be a great security risk if user-script can interact with internal browser functions.
No, the documentation on https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction describes it:
This function provides a safe way to expose a function from a privileged scope to a less-privileged scope. In this way privileged code, such as an extension, can share code with less-privileged code like a normal web page script.
FM is a FF extension ;)
Internal API are not content JavaScript. The code designed to run in Firefox background. It is not the same as the JavaScript that runs inside a web page.
For example, none of the browser.***.***()
API are available inside JS in a web-page.
But seriously: your linked GM documention leads to https://wiki.greasespot.net/Content_Script_Injection where it's cleary stated: "Warning: The contents of this page are not accurate when used with Greasemonkey 4.0."
True.. that is a bad practice but some people still do it. I did not include the unsafeWindow
in FireMonkey for that reason. I was showing you alternative methods of communication between userscript & page script.
In GM 4.0 exportFunction() & cloneInto() are the only way to exchange data with the website, because unsafeWindow was disabled (I guess this includes eval).
eval()
is a JavaScript function while unsafeWindow
is a GreaseMonkey made-up function and exportFunction()
& cloneInto()
are core Firefox functions designed to run in the browser-scope.
Page/content-scope functions should not be mistaken for browser-scope functions.
FireMonkey does not include unsafe methods that allows malware to cause harm to users by using browser-scope functions (such as exportFunction()
& cloneInto()
)
No, the documentation on https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction describes it:
That is used for extension's OWN code which is owned by the extension and checked and has privilege. Extension code is the code that is inside the addon when you download the addon.
It is not to be used for userscript which is not written by the extension developer and not checked by AMO and has unknown code & origin.
userscript is content-scope JavaScript code and can use any function that are part of JavaScript.
Old thread, but just some addition:
exportFunction() & cloneInto() are core Firefox functions designed to run in the browser-scope.
No, seems this was not the intention, according to the bugzilla ticket, this use case was considered primarily and should be alot safer & more reliable than e.g. eval:
The userScripts API already allows the extension itself to provide this kind of helpers (if they are needed and they should be available to the particular user script), because the userScripts's apiScript has these helpers available
exportFunction()
& cloneInto()
are not standard JavaScript. In order to make them available, background script has to create an API for them specifically.
I have to check with Mozilla security team to see if there are the security risks. If there are no security risks, then it can be considered.
Ref: Chat with Luca Greco (userScript
API Developer)
erosman: I have always tried to make FireMonkey as secure as possible and avoid risky situations. Global window functions like the
unsafeWindow
has been known to be a risk. window.wrappedJSObject&
exportFunction&
cloneIntoare similar to
unsafeWindow`. I don't want to open an avenue for exploitation even if that means losing users.Luca Greco: yeah, exposing those (
unsafeWindow
,exportFunction
andcloneInto
) will make the userScript potentially more exposed to the content, this is definitely true, and so potentially more open to exploitation. We decided not to provide those helpers in the userScripts global by default for the same reasons.
I have updated the Help to provide more info on the subject.
Also ..... quoting from above linked "update the userscripts"
Out of curiosity ........... Can someone ask Jason Barnabe at Greasy Fork, what percentage of scripts require unsafewindow, exportFunction() & cloneInto()?!!! If the percentage is high, I will try to work something out with Mozilla security team (somehow).
Thanks for digging into this and consolidating the info here ;) Would be cool if there's an "official" way.
I only found this old blog post that announced ""unsafe-content-script": true" as workaround and exportFunction() & cloneInto() as the "real fix" for userscripts (in 2014 these were content scripts / GM scripts)( https://blog.mozilla.org/addons/2014/04/10/changes-to-unsafewindow-for-the-add-on-sdk/ ).
Also GM needs a way to turn xrays of, if that is related: https://bugzilla.mozilla.org/show_bug.cgi?id=1437098#c22
Firefox implemented Xray vision to make users safer. Turning off or bypassing Xray vision is a risk to the user security and will open up security holes.
exportFunction()
& cloneInto()
create security holes. The MDN document explains the issue with reference to extensions own code.
It is even more dangerous if that is allowed for unknown 3rd party scripts (e.g. user-script) as it will create all sorts of problems.
👉 There are methods to interacts with page scope JS without the need for exportFunction()
& cloneInto()
as listed on the referenced pages.
Here are some snippet that I have added to the Help document for the next release. ⬇⬇⬇⬇⬇
Source: unsafeWindow
This API object allows a User script to access "custom" properties--variable and functions defined in the page--set by the web page. The unsafeWindow object is shorthand forwindow.wrappedJSObject
. It is the raw window object inside theXPCNativeWrapper
provided by the Greasemonkey Sandbox.
USE OF UNSAFEWINDOW IS INSECURE, AND IT SHOULD BE AVOIDED WHENEVER POSSIBLE.
Safer alternatives to unsafeWindow
are also listed in above page.
Source: unsafeWindow
This command can open certain security holes in your user script, and it is recommended to use this command sparingly.
Please be sure to read the entire article and understand it before using it in a script.
More Info: Why is window (and unsafeWindow) not the same from a userscript as from a <script> tag?
Keywords: Xray vision in Firefox, exportFunction(), cloneInto(), Constructors from the page context
Source: Sharing objects with page scripts As an extension developer you should consider that scripts running in arbitrary web pages are hostile code whose aim is to steal the user's personal information, damage their computer, or attack them in some other way. The isolation between content scripts and scripts loaded by web pages is intended to make it more difficult for hostile web pages to do this. Since the techniques described in this section break down that isolation, they are inherently dangerous and should be used with great care. ... Note that once you do this, you can no longer rely on any of this object's properties or functions being, or doing, what you expect. Any of them, even setters and getters, could have been redefined by untrusted code.
Note:
TemperMonkey unsafeWindow
seems to be implemented via injecting <script>...</script>
GreaseMonkey unsafeWindow
seems to be implemented via eval()
& window.wrappedJSObject
ViolentMonkey unsafeWindow
seems to be implemented via eval()
v1,24 Added unsafeWindow
support
Wow, pretty cool, thx ;)
Realized only that this code produces an "Error: Permission denied to access object" on FF 72.02:
Userscript:
window.wrappedJSObject.focus = function (){};
Website:
<SCRIPT language=JavaScript><!--
self.focus();
//--></SCRIPT>
Strangely this error is not displayed in FF developer 73b12
FM unsafeWindow
also uses window.wrappedJSObject
(similar to GM).
Let me know how it works out.
Gotcha, unsafeWindow works great, no errors. Many many thanks erosman ;)
I could be mis-remembering (my testing was done so long ago and I haven't touched scripts in a while), but I think exportFunction
retains line numbers on errors. A lot of things exportFunction
does can be mimiced with a window.eval
or new window.Function('arg1', 'arg2', 'return arg1 + arg2')
, but you lose some debuggability.
Not saying it's worth the security risks (assuming my memory is working properly), just thought I'd point it out.
@Sxderp Thanks for the info. I will test it out.
userScript API does log errors to the console with clickable reference to the error location (e.g. script blob), although sometimes errors are generated elsewhere for example if GM.*/GM_*
API is used.
Here's a sample. Assuming exportFunction
is available.
// ==UserScript==
// @name meh
// @version 1.0
// @match https://example.com/*
// ==/UserScript==
function exportFunc() {
let unknown;
unknown.error;
}
exportFunction(exportFunc, window, {defineAs: 'exportFunc'});
let funcFunc = new window.Function(`
let unknown;
unknown.error;
`);
window.wrappedJSObject.funcFunc = funcFunc;
window.eval(`
window.evalFunc = function() {
let unknown;
unknown.error;
}
`);
Go to https://example.com and open the debug window (Ctrl+Shift+K). In the interpreter tool (I don't know what it's actually called), run window.exportFunc()
, window.funcFunc()
, window.evalFunc()
. The export is the only one with a clickable error line.
Test 1
// ==UserScript==
// @name meh
// @version 1.0
// @match https://example.com/*
// ==/UserScript==
function exportFunc() {
let unknown;
unknown.error;
}
typeof exportFunction !== 'undefined' &&
exportFunction(exportFunc, window, {defineAs: 'exportFunc'});
let funcFunc = new window.Function(`
let unknown;
unknown.error;
`);
window.wrappedJSObject.funcFunc = funcFunc;
window.eval(`
window.evalFunc = function() {
let unknown;
unknown.error;
}
`);
console.log('window.exportFunc', typeof window.exportFunc);
console.log('window.funcFunc', typeof window.funcFunc);
console.log('window.evalFunc', typeof window.evalFunc);
All above console.log are clickable to the blob.
Note: code errors also have clickable links
Test 2
// ==UserScript==
// @name meh
// @version 1.0
// @match https://example.com/*
// ==/UserScript==
function exportFunc() { console.log('this is exportFunc'); }
typeof exportFunction !== 'undefined' &&
exportFunction(exportFunc, window, {defineAs: 'exportFunc'});
console.log('exportFunc', typeof window.exportFunc);
let funcFunc = new window.Function(`console.log('this is funcFunc');`);
console.log('funcFunc', typeof window.funcFunc);
window.wrappedJSObject.funcFunc = funcFunc;
console.log('funcFunc', typeof window.funcFunc);
window.eval(`function evalFunc() { console.log('this is evalFunc'); }`);
console.log('window.evalFunc', typeof window.evalFunc);
AFA how to actually export function without exportFunction()
I have to do more testing.
It seems unsafeWindow/window.wrappedJSObject
can get/set variables in the page window and can get functions but not set them !!? (It could be the Xray Vision blocking it).
window.eval
doest work either
window.eval(`var foo = "I'm defined in a page script!";`);
console.log('window.foo', typeof window.foo); // window.foo undefined
console.log(window.foo); // undefined
console.log(foo);
//Console
//ReferenceError: foo is not defined ae3ddbd7-5284-49d1-be8a-a72925516b9d:15:1
Note: these are tested with FM & userScript API that has Xray Vision.
Test 3
// --- Page JavaScript
var foo = "I'm defined in a page script!";
function runTest() {
console.log('runTest', foo); // runTest new unsafeWindow foo
console.log('testFunc', typeof window.testFunc); // testFunc function
window.testFunc(); // Error: Permission denied to access object
testFunc(); // Error: Permission denied to access object
}
// --- user-script
console.log('unsafeWindow.foo', unsafeWindow.foo); // unsafeWindow.foo I'm defined in a page script!
unsafeWindow.foo = 'new unsafeWindow foo'; // changing foo on page
unsafeWindow.testFunc = function() { console.log('this is testFunc'); } // creating new function
unsafeWindow.runTest();
unsafeWindow.testFunc(); // this is testFunc
testFunc(); // Error: Permission denied to access object
It seems...
unsafeWindow
can be used to create functions and export to page windowunsafeWindow
created functionsunsafeWindow
cna take the place of exportFunction()
re: cloneInto()
I will test object creation
Note: All data relates to FireMonkey only.
Test 4
Accessing Page DOM
unsafeWindow.testFunc = function() { console.log(location.href); }
unsafeWindow.testFunc(); // https://example.com/
Test 5
Normal cloneInto()
const obj = {text: 'hello from user-script'};
unsafeWindow.obj = cloneInto(obj, unsafeWindow);
unsafeWindow.obj = {text: 'hello from user-script'};
console.log(unsafeWindow.obj.text); // hello from user-script
After an eval you must use window.wrappedJSObject
(assuming this is unsafeWindow
in FM) to access the object in the UserScript.
window.eval(`
window.evalFunc = function() {
let unknown;
unknown.error;
}
`);
console.log(window.wrappedJSObject.evalFunc); // function evalFunc()
My concern is that you can't debug the content that was evaled. More complete example. Page code
function runTestEval() {
console.log('runTestEval');
console.log('evalFunc', typeof window.evalFunc);
window.evalFunc(); // This errors with no clickable bit
}
Userscript
// ==UserScript==
//
// ==/UserScript==
window.eval(`
window.evalFunc = function() {
console.log('in eval func')
let unknown;
unknown.error;
console.log('out eval func')
}
`);
window.wrappedJSObject.runTestEval();
No error message appears in the web page console. An error does appear in the browser console, but it is not clickable.
Now, this is generally pretty edge case. I'd say it only occurs when you're purposely piggy-backing off of the page content, or trying to overwrite very specific functions within the page content.
I actually get a clickable reference however, since the actual function in on the page, it refers to the page (not the user-script that called it) Note: I didn't define it to force an error and see the result
unknown.error;
That's weird, how do you get evalFunc is not a function? Using Firemonkey with the above code blocks this is what I get in the page console; the eval link (console.log) is clickable, and sends you to the blob. But as you can see the page doesn't indicate any errors.
And in the browser console; the eval link is not clickable.
That creates a no-reference error
It's supposed to. In order to force an error.
Page Script
function runTestEval() {
console.log('runTestEval');
console.log('evalFunc', typeof window.evalFunc);
window.evalFunc(); // This errors with no clickable bit
}
User script (I commented out the error)
window.eval(`
window.evalFunc = function() {
console.log('in eval func')
let unknown;
//unknown.error;
console.log('out eval func')
}
`);
window.wrappedJSObject.runTestEval();
All are clickable to the page or debugger
All are clickable to the page or debugger
Yes. My concern was that errors are not clickable (nor do they show up in the page console). I would not call it a major issue, but it does make debugging a little harder.
That's weird, how do you get evalFunc is not a function?
Sorry... That was me. :p I was testing and didnt define it to see how the error comes up.
My concern was that errors are not clickable (nor do they show up in the page console)
Sadly, it is out of our hands ;)
Sry to bother (again), but seems there's still some glitch, if the following userscript is used (all frames have to be used):
// ==UserScript==
// @name asdf
// @run-at document-start
// @match <all_urls>
// @allFrames true
// ==/UserScript==
unsafeWindow.document.hasFocus = function() {return true;};
on the following site: https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_document_hasfocus webconsole gets flooded with "Error: Permission denied to access object" that is called repeatedly & endlessly. In general, I guess there shouldn't be any issue, since the script should be inserted in the frame and therefore same-origin.
Try:
// ==UserScript==
// ...
// ==/UserScript==
let hasFocus = new window.Function('return true');
unsafeWindow.document.hasFocus = hasFocus;
@kekkc To start with, that is a really poor code on w3schools.com
setInterval("myFunction()", 1);
function myFunction() {
var x = document.getElementById("demo");
if (document.hasFocus()) {
x.innerHTML = "The document has focus.";
} else {
x.innerHTML = "The document DOES NOT have focus.";
}
}
That setInterval()
is running 1000 times per second. Who wrote that!?? :-1:
Then the error comes from that page (not the user-script)
Error: Permission denied to access object tryit.asp:14:16
-> if (document.hasFocus())
I tested it with GM and the same error come up, although the error only comes up when clicking as it breaks codemirror code.
Error: Permission denied to access object 5 codemirror.js:3702:90
-> var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus())
Try:
// ==UserScript== // ... // ==/UserScript== let hasFocus = new window.Function('return true'); unsafeWindow.document.hasFocus = hasFocus;
Many thanks, that fixed it. BTW: I also included:
Object.defineProperty(unsafeWindow.document, 'hidden', {value: false});
Maybe these methods of "overwriting" methods can also be included in the help file ;)
I tested it with GM and the same error come up, although the error only comes up when clicking as it breaks codemirror code.
Thanks, I realized the same. Know that w3schools.com is often bad, but at least good for tests.
OT: Nevertheless Sxderp's suggestion fixed the "Error: Permission denied to access object" (and many other issues I had with my old method of replacing methods. To be exact 20 years old, coming from an old program Proxomitron that focused on filtering / modifying webpages to get rid of ActiveX phone dialers)
@Sxderp @kekkc Thank you. I will add it to the Help. :+1:
PS. OT. I do recall Proxomitron from that time and used it before switching to Privoxy ;)
Support for exportFunction()
& cloneInto()
will be added to v2.19
Hi,
seems exportFunction() & cloneInto() are not available in a userscript ( https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts#exportFunction ).
This seems to be because the userscript requires the extension to make this available for the actual user, which was described by Luca in a bugzilla bugreport: https://bugzilla.mozilla.org/show_bug.cgi?id=1516356#c1
Any chance that this will be implemented so that both function can be used in scripts?