TamperMonkey for Content Protection

Some time ago, I made a grease monkey plugin to sort out the urls of commonly used projects in different environments for ease of use.
Recently, I have been a little annoyed by the db configuration of various projects in various environments, so I thought about putting the db configuration into the script, but there are still some sensitive information in these db configurations, and there may be security problems if I put it rashly. In order to study the security of TamperMonkey, I studied the doc of TamperMonkey and its principle.

Independent operating environment

First, stick to the conclusion of a foreign forum:

See “Are Chrome user-scripts separated from the global namespace like Greasemonkey scripts?”. Both Chrome userscripts/content-scripts and Greasemonkey scripts are isolated from the page’s javascript. This is done to help keep you from being hacked, but it also reduces conflicts and unexpected side-effects.

However, the methods are different for each browser…

Firefox:

  1. Runs scripts in an XPCNativeWrapper sandbox, unless @grant none is in effect (as of GM 1.0).
  2. Wraps the script in an anonymous function by default.
  3. Provides unsafeWindow to access the target page’s javascript. But beware that it is possible for hostile webmasters to follow unsafeWindow usage back to the script’s context and thus gain elevated privileges to pwn you with.

Chrome:

  1. Runs scripts in an “isolated world”.

  2. Wraps the script in an anonymous function.

  3. Strictly blocks any access to the page’s JS by the script and vice-versa.

    Recent versions of Chrome now provide an object named unsafeWindow, for very-limited compatibility, but this object does not provide any access to the target page’s JS. It is the same as window in the script scope (which is not window in the page scope).

The above conclusion can be roughly summarized as follows: TamperMonkey’s runtime environment is not the same as the page’s JS script. The script in TamperMonkey runs in a sandbox environment, but both can operate the page.

The sandbox environment is also different. If your @grant value is none, then if you mount some properties to the window object, other scripts can theoretically access it.

But if your @grant is not none, then even the window object is separate, and the properties you mounted in the script cannot be accessed in other scripts.

Closure

Let’s take a look at the basic format of the TamperMonkey script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// UserScript
// @name New Userscript
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Xray_vision
// @grant none
// /UserScript

(function() {
'use strict';

// Your code here...
})();

We can see at a glance that this is a closure and an immediate function, so all variables defined in it are inaccessible externally unless we consciously mount them on the window.

Conclusion

To sum up, the script in TamperMonkey runs in another environment that is completely independent of the page’s JS, and each script will not affect each other. At the same time, because the actual content of the script is in a closure, only if the grant is None and we consciously mount a property on the window object, otherwise this property will not be accessed externally.

Expand

Chrome

Gecko runs JavaScript from a variety of sources and at a variety of privilege levels:

  • The JavaScript code that implements the browser itself together with the C ++ core is called chrome code and runs with system privileges. If the chrome privileged code is compromised, an attacker can take over the user’s computer.

  • JavaScript loaded from a normal webpage is called content code. Because this code is loaded from an arbitrary webpage, it is considered untrusted and potentially hostile to other websites and users.

  • In addition to these two privilege levels, chrome code can also create sandboxes. Determine its privilege level for the security principal defined by the sandbox. If an extended principal is used, the sandbox will gain certain privileges over the content code and is protected by direct access to the content code.

Security mechanisms in Gecko ensure asymmetric access between code with different privilege levels: for example, content code cannot access objects created by chrome code, but chrome code can access objects created by content.

However, even the ability to access content objects may pose a security risk to chrome code. JavaScript is a highly extensible language. Scripts running in web pages can add additional attributes (also known as expando attributes) to DOM objects, and even redefine standard DOM objects to perform unexpected operations. If chrome code relies on such modified objects, it can be tricked into doing things it shouldn’t.

For example: window.confirm () is a DOM API that should let users confirm an action and return a boolean value based on whether they click “OK” or “Cancel”. Web pages can redefine it to return true:

Any privileged code that calls this function and expects its result to represent user confirmation will be tricked. Of course, this is very naive, but accessing content objects from chrome can cause security issues in a more nuanced way.

This is the problem that Xray aims to solve. When a script accesses an object using Xray, it will only see the native version of that object. Any expandos are not visible, and if any properties of the object have been redefined, it will see the original implementation, not the redefined version.

Therefore, in the example above, the chrome code that calls window.confirm () of the content will get the original version of Confirm (), not the redefined version.

Privileged code automatically gets Xray when accessing objects belonging to less privileged code. Therefore, when chrome code accesses content objects, it will see them through Xray.

1
2
3
// chrome code
var transfer = gBrowser.contentWindow.confirm("Transfer all my money?");
// calls the native implementation

Prohibit Xray

Xray is a security heuristic designed to make the most common operations on untrusted objects simple and safe. However, some operations are too restrictive on them: for example, if you need to see expandos on DOM objects. In this case, you can give up Xray protection, but then you will no longer rely on any properties or functions that are being or are being executed. Any of them, even setters and getters, may have been redefined by untrusted code.

To discard the Xray of an object, you can use Components.utils.waiveXrays (object), or you can use the wrappedJSObject property of the object

1
2
3
4
5
6
7
8
// chrome code
var waivedWindow = Components.utils.waiveXrays(gBrowser.contentWindow);
var transfer = waivedWindow.confirm("Transfer all my money?");
// calls the redefined implementation
// chrome code
var waivedWindow = gBrowser.contentWindow.wrappedJSObject;
var transfer = waivedWindow.confirm("Transfer all my money?");
// calls the redefined implementation

The exemption is transitive: therefore, if you discard the Xray of an object, all its object properties are automatically discarded. For example, window.wrappedJSObject.document lets you discard the doc version.

To undo the waiver again, call Components.utils.unwaiveXrays (waivedObject):

Xray

