My question is sort of two-fold. First, how the sandbox model works, how it impacts the userscript, what is accessible / seen from the webpage and userscript point of view, and if using a different sandbox model affects the page being able to notice your script being injected into the page (or not). Second, how scripts are injected into the page, and can the page detect it?
First
From what I can see, when you use #grant none, the sandbox is disabled and you will have access to the webpage and its javascript. IF you make any changes to the javascript and/or DOM, it is possibly detectable by the page.
My understanding is, if you use #grant unsafeWindow, your script will be isolated in its own js context, anything you do to window will NOT be seen by the webpage, BUT you can access the webpage and javascript through unsafeWindow. You will have regular access to the DOM, e.g. document returns the regular page document rather than you needing to say unsafeWindow.document. Obviously, any changes you make to the DOM or page js context (e.g. unsafeWindow.foo = 'bar';) will still be detectable. The reason it is unsafe is not because of being detected or not, but because you are able to potentially give the untrusted page access to privileged GM_* functions in this mode, (which are not granted in regular mode, which means that #grant GM_* for any function will isolate the js context, and you'll lose access to the page's js context unless you #grant unsafeWindow)
Second
How are scripts injected into the page? Is it possible that the webpage can notice the userscript injection (assuming the userscript modifies NOTHING on the page).
For example, if a script was injected using a script tag, I think the page could possibly notice the script injection, even get a look at its code?
Does the sandbox model have any role in the way this happens, and make it "safer" to not be seen? For example, if the js contexts are isolated if you use #grant unsafeWindow, then perhaps the js on the webpage can't even see any userscript load event, making #grant unsafeWindow fundamentally safer, UNLESS you go modifying the DOM or unsafeWindow of course.
I'm also assuming that there's no leak of special functions, objects, properties, etc (such as GM_info to the webpage which would betray the existence of tampermonkey?). Neither in #grant none mode or #grant unsafeWindow mode (provided you didn't leak anything to the page)
This lets me feel that unsafeWindow is actually safer in terms of not being detected (because the js contexts are isolated), as long as you don't go modifying anything (and especially DON'T expose privileged GM_* functions to unsafeWindow). For example, if you used an eventListener on #grant none mode, it may possibly be detected, but if you use it in #grant unsafeWindow mode, it may not be detected because of the isolation? Furthermore, IF it was possible for a page to detect the userscript loading (I don't know if this is actually possible or not), it wouldn't know if the js contexts are isolated
In a brief summary, can a page detect either your userscript's or tampermonkey's existence IF you don't betray it?
Are any of my above thoughts above incorrect in any area, and if so, how does it actually work?
Update
A little information for clarification:
A userscript only reads information passively from the page (perhaps using a MutationObserver). It doesn't alter anything in any way, does not use any js libraries (neither from the userscript nor from the webpage) no ajax calls, no script nodes, definitely no clicks, etc. The script MAY read some information from JS vars on the page (let's assume those vars and functions are not booby trapped), as well as using a WebSocket (internal service). Using an IIFE too. So the question mostly is, is tampermonkey in and of itself (and if it runs a page script) detectable?
In this answer: https://stackoverflow.com/a/8548311
I can rule out 1, 4, 5, 6, and 7; probably 2 and 3 as well, but I don't know if tampermonkey in and of itself would affect any of these
Browsers and Greasemonkey/Tampermonkey/Violentmonkey have (mostly) improved how they do injection, scoping, and sand-boxing. Userscripts are not injected using ordinary <script> tags (although your script may need to create such tags in some occasions).
In fact, there's almost no need to use an IIFE nowadays.
But, in addition to the detection methods in the previously linked question:
In #grant none mode, if you #require a library that copies itself to window scope, the page can see it. Most libraries do not do that, but one that does is jQuery.
Tampermonkey actually provides the installed script version to sites that are whitelisted in the advanced settings. This is mainly for script hosts like greasyfork.org.
I don't know if a page can detect WebSockets in use by a userscript. I doubt it.
Bottom line, is for a "read only" userscript, that does not require global libraries in #grant none mode, the page cannot detect it.
(Unless the page is greasyfork.org, etc., and you have the Allow communication with cooperate pages setting at the default value.)
If you discover some leak whereby a page can detect a "passive" script, let us know and chances are it can get plugged.
As mentioned by the answer https://stackoverflow.com/a/8548311 if you do something of the likes it is definitely detectable. But, depending on what you want to do with the tampermonkey script, it will be easier or more difficult to detect, and in some cases impossible.
From what you are asking, it seems like what you want to make is just invoke an IIFE from the page, and just stop there, "let's say it just reads information".
This is really tricky to capture, and usually for this, the page should have to compare profilers and execution times and such of other users against you, or some other funky things, and there is no real easy way to find out if the user executed extra JS in the page (as long as you use an IIFE) that has NO SIDE EFFECT. I am not saying that it is 100% undetectable, but let's say it's really really tricky.
If you are going to modify the DOM, make API calls to an external or internal service, fake movements of the user or other things of this kind, you are going to be detected. So, it depends on what you want to do with the page, but you can be detected "quite easily".
In a brief summary, can a page detect either your userscript's or tampermonkey's existence IF you don't betray it?
Yes a page can detect these in those cases in which you leave a trace in the page (as defined above). Keep in mind that this will happen only there is a reason for the page to want to know if that is happening. Also keep in mind that no page will implement something like this just for the sake of it, so don't expect normal pages to complain about this.
Amazon implemented something that waits 3 or 4 seconds and then retrieves all console information.
This is what is looks like
{
"logs": [{
"level": "error",
"message": "Cannot set property 'checked' of null",
"error": {
"errorMessage": "Cannot set property 'checked' of null",
"errorName": "TypeError",
"errorStackTrace": "TypeError: Cannot set property 'checked' of null\n at storageUpdate (chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?name=myUserscript).user.js&id=ea4d27bb-1f9a-44e5-847c-2f61122b4d75:14467:86)\n at Window.configModal (chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/userscript.html?name=myUserscript).user.js&id=ea4d27bb-1f9a-44e5-847c-2f61122b4d75:14488:29)\n at <anonymous>:3:100\n at E.z.<computed> (eval at exec_fn (:1:157), <anonymous>:43:442)"
},
"context": {
"logTime": 1610058101393
}
}]
}
It's new, and whether or not they are targeting me, they can clearly see that tampermonkey is working:
chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/
Related
I am creating a Firefox extension, and one feature of it that I would like is the ability for the user to inject a script or stylesheet into a specific website, rather like Greasemonkey (except that this will only be for one site). I am adding some functions for the scripts to make use of, which I intended to add from the Content Script into the main (unsafe) window. On the MDN blog, it says that they have made changes to how it should be implemented, so I have based my code on the new implementation as advised in the post, so this is what I have:
var $jq = jQuery.noConflict();//Yes, I am also injecting jQuery at the same time
console.log("created jquery object"); //This works
exportFunction($jq, unsafeWindow, {defineAs: "$jq"});
console.log("This will never be called");
But execution of the script just stops, and in the console it prints Message: TypeError: window is null.
I am testing in Firefox 28 predominantly (I can't seem to get Firefox for Ubuntu to update beyond that right now, and a whole load of issues are forcing me to use Ubuntu in a VM for this), but in Nightly 31a1 (Win7) nothing is ever injected, including a hardcoded style (that works on FF28) so I will have to figure that out at some point. (The PageMod code is here:
var lttWorker = sdk.pageMod.PageMod({
include:["*"],
/*contentScriptFile: [sdk.data.url("jquery.large.js"), sdk.data.url("scripts/bootstrapper.js")],
contentScriptWhen: "ready",*/ //This is commented to test whether it was an issue with the script. It's not.
contentStyle: "#header_bar{background-color:green;}", //This is injected in FF28 but not 31
attachTo: ["existing", "top"],
onAttach: function(){desktopNotifications({title:"attached content worker", text:"The content worker has been successfully attached"})} //This is called in FF28 but not 31
});
lttWorker.on("error", function(){callError("pageWorker failed");}); //This never gets called. Ever.
if anybody is interested)
EDIT: I have now tried it on Firefox 30b and there are still a load of issues, although they seem to be slightly different to both FF28 and 31...
First of all: These new functions are supported in Firefox 30 and later. See #canuckistani answer.
The exportFunction API is way too limited to actually inject something like jQuery with all the complex objects being or containing DOM nodes. That simply won't fly with the structured-clone algorithm that is applied to arguments.
The API is meant as a way for add-ons to communicate with pages bi-directionally, and not to inject complex libraries.
Your best bet is actually creating a script tag using the DOM APIs and putting jQuery there.
So, here is the issue.
I have something like:
// Dangerous __hostObject that makes requests bypassing
// the same-origin policy exposed from other code.
(function(){
var danger = __hostObject;
})();
delete __hostOBject;
Am I perfectly safe knowing no script can tamper or access __hostObject?
( If they can, I have an CSRF vulnerability or worse. )
Note 1: This is for a browser extension. I have better hooks than other scripts running on the page. I execute before them and I'm done before they've even loaded.
Note 2: I know this has been asked multiple times for scripts in general. I'm wondering if it's possible if I know I load before any other scripts.
Provided that the __hostObject is deletable, the code in your question is safe.
However, I assume that your real code is slightly more complicated. In that case, very careful coding is required, because the page can change built-in methods (e.g. Function.prototype.call) to get into your closure and do whatever evil things they want. I had successfully abused functionality of extension frameworks such as Kango and Crossrider via this method when I performed such a test.
Won't simply adding a breakpoint and reloading the script expose your __hostObject
Can, for example, Facebook.com run a version control script on my browser and find out if I am running altered HTML code with the use of a script?
Could that be done with a script that can read the HTML code in the cache and produce some kind of hash tag that is sent back to the server and compared with the code that was sent to the client?
Yes, in theory, a site can deduce the presence of scripts in various situations.
This is not foolproof and usually is way too much trouble for the negligible "threat" to the site. (Then again, some webmasters can be obsessive-paranoids about such things. ;) )
Some methods, depending on what the script does (in no particular order):
Gaming or auction sites can monitor the timing (speed and regularity) of "bid" clicks.
A site can AJAX-back the count of say, <script> nodes, looking for extras.
Similarly, a site can AJAX-back any or all of the content of a page and compare that to expected values.
Extra AJAX calls, or AJAX calls that don't meet hidden requirements can be noted (and allowed to appear to succeed).
If the script uses unsafeWindow in just the right (wrong) way, a page can detect that and even hijack (slightly) elevated privileges.
"Clicks" that were not preceded by mouseover events can be detected. I've actually seen this used in the wild.
A page's javascript can often detect script-generated clicks (etc.) as being different than user generated ones. (Thanks, c69, for the reminder.)
Ultimately, the advantage is to the userscript writer, however. Any counter-measures that a webpage takes can be detected and thwarted on the user end. Even custom, required plugins or required hardware dongles can be subverted by skilled and motivated users.
Update: The methods below are fully ineffective as of Greasemonkey 3.3.
See (Dead link) How-to Detect Greasemonkey.
Javascript to detect if GM is installed (but not whether a script is actually running on that page):
Obsolete Option 1:
if (Components.interfaces.gmIGreasemonkeyService) {
alert("I smell a monkey!");
}
Obsolete Option 2:
<script type="text/javascript" src="resource://greasemonkey/addons4.js"></script>
<script type="text/javascript">
if (typeof GM_addonsStartup !== "undefined") {
alert("I smell a monkey!");
}
</script>
Is there a easy way to do this. And is there anything that needs to be changed due to differences in how it is ran?
The easiest way to do this:
Run the bookmarklet code through a URL decoder. so that javascript:alert%20('Hi%20Boss!')%3B, for example, becomes:
javascript:alert ('Hi Boss!');
Strip the leading javascript: off. Result: alert ('Hi Boss!');
Add this code to the end of your Greasemonkey file. For example, create a file named,
Hello World.user.js, with this code:
// ==UserScript==
// #name Hello World!
// #description My first GM script from a bookmarklet
// #include https://stackoverflow.com/questions/*
// #grant none
// ==/UserScript==
alert ('Hi Boss!');
Open Hello World.user.js with Firefox (CtrlO ). Greasemonkey will prompt to install the script.
Now the bookmarklet code will run automatically on whatever pages you specified with the #include and #exclude directives.
Update: To ensure maximum compatibility, use the #grant none directive that was added in later versions of Greasemonkey and Tampermonkey.
IMPORTANT:
The userscript will run much sooner than you could ever activate a bookmark. Normally, this is not a problem.
But in some cases, you might need to wait for some part of the page to fully load.
In that case, you can use techniques/utilities like waitForKeyElements.
See also, Choosing and activating the right controls on an AJAX-driven site .
If you still can't get your new script to work, be sure to read My very simple Greasemonkey script is not running?. Follow the steps and include the specified information in any question you open about problems with the new script.
Here is a very good article to avoid common pitfalls because of differences between "normal" JS and Greasemonkey.
The most important things at the beginning:
Do not use functions as strings, like: window.setTimeout("my_func()", 1000); but rather
window.setTimeout(my_func, 1000); or
window.setTimeout(function(){doSomething(); doSomethingOther();}, 1000);
Do not set element.onclick but rather element.addEventListener("click", my_func, true);
Some code that normally returns various DOM objects, in Greasemonkey environment returns those objects wrapped in XPCNativeWrapper. This is for security reasons.
Some methods and properties are "transparent" and you can invoke them on wrapped object, but some not. Read in the mentioned article about how to circumvent this; you can also use (this is not recommended generally, but for testing etc.) wrappedJSObject property. It is, when obj.something/obj.something() doesn't work in Greasemonkey, try obj.wrappedJSObject.something/obj.wrappedJSObject.something().
I have made a script that runs with no glitch on Firefox. I'm retrieving some data from external domain in an iframe to insert them in the page by using setInterval()
I have tried to use Trixie, so that it runs in IE, but it seems that functions GM_getValue and GM_setValue were not defined.
I've added these replacement functions, based on cookies, but I can't get it to work in a cross-domain way : http://www.howtocreate.co.uk/operaStuff/userjs/aagmfunctions.js
The cookie is created, and the data stored, but it's only accessible from the iframe, not from the top document.
Here is the basic structure I used : http://www.pastie.org/1889407
In test() I have access to the value stored with GM_getValue("destination",""), but it doesn't work inside function check().
1) Is there a way to make the cookie cross-domain?
2) Are there other ways to store data in IE in a cross-domain way? (I have briefly heard of Flash objects, but it doesn't seem quite a light solution...) Other implementations of these functions (getValue and setValue) are quite hard to find.
3) I'm using Trixie, maybe it's not the best solution, any advice on what plugin I should better use, to have those functions?
Well, after trying many solutions, I finally found an answer.
I used IE7PRO, that includes functions PRO_setValue and PRO_getValue, that ended to work just fine.
My problem was that I tried to access the external domain on an iframe, which is not supported by IE7PRO apparently.
I just splitted my script into 2 user scripts : one for my domain, that check if new PRO_getValue are available, and the other one for the external domain, that saves the data. One tab for my page, one for the other and surprisingly, it worked like a charm.
It doesn't seem possible with Trixie or GM4IE, the only inconvenient is that IE7PRO is heavy, since the GM script support is just a small part of this extension.