Why do userscript managers still suport the use of unsafeWindow? - javascript

unsafeWindow API was made so that userscripts could interact with the variables and functions of the pages the script executed on. However it is strongly discouraged as websites could then hijack userscripts through unsafeWindow and make them execute malicious code. However, why was unsafeWindow even necessary and why is it still used? Previously until Firefox 39, users were able to use the location hack as an alternative to unsafeWindow. This hack eventually stopped working due to an update in Firefox 39 which patched this. Despite this, users could still use GM APIs in the isolated sandbox and insert code through a script tag like this:
const fnToRunOnNativePage = () => {
console.log('fnToRunOnNativePage');
};
const script = document.body.appendChild(document.createElement('script'));
script.textContent = '(' + fnToRunOnNativePage.toString() + ')();';
// to use information inside the function that was retrieved elsewhere in the script,
// pass arguments above
script.remove();
I got this code from this stackoverflow post: How do I make my userscript execute code in isolated sandbox and unsafeWindow too?
So why is unsafeWindow is still useable? The code above is an almost perfect alternative to unsafeWindow. Also as a side note, is there any difference in the way unsafeWindow runs in Greasemonkey and Tampermonkey? Thanks.
External Resources:
https://sourceforge.net/p/greasemonkey/wiki/unsafeWindow/
https://www.tampermonkey.net/documentation.php#unsafeWindow
https://wiki.greasespot.net/UnsafeWindow

Let's call the creation of a <script> tag and inserting it into the page script injection.
Script injection has some drawbacks. To quote Brock Adams:
The script, at least the injected parts, cannot use the enhanced privileges (especially cross-domain) provided by the GM_ functions -- especially GM_xmlhttpRequest().
Can cause side effects or conflicts with the page's JS.
Using external libraries introduces even more conflicts and timing issues. It's nowhere near as easy as #require.
#require, also runs the external JS from a local copy -- speeding execution and all but eliminating reliance on an external server.
The page can see, use, change, or block the script.
Requires JS to be enabled. Firefox Greasemonkey, especially, can run on a page which has JS blocked. This can be godsend on bloated, crappy, and/or intrusive pages.
So, some developers may prefer to use unsafeWindow than to use script injection. Removing unsafeWindow would make things harder for them.
unsafeWindow often just works, and it can be less cumbersome than the creation and injection of a script tag.
Another issue is that, on the web, backwards compatibility is often one of the most important factors that is rarely, if ever, given up. If something works in a 2015 version of something, the general philosophy is that it should work in the 2020 version too, without requiring whoever worked on the 2015 version to come back and fix it (since they may not be around to do so anymore). Related discussion here. While userscript managers don't have to care as much about backwards compatibility as other things on the web, the same sort of reasoning applies - avoid breaking things that are currently working unless you have a really good reason.

In addition to the excellent explanation by CertainPerformance, unsafeWindow is simply implemented as an alias of window.wrappedJSObject.
As a script-manager developer myself, initially, I resisted the implementation of unsafeWindow but later had to add it for compatibility.
Modern browsers separate the JavaScript injected by the extension from the web page JavaScript, for security reasons.
Firefox goes further with a dedicated userScripts API to further limit the access of userscripts (i.e. 3rd party scripts) in comparison to extension's own scripts.
Bridging between these separated scopes/contexts (page, extension, userscript) increases the security concerns.
However, such interaction is sometimes needed to overwrite (undesirable) page JavaScript which can be a good thing, or to get data that is stored in the page JavaScript.
In conclusion, although unsafeWindow creates the possibility of unsafe bridges, it is not a new bridge and the same access is already available with window.wrappedJSObject.

Related

Injecting scripts in Chrome Extension global object and tamper with shadow DOM

Warning: possibly involves a set of programming bad practices to bypass security features.
We're developing a bundle of scripts that extract public information from some website. I use ViolentMonkey to inject scripts in the website, which uses Google's reCAPTCHA (see example).
To bypass it, I installed Buster: Captcha Solver, a very neat extension which places an icon on reCAPTCHA frames which, upon clicked, switches to audio solving mode and tries to transcribe the audio. It will fail every other attempt, but it's quite helpful.
The thing is: Buster detects reCAPTCHA is loaded - there doesn't seem to be enough a reason for it to require a user click. I wouldn't say it defeats the purpose, but I would rather have it run automatically. I even injected a pre-click which pops open the reCAPTCHA modal (and yes, it works), but I can't force-click Buster's icon because it is enclosed in a closed shadow DOM.
Buster basically uses shadowRoot.appendChild(), so I tried setting #run-at document-start and tampering with HTMLElement.prototype.attachShadow:
HTMLElement.prototype.attachShadow = function(args) {
return this;
};
This would theoretically make shadowRoot.appendChild() append the child directly to the node itself, but Buster is still able to attach the shadow DOM regardless. I believe overwriting HTMLElement.prototype will only work for the current global scope, and Buster is sandboxed in its own extension's environment. Had it worked, something like this would most likely be possible:
// and yeah, that's really all I'm trying to achieve
document.querySelector('.button-holder.help-button-holder')
.shadowRoot
.getElementById('solver-button')
.click();
I thought of compiling a modified version of Buster's source code in which I simply use .attachShadow({mode: 'open'}) so my scripts can finally access its button, but that'd be too much of a hassle right now. I also can but don't want to use tools outside of Chrome (like AutoHotkey) for this.
TL;DR
How can I programmatically click an element inside a closed shadow DOM with ViolentMonkey/TamperMonkey?