The main use of Xray is for DOM objects: objects that represent parts of a web page.

In Gecko, DOM objects have a dual representation: the canonical representation is in C ++, and in order to reflect the advantages of JavaScript code, this is reflected in JavaScript. Any modifications to these objects (such as adding expandos or redefining standard properties) are retained in JavaScript reflection and do not affect the C ++ representation.

Dual representation enables an elegant implementation of Xrays: Xray simply accesses the C ++ representation of the original object directly, and does not involve JavaScript reflection of the content at all. Instead of completely filtering out changes made to the content, Xray shorts the content completely.

This also makes the semantics of Xrays for DOM objects clearer: they are the same as the DOM specification because it is defined using WebIDL, and WebIDL also defines a C ++ representation.

Xray

Until recently, built-in JavaScript objects that do not belong to the DOM (such as Date, Error, and Object) could not obtain Xrays when accessed through more privileged code.

In most cases, this is not a problem: the main problem Xrays solve is handling untrusted web content manipulation objects, and web content usually works with DOM objects. For example, if the content code creates a new Date object, it is usually created as a property of the DOM object, which is then filtered out by the DOM Xray.

1
2
3
4
5
6
7
8
9
10
11
// content code

// redefine Date.getFullYear()
Date.prototype.getFullYear = function() {return 1000};
var date = new Date();
// chrome code

// contentWindow is an Xray, and date is an expando on contentWindow
// so date is filtered out
gBrowser.contentWindow.date.getFullYear()
// -> TypeError: gBrowser.contentWindow.date is undefined

Extensions

Extensions are made of different, but cohesive, components. Components can include background scripts, content scripts, an options page, UI elements and various logic files. Extension components are created with web development technologies: HTML, CSS, and JavaScript. An extension’s components will depend on its functionality and may not require every option

Architecture

An extension’s architecture will depend on its functionality, but many robust extensions will include multiple components:

Manifest

https://developer.chrome.com/extensions/manifest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"name": "Getting Started Example",
"version": "1.0",
"description": "Build an Extension!",
"permissions": ["activeTab", "declarativeContent", "storage"],
"options_page": "options.html",
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [],
"page_action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/get_started16.png",
"32": "images/get_started32.png",
"48": "images/get_started48.png",
"128": "images/get_started128.png"
}
},
"icons": {
"16": "images/get_started16.png",
"32": "images/get_started32.png",
"48": "images/get_started48.png",
"128": "images/get_started128.png"
},
"manifest_version": 2
}

Content

** Content script ** is a part of your extension that runs in a specific webpage environment (and ** is not a backend script **, which is part of the extension, nor is it a script loaded by the webpage using ‘< script >’, the script loaded by ‘< script >’ is part of the webpage).

** Background scripts can access all WebExtension JavaScript APIs, but they cannot directly access the content of the webpage **, so if you need Content Scripts to do this.

Just like a normal web page loading script, Content Scripts can read and modify page content using standard DOM APIS.

Content Scripts can only access a small subset of WebExtension APIS, but they can use the communication system to communicate with backend scripts to indirectly access WebExtension APIS.

DOM access

Content scripts can access and modify the DOM of the page, just like regular page scripts. They can also detect any changes made to the page by the page script.

However, content scripts get a “clean DOM view”, which means:

  • content scripts cannot see javascript variables defined by page scripts.
    If a page script redefines a DOM built-in property, content scripts will retrieve the original version of the property instead of the redefined version.

At Gecko, this behavior is called X-ray vision (Xray).

For example, consider a webpage as follows:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>

<body>
<script src="page-scripts/page-script.js"></script>
</body>
</html>

The script file “page-script.js” is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// page-script.js

// add a new element to the DOM
var p = document.createElement("p");
p.textContent = "This paragraph was added by a page script.";
p.setAttribute("id", "page-script-para");
document.body.appendChild(p);

// define a new property on the window
window.foo = "This global variable was added by a page script";

// redefine the built-in window.confirm() function
window.confirm = function() {
alert("The page script has also redefined 'confirm'");
}

Now an extension inserts a content script as follows:

1
2
3
4
5
6
7
8
9
10
11
// content-script.js

// can access and modify the DOM
var pageScriptPara = document.getElementById("page-script-para");
pageScriptPara.style.backgroundColor = "blue";

// can't see page-script-added properties
console.log(window.foo); // undefined

// sees the original form of redefined properties
window.confirm("Are you sure?"); // calls the original window.confirm()

The opposite is also true: Page scripts are not aware of JavaScript properties added via content scripts.

This means that content scripts can ** rely on DOM properties to obtain predictable behavior **

One consequence of this behavior is that content script ** cannot retrieve any Javascript libraries loaded through the page. Therefore, if the page contains JQuery, content script will not care about it.

If a content script wants to use a Javascript library, the library itself must be inserted next to the content script like a content script:

1
2
3
4
5
6
"content_scripts": [
{
"matches": ["*://*.mozilla.org/*"],
"js": ["jquery.js", "content-script.js"]
}
]
WebExtensions

In addition to the standard DOM APIS, content scripts can use the following WebExtension APIS:

From extension:

  • getURL()
  • inIncognitoContext

From runtime:

  • connect()
  • getManifest()
  • getURL()
  • onConnect
  • onMessage
  • sendMessage()

From i18n:

  • getMessage()
  • getAcceptLanguages()
  • getUILanguage()
  • detectLanguage()

All’storage '.

Reference link:

https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Xray_vision

https://stackoverflow.com/questions/10824697/why-is-window-and-unsafewindow-not-the-same-from-a-userscript-as-from-a-scrip

https://developer.chrome.com/extensions/content_scripts#execution-environment

https://developer.chrome.com/extensions

https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/Content_scripts

https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM