Eval in contentWindow - CSP

In a privacy related a–on I’m working on, I need to expose functions to the web pages which need to look and behave exactly like build-in functions.

I have tied to do this in a couple of ways, most notably:

  1. )
    Plain contentWindow.eval(codeThatProducesFunctions)
    but this failes if the pages was served with a CSP that forbids 'unsafe-eval'.

  2. )
    Using Sandboxes:

const ucw = Cu.waiveXrays(contentWindow);
const sandbox = Cu.Sandbox(ucw, {
	sameZoneAs: ucw,
	sandboxPrototype: ucw,
	wantXrays: false,
});
Cu.evalInSandbox(...)

but the resulting functions are not instanceof ucw.Function.

  1. )
    Using Cu.exportFunction(func, Cu.waiveXrays(contentWindow))
    but this cant produce functions which throw for Reflect.construct(Object, [ ], func), which most build-in functions do.

The best solution would probably be to get 1.) to ignore the CSP. Or find a configuration for Cu.evalInSandbox(...) that actually evaluates in the contentWindow.

Any suggestions on how to achieve this?

1 Like

What I do is establish a message port between framescript and content. Then I set up a little async callback scheme based on event listeners.

The below is very crude, not tested I just wrote it right now - its based on what I do in my communication api i use - https://github.com/Noitidart/Comm

In content I addEventListener for custom things like:

window.addEventListener('callInFramescript-request', function() {
    var obj = e.data;
    var msgid = data.id;
    messageChannel.port.postMessage(obj);
}, false);

messageChannel.port.onMessage = , function(e) {
        var msgid = e.data.id;
        if (msgid) // trigger the callback by triggering callInFramescript-response event
    }

Then from website I do:

 var nextid = 1;
 var callbacks = {};

 function callInFramescript(method, obj, callback) {
     obj.method = method;
     if (callback) {
         obj.id = nextid++;
         callbacks[obj.id] = callback;
     }
     var evt = document.createEvent('CustomEvent');
     evt.initCustomEvent('callInFramescript-request', true, true, obj);
     window.dispatchEvent(evt);
 }
 window.addEventListener('callInFramescript-response', function() {
     var obj = e.data;
     callbacks[id](obj);
     delete callbacks[obj.id]
 }, false);

Now if you want to call something from your website just do:

callInFramescript('doX', {data:true}, rez=>console.log(rez));

Of course you have to set

Ahm, that’s not an answer to my question at all.
My problem is getting code into the page (in the exact right way), not sending messages back.
Are you sure you wanted to post that in this thread?

“Body is too similar to what you recently posted” -.- :
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Oh my mistake, I thought your end goal was to expose a function to websites, which will do work in the privileged scope and return a result.

Nope. The functions can live entirely in the content, the important point is that they need to be “born” there too (or at least look as if they were).

Ah.

My friend told me to tell you this:

Look into using Proxies - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

It would increase the calling overhead even more than it already is, but that might in fact work.
If a proxy can wrap an object for which it would receive an opaque wrapper, that is. I’ll try that.
Please tell your friend “Thank you”

Meh, that doesn’t work either.
Form what I’ve tested,

(func, ucw) => new ucw.Proxy(function() { }, {
    apply(_, self, args) { return func.apply(self, args); },
});

is very similar to

(func, ucw) => Cu.exportFunction(func, ucw);
// (where ucw is Cu.waiveXrays(someContentWindow)

and also suffers from the same problems as my previous attempt 3.) above.

What about this stuff right here, its Firefox only cloneInto and exportFunction: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#Sharing_content_script_objects_with_page_scripts

Unfortunately, my problem is somewhat more involved than that. I am able to export functions, but:

JavaScript functions can have two internal methods:

  • [[Call]], which is used if the function is called without new
  • [[Construct]], which is used if the function is called with new.
    Each object which is typeof === 'function'has either or both of these.

If you attempt to call a function the wrong way, a TypeError will be thrown.
The problem is that the .message of this TypeError contains information that is not available to the script, like the local identifier of the function being called; which means it is not sufficient to check the value of new.target within the function. It is not possible to throw the correct error here.

So if, for example, I want to expose a method (which mustn’t have a [[Construct]] internal method), I can’t just export a plain function. Calling that with new can’t produce the correct Error.

Unfortunately it seems to me that Cu.exportFunction always produces functions with both [[Call]] and [[Construct]] internal methods, and the methods that don’t exist on the original function will then throw. But at that point it’s already to late; the error is not authentic.

The difference between an actuall function missing either of the internal methods and a wrapped function is quite small, but at least the presence of the [[Construct]] method can be reliably checked. For example with this function:

/**
 * Tests whether a function can be used as a constructor, without attempting to call that function.
 * @param  {function}  func  Function object to test.
 * @return {Boolean}         True iff func has a [[Construct]] internal method.
 *                           That is, if it returns false, then func is not a function or constructing it with new would throw 'TypeError: <local id> is not a constructor'.
 *                           If it returns true, it may still throw 'TypeError: Illegal constructor.', but is is a constructor.
 */
function isConstructable(func) {
	try {
		construct(Ctor, [ ], func);
		return true;
	} catch (_) {
		return false;
	}
};
class Ctor { }
const { construct, } = Reflect;



So, as I said before, as far as I know, only contentWindow.eval(...) produces correct functions. But to get that to work on every page I’d have to intercept the CSP, allow 'unsafe-eval' (which opens a security hole) and then try to stuff that hole again by wrapping eval, Function, setTimeout and everything else covered by that directive. And that’s quite a lot of effort and also quite a mess.

I hoped Firefox would provide a better way o do this.

1 Like

When I suggested to use proxies and provided a link to proxies, I hoped that you would find the construct trap. Every method of Reflect has a counterpart in a Proxy handler, including the construct trap (from where you can throw your desired error to prevent the constructor from working).

Proxies are powerful. If all you’re caring about is that the function cannot be constructed (and this is not important), then you can use arrow functions instead: var func = () => { /* implementation here */}; will pass your isConstructable test because arrow functions cannot be constructed. To hide the implementation (i.e. serializing to “[native code]”), use bind: var func = (() => {}).bind();

1 Like

That’s the problem. “manually” throwing an error is not good enough, it can’t have the correct message (which includes information about the calling code). What I need is a function that directly throws, before it is being called. Wrapping (using Cu.exportFunction()or a Proxy without constructtap) an arrow function or a class method (which both lack the [[Construct]] method), should (in my opinion), but doesn’t work.

Either way I end up with an object that has a [[Construct]] method which attempts to construct the target and then throws. But that’s to late. The error message and stack is wrong. The stack I can fix, the message I can’t.

And because the this value does matter in some cases, I can’t use arrow functions and bind(). I hide the functions code by overwriting Function.prototype.toString().

1 Like