Don't allow third-party javascript references to freeze site

First some backstory:
We have a website that includes a Google Map in the usual way:
<script src="https://maps.googleapis.com/maps/api/js?v=....></script>
Then there is some of our javascript code that initializes the map. Now suddenly yesterday pages started to load but then freezed up entirely. In Chrome this resulted in having to force quit and restart. Firefox was smarter and allowed the user to stop script execution.
Now after some debugging, I found out that a previous developer had included the experimental version of the Google Maps API:
https://maps.googleapis.com/maps/api/js?v=3.exp
So it's likely that something has changed on Google's servers (which is completely understandable). This has uncovered a bug on our end, and both in combination caused the script to hang and freeze up the website and the browser.
Now ok, bug is found and fixed, no big harm done.
And now the actual question:
Is it possible to somehow sandbox these external script references so that they cannot crash my main site. I am loading a decent amount of external javascript files (tracking, analytics, maps, social) from their own servers.
However such code could change at all times and could have bugs that freeze my site. How can I protect my site? Is there a way to maybe define maximum allowable execution time?
I'm open to all kinds of suggestions.
It actually doesn't matter where the scripts are coming from - whether an external source or your own server. Either way they are run in the clients browser. And that makes it quite difficult to achieve your desired sandbox behavior.
You can get a sandbox inside your DOM with the usage of iframes and the keyword "sandbox". This way the content of this iframe is independent from the DOM of your actual website and you can include scripts independent as well. But this is beneficial mainly for security. I am not sure how it would result regarding the overall stability when one script has a bug like an endless loop or similar. But imho this is worth a try.
For further explanation see: https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/

Is there any difference in JavaScript injection and bookmarklet?

