Open Bug 1424176 Opened 7 years ago Updated 2 years ago

"document_start" hook on child frames should fire before control is returned to the parent frame

Categories

(WebExtensions :: General, defect, P3)

57 Branch
defect

Tracking

(Not tracked)

People

(Reporter: psnyde2, Unassigned)

References

(Depends on 1 open bug)

Details

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0
Build ID: 20171128222554

Steps to reproduce:

When adding a reference to a child frame to a parent frame, the "document_start" hook should fire before control is returned to the parent frame.

Many extensions use the "document_start" hook to perform DOM modifications for security and privacy preserving reasons (e.g. removing or interposing on references to methods associated with finger printing, like HTMLCanvasElement.prototype.toDataURL).  PrivacyBadger is one of many such extensions.

However, the protections these extensions offer can be trivially circumvented by injecting a child frame into a document, and then extracting references to the modified features from the child frame.

The below example uses injecting an iframe, but similar tricks can be done with frame elements, or window.open (if the user has given the page permission to open popups).  There are likely other such examples.

The attack is possible because the injecting / parent frame is able to access the DOM of the child frame before the document_start has triggered.  It would be better if control was not returned to the parent frame until the document_start hook had fired in the child frame.  Otherwise, it makes it impossible (as far as I can tell) for extensions to provide users with the kinds of security and privacy improvements users desire (given the popularity of such extensions).

iFrame Example
===

content_script.js (configured to fire at run_at = document_start, all_frames = true)
---
window.HTMLCanvasElement.prototype.toDataURL = () => "blocked";

page script.js
---
const canvasElm = document.createElement("canvas");
canvasElm.toDataURL() === "blocked"; // Success

const iframeElm = document.createElement("iframe");
iframeElm.src = "//" + window.location.host;
document.body.appendChild(iframeElm);
const toDataUrlRef = iframeElm.contentWindow.HTMLCanvasElement.prototype.toDataURL;
toDataUrlRef.call(canvas) !== "blocked"; // Fail


Actual results:

The above `toDataUrlRef.call(canvas)` gives the parent frame access to the unmodified method (ie a data URL string is returned)


Expected results:

"blocked" should have been returned, since the extension (attempts to) replace the HTMLCanvasElement.prototype.toDataURL in every frame.
Component: Untriaged → WebExtensions: Untriaged
Product: Firefox → Toolkit
Status: UNCONFIRMED → NEW
Ever confirmed: true
Priority: -- → P3
Product: Toolkit → WebExtensions
Bulk move of bugs per https://bugzilla.mozilla.org/show_bug.cgi?id=1483958
Component: Untriaged → General
Depends on: 1486036
Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.