Closed
Bug 1474276
Opened 7 years ago
Closed 5 years ago
Service worker can modify content scripts injected scripts
Categories
(Core :: DOM: Service Workers, defect, P5)
Tracking
()
RESOLVED
INVALID
People
(Reporter: doliere.some, Unassigned)
Details
User Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0
Build ID: 20180625102006
Steps to reproduce:
(1) Create a service worker which intercepts HTTP requests for scripts and responds with a custom code. Create a web page which loads embeds the service worker
(2) Create an extension, add a content script which further injects a script in all web pages (a remote script like https://code.jquery.com/jquery-3.3.1.min.js). Load the extension in developer mode.
(3) Load the web page in a new browser tab.
Actual results:
The service worker can intercept and modify scripts injected by an extension content script. This applies only to remote scripts such as https://code.jquery.com/jquery-3.3.1.min.js, and not to web accessible resources injected by content scripts in web pages. The same issue was found on Chrome, where both remote scripts and web accessible resources are routed to service workers.
Expected results:
As an extension is privileged compared to web application, we were expecting that a service worker would not be able to modify scripts (or any other content) injected by an extension content script.
For instance, suppose an Adblocker is injecting a script in the page DOM to remove ads, if the page can modify this script, then it prevents the extension from working properly.
Comment 1•7 years ago
|
||
Reporter, can you clarify how the extension script injects this script in all pages? Perhaps add a testcase add-on that reproduces the issue?
Group: firefox-core-security → dom-core-security
Component: Untriaged → DOM: Service Workers
Flags: needinfo?(doliere.some)
Product: Firefox → Core
Reporter | ||
Comment 2•7 years ago
|
||
Here are the details of my sample extension. It has 3 files
#1. manifest.json: loads a content_script.js as the content script, in any web page.
{
"name": "Content Injector",
"version": "1.0.2",
"manifest_version": 2,
"description": "Inject content in pages",
"content_scripts": [{
"matches": ["http://*/*", "https://*/*"],
"js": ["content_script.js"]
}],
"web_accessible_resources": [
"inject.js"
]
}
#2. content_script.js: it is the content script which further injects inject.js (a web accessible resource) and jquery in the page. Following is its content
console.log("Content Script Successfully Loaded in web page");
function injectScript(url){
var script = document.createElement("script");
script.src = url;
document.body.appendChild(script);
}
chrome = chrome || browser;
injectScript(chrome.runtime.getURL("inject.js"));
injectScript("https://code.jquery.com/jquery-3.3.1.min.js");
#3. inject.js: web accessible resource injected by the content script in the page. It simply displays a message in the console
console.log("inject.js loaded by content script in web page");
#4. Maybe you would like to see the code of the service worker. Here it is:
self.addEventListener('fetch', function(event) {
var creq = event.request.clone(),
newurl = creq.url,
console.log("Service worker intercepted URL ", newurl);
if(newurl.indexOf("chrome-extension://") == 0 || newurl.indexOf("moz-extension://") == 0){
console.log("Web accessible resource ", newurl);
event.respondWith(new Response("alert('substituted extension content with content from the page');"));
}else if(newurl.indexOf("https://code.jquery.com/") == 0){
console.log("JQUERY", newurl);
event.respondWith(new Response('console.log("jquery content substituted by the page content script");'));
}else{
event.respondWith(
fetch(event.request).then(function(response) {
return response;
})
);
}
});
Flags: needinfo?(doliere.some)
Comment 3•7 years ago
|
||
(In reply to Dolière Francis SOME from comment #0)
> As an extension is privileged compared to web application, we were
> expecting that a service worker would not be able to modify scripts (or any
> other content) injected by an extension content script.
> For instance, suppose an Adblocker is injecting a script in the page DOM to
> remove ads, if the page can modify this script, then it prevents the
> extension from working properly.
Hmm. A page could detect any modifications to its DOM using Mutation observers, and then overwrite any effects from running the first script by just adding another one. My understanding is that adblockers tend to work at the request level, rather than the page DOM, and just avoid loading (or running, with CSP) any of the content that adds in the ads to begin with. So I'm not sure that I agree that this is unexpected per se. Add-ons frequently underestimate to what degree pages can hamper their influence over the page, where that influence is limited to changing the page DOM and executing script inside the page itself. This is why we've considered things like bug 1340930. At the same time, given that add-ons make changes desired by their users, pages have generally refrained from explicitly interfering with those add-ons given the expected backlash.
In this particular case, I *suspect* that if we have the right triggering principal, we could avoid the service worker being called for this script, but it's not clear to me that that solves any real security issues. The <script> tag is added into the page DOM, and in that sense it can do everything that a "normal" script tag could do, and the page can do everything with it that it could do with a "normal" script tag... Kris/Blake, am I missing something here?
Flags: needinfo?(mrbkap)
Flags: needinfo?(kmaglione+bmo)
Reporter | ||
Comment 4•7 years ago
|
||
So is there any explanation to why web accessible resources are not routed to service workers as it is the case for other remote content injected by content scripts in web pages ?
Reporter | ||
Comment 5•7 years ago
|
||
I played a little bit with mutation observers (https://developer.mozilla.org/fr/docs/Web/API/MutationObserver). It seems that a mutation observer cannot overwrite the effects of a script injected from a content script. When the mutation observer is notified about the fact that a script is added to the DOM by a content script, it is already too late to prevent it from loading or change its src attribute to a different value for instance.
And once a script is loaded, changing its src attribute or removing it does not remove the objects the script has added to the JavaScript environment of the page.
At this point, these objects can be altered by other scripts in the page, but one can use different techniques (http://2ality.com/2013/08/protecting-objects.html) to protect these objects from modifications.
Last but not least, if the content scripts are run at "document_start", then I guess extension scripts can load first, then protect themselves from the page scripts that will be loaded later.
Service workers however always intercept all content injected by content scripts, even though they get injected first in web pages.
Comment 6•7 years ago
|
||
(In reply to Dolière Francis SOME from comment #5)
> At this point, these objects can be altered by other scripts in the page,
> but one can use different techniques
> (http://2ality.com/2013/08/protecting-objects.html) to protect these objects
> from modifications.
Sure, but a self-contained untouchable JS object doesn't really matter - it won't change anything on the page unless it touches the page/DOM. And in turn, any such modifications to the DOM from the extension script could be overwritten by page script.
> Last but not least, if the content scripts are run at "document_start", then
> I guess extension scripts can load first, then protect themselves from the
> page scripts that will be loaded later.
I'm not sure how they'd protect changes to the DOM, which is world-writable... It'd be a cat/mouse game in fixing up / breaking each other's DOM updates, which the add-on is bound to lose because its update cycle is going to be slower than that of the page.
Anyway, I'll let Kris and Blake chime in, this isn't really my domain.
Updated•7 years ago
|
Group: dom-core-security
Comment 7•7 years ago
|
||
I don't know enough about the WebExtensions API to speak very authoritatively on this. My first reaction, though, is similar to Gijs's. In particular, once you're injecting elements into the page, you're losing a lot of control over what can happen and what the page can do. At that point you're in an arms race with the page which the page is almost certain to win.
It seems like it would be better to (if you have to load the scripts over the wire) do so from a hidden iframe or find some way to load them from the extension itself.
Flags: needinfo?(mrbkap)
Updated•7 years ago
|
Priority: -- → P5
Comment 8•5 years ago
|
||
I assume the reporter found a better solution in the meantime.
Status: UNCONFIRMED → RESOLVED
Closed: 5 years ago
Flags: needinfo?(kmaglione+bmo)
Resolution: --- → INVALID
You need to log in
before you can comment on or make changes to this bug.
Description
•