How to prevent fingerprinting via Add-on UUID

As Firefox on Android does not offer it, I’ve developed an Add-on some time ago which integrates PDF.js for PDF viewing:

The problem is, that I actually inject parts of PDF.js into foreign websites and by doing that, expose my Add-on UUID. As this is “random” for this Add-on installation, at least in theory it allows to identify this exact browser.

What would you recommend to reduce this risk somewhat? Is this actually an issue as only a few people are using my Add-on and probably noone will care about fingerprinting it?

I could think of an webRequest.onBeforeRequest hook combined with webRequest/filterResponseData. This would in theory allow me to fake any random host name as source for my resources and catch it with my filter to deliver the contents. This approach would still allow to know that my Add-on is used, but at least I would no longer have a unique identifier per browser. Another small advantage would be that scripts, delivered this way, are no longer “privileged” in any way (same origin policy applies).

2 Likes

You mean you inject some web-accessible resources in the page HTML?

I would say that using the Shadow DOM should be enough to hide it:

Yes, probably it would be OK to do it that way. But how…

I’m thinking on this for hours now and just can’t find out how to solve it with shadow DOM.

The first time I interrupt the traffic is when the PDF file is loaded. This is on “webRequest.onHeadersReceived”. Here I, so far, placed my filter to stop loading of the actual PDF and injecting my HTML.

So how do I add shadow DOM here? Of course I can’t just add the code into the HTML, I’m injecting. If I did it that way, then the original website can just fetch() my HTML and analyze it again.

But a content script also doesn’t seem to do the trick. A PDF can also be loaded into an “<embed>” and if it is, then the does get frameId of “0” (just like the top page). So I can’t easily load a content script into a page loaded into an <embed>…

Many addon developers are using it because it also prevents CSS styles collision with the target page. Also it’s super simple to use - you just create empty node, attach shadow to it and then append your whole DOM content to the shadow:

const container = document.createElement('div');
const shadow = container.attachShadow({mode: 'closed'});
shadow.appendChild(myNodes);
document.body.appendChild(container);

But where to do it?

I can’t do it in my background script (where I have my webRequest.onHeadersReceived) and I can’t do it in a content script, too, as in my content script I don’t know where to do my modifications as <embed> (and probably <object>) don’t get their own frameId to reference the content loaded there.

I don’t think you can do it in the background script, but from content script for sure.

How do I find the right place in the content script?

What I’m currently doing is interrupting the PDF file load and replacing its content with HTML. This HTML, so far, is the full PDF.js content including some moz-extension:// URLs.

What I’m currently thinking of is just placing the actual files to a server on the internet (probably my own) and adding the “public URL” in my HTML. And to keep my files all in one extension without actually needing internet access (or allowing any kind of external logging) I would interrupt this web load in my extension and handle it locally.

I’ve checked your code and I’m not sure there is actually a problem at all. If you are intercepting the request and replace the result with your custom HTML page, then no one else can see the UUID in the HTML - because there is only your own script running in the page, right?

It is actually really easy to see the UUID. I’ve checked it. All I have to do is load a PDF into an iframe. My Add-on replaces the actual PDF with HTML but as the host keeps the same, the actual page can access the iframe after its content is loaded and easily see my moz-extension:// URLs.

It even works with full page loads. All the page has to manage to do is to use “window.open” and remember its return value to access the opened window (or tab).

I’m a bit confused. Where is the PDF file rendered? Is it in the iframe? Or is it’s own page? I don’t see any iframes in the source code.

And when the PDF is opened, is the original page still loaded? Because if the new page is loaded in the same tab then the old one is not running anymore.

The PDF file is rendered wherever the page wants to display it. For example if a page has an iframe which points directly to a PDF file (which would render just fine with PDF.js on desktop Firefox), then my Add-on “jumps in” and replaces the actual PDF content with the PDF.js viewer. It also replaces the content type of this request to “text/html”.

So all the website code has to do is access the iframe to read the “<head>” section. and find my UUID.

I’m currently on my way of replacing this with links to my own web server. Once this works I’ll add code to the actual Add-on to interrupt this public request and serve it directly from within the Add-on.

I see, however page having iframe with PDF being downloaded inside sounds quite rare.

I don’t think this is allowed on AMO - all addon files and scripts must be bundled within the addon. Even if you are loading just a style or image, by doing so you can be tracking users (IP, user-agent, time, etc…) and you need to have opt-in and inform users about it.
And loading and executing remote scripts is completely forbidden:

Add-ons must be self-contained and not load remote code for execution

As mentioned above: I just use the remote address to fake a real target.

Inside the Add-on I prevent the external server access and serve it directly in the Add-on. I’ll also verify that there is really no access to my server.

And code is already “self contained”. It is easy to include this in the HTML. But it is difficult to do this with, for example, language files. It would be real waste of memory to embed every possible language into the website source.

1 Like

Whoa, that’s insane :smiley:. Great out of box thinking!

Just a good intuition if it gets to silly workarounds for broken Mozilla API.

Unfortunately that’s a skill you have to get good at to make nice stuff for Firefox :frowning:

This is my solution now: https://github.com/M-Reimer/android-pdf-js/blob/master/resource-delivery.js

To actually cancel the load from my server I have to redirect the request and the only valid target is back to the moz-extension URL. A quick web search said “websites can’t easily find out anything about redirects using Javascript”. I hope that’s true.

And as adding my Add-on still makes the browser behave differently I decided to block enabling in incognito mode.

The interior UUID is purposely made per establishment of an expansion, to abstain from fingerprinting, you can see a more extended conversation at https://bugzil.la/1372288.

Notwithstanding, I believe you’re misjudging the motivation behind the Origin header, it lets your web application and program cooperate to forestall a class of cross-web page prearranging assaults. Yet, without anyone else, it doesn’t actually give your web application a solid marker of the beginning of the solicitation (that is, any non-program customer can undoubtedly build an HTTP demand with a subjective worth in the Origin header)