As per the Wikipedia article on Bookmarklets (http://en.wikipedia.org/wiki/Bookmarklet), the concept of Bookmarklets is:
Web browsers use URIs for the href attribute of the tag and for
bookmarks. The URI scheme, such as http:, file:, or ftp:, specifies
the protocol and the format for the rest of the string. Browsers also
implement a prefix javascript: that to a parser is just like any other
URI. Internally, the browser sees that the specified protocol is
javascript, treats the rest of the string as a JavaScript application
which is then executed, and uses the resulting string as the new
page.
It says that the resulting string is used as the new page. So does that mean the original DOM that browser has is not affected by that string? But then how can I change or inject new DOM elements in the existing DOM if only the resulting string is used as a new page? Because script to alert Hello or to inject some new DOM element doesn't really return anything. They kinda work on the existing DOM.
Now, in internet explorer, apart from using Bookmarklets to execute some JavaScript on the page, I can write a BHO plugin and inject it in the following way:
document = (HTMLDocument)webBrowser.Document;
var injectedJS = System.IO.File.ReadAllText("InjectedJS.js");
var window = document.parentWindow;
window.execScript("(function(){ " + injectedJS + " })()");
Similarly in chrome, I can write an extension to achieve the same thing:
var s = document.createElement('script');
s.src = chrome.extension.getURL("script.js");
s.onload = function() {
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
How are these different?
The high level questions that I have are:
Do these three approaches execute the JavaScript code in different environments?
Is there any limitation to one of them that another one doesn't have?
Is there any difference in the way the result of the execution is presented to the user or is reflected back in browser?
Is there any difference at all between the terms "JavaScript Injection" and "Bookmarklets"? Though I believe JavaScript Injection is an effect and Bookmarklets are a way to achieve that, BHO and Chrome extensions being another.
If assumption in 4 is correct, is there any difference in the way JavaScript is executed using BHO's execScript method or using javascript: protocol in a browser?
1. Do these three approaches execute the javascript code in different environments?
All of these three methods execute JavaScript code in the context of the page. When these methods are compared with each other, you can say that the JavaScript code is executed in the same environment.
Content scripts (Chrome/Opera/Firefox/Safari) run in an environment that is isolated from the web page, so looking from that perspective, the environment is indeed different.
BHOs are a tad different, because unlike the other extension platforms, the extension's language is not JavaScript, but C++, C#, ... The JavaScript code cannot directly access the BHO's native code (unless you implement such a thing yourself), so the environment is certainly "different".
2. Is there any limitation to one of them that another one doesn't have?
Bookmarklets are javascript:... URLs, and nothing more. Browser extensions can directly perform cross-origin HTTP requests, access persistent site-independent storage, etc. If you want to get similar features in a bookmarklet, you need to use an external web service.
Bookmarklets can only be active when they are manually activated by a user. Whether this is an advantage or disadvantage depends on your situation.
The maximum size of a bookmarklet is restricted by the maximum URL length, which is rather small. This limitation can be circumvented by inserting a <script src> tag in the document. The script has to be loaded first, so the execution of the code is delayed.
Bookmarklets can be used in almost every web browser, including the ones on phones and tablets (Chrome extensions can only be used in, well, desktop Chromium browsers).
3. Is there any difference in the way the result of the execution is presented to the user or is reflected back in browser?
No. In all cases, you're running code in the context of the current page. In theory, a page can replace all built-in methods (e.g. Function.prototype.call, String.prototype.replace, ..), and interfere or abuse the functionality of your script.
Possibly worth noting: The Crossrider and Kango extension frameworks implement the "content script" feature for Internet Explorer in a way that is similar to these three methods. This implies that pages can be crafted in such a way that they detect IE plugins written using these frameworks, intercept the API declaration and abuse their features.
4. Is there any difference at all between the terms "Javascript Injection" and "Bookmarklets"? Though I believe Javascript Injection is an effect and Bookmarklets are a way to achieve that, BHO and Chrome extensions being another.
There is no conceptual difference between a bookmarklet and "injected script". There are some practical differences, explained at section 2 of this answer.
(by "injected script", I assume you're referring to this method I coined the term to distinguish between the types of scripts in the Chrome extensions. Opera 12- and Safari both use this term for "content scripts").
5. If assumption in 4 is correct, is there any difference in the way javascript is executed using BHO's execScript method or using javascript: protocol in a browser?
Besides the previously mentioned differences, no.

show which scripts are loaded

I have a suspicion some users have toolbars, plugins, malware or something, which is running some javascript in their browser when visiting our site, and this javascript is messing up the functions on our pages.
Can my javascript see which javascripts have been loaded from other files, i.e. by http://some.site/script.js'> ?
Open the developer tools on your browser (usually with F12). Then check the network section/tab, it will list all resources loaded into the current document.
If you want to find scripts loaded by a page and you are not able to access the dev tools, you can search the DOM for script tags. This will only find scripts that were loaded by the DOM (which is quite typical). But it will not find scripts loaded through xhr requests and then eval-ed and it will not find scripts loaded by browser extensions.
But, this may still help you somewhat. You can do something like this:
console.log(window.document.getElementsByTagName("script"))
This will log all loaded scripts to the console. Alternatively, you can analyze this in the client and pop up a bright big warning message if you see something suspicious.
To capture other kinds of requests, you might be able to add a wrapper to the xhr send method, like this:
XMLHttpRequest.prototype.send = function () {
// do some checking of the request
// do the actual request
this.send();
}
I haven't tried this, so there may be some caveats, like I'm not sure if all browser engines allow you to redefine methods of built-in types. But it is worth trying out.
Your question is interesting but the motivation is wrong, IMO. What leads you to believe that "something" is messing up your JavaScript? Can you reproduce the issue? Even if some kind of bookmarklet is clobbering global JavaScript code, you (as a developer) have the ability to check for proper functionality and polyfill if needed.
If you look more closely at your code and why it's not doing what you expect, I expect that you're going to find that malware/toolbars/plugins are the not to blame.
Isolate the code that is failing and reproduce the behavior.

How can I patch client side javascript on a website after loading it in Opera or Firefox?

There is a particular website I must use for work which is absolutely heinous and despised by all who must use it. In particular, the site's Javascript is fundamentally broken and works only in IE, which pretty much makes it the only site I must use outside my preferred browsers.
So, to the question. If I could 'patch' the javascript after loading the website in such a fashion as to 'do the right thing', I could then use the website without IE.
( Just to cut out some of the superfluous answers: I have already tried masking both browsers as IE, which has no effect because the issue is with the javascript, not browser detection on the server. )
I would prefer solutions which are for Opera, though I'm not opposed to Firefox answers. Also, I would rather not have to view the site though a proxy, though I will entertain such answers.
For Opera, you want User JavaScript. Similar to Greasemonkey, but built-in to Opera. Built to be used for exactly the sort of situation you're in: fixing sites that are broken in Opera...
For Firefox, you could use the Greasemonkey addon to do this.
You could probably use the Greasemonkey addon for firefox to do this. It would let you write javascript to run in their page, and could probably use that to do a "patch" at runtime. I've never written a greasemonkey script before, so I don't know how easy it would be to get something working, but it might be worth a look.
Greasemonkey is exactly what you want for this. I have extensively hacked some sites using it and there are a plethora of good examples at www.greasespot.net. Although this page is about Opera it has some good examples that are applicable to Greasemonkey in firefox too. I also noticed that the Greasemonkey wikipedia article has information about using Greasemonkey or equivalents in other browsers too.
You can use also a proxy (I used Proxomitron a long time ago, there are more modern equivalences) which alters the page on the fly before they reach the browser... Thus you can remove all original JavaScript, and add your own, by this way, or with Greasemonkey or user scripts.
I'm not sure that GreaseMonkey will be much good for patching.
GM scripts run after the page has loaded and all native scripts have run so anything that was going to break will already have broken.

Categories