Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: test access to host context, window.[top|parent], window.frameElement.ownerDocument.defaultView #1

Open
danielweck opened this issue Jun 1, 2020 · 3 comments
Labels
enhancement New feature or request

Comments

@danielweck
Copy link

Examples of how a malicious ebook could hijack the context that hosts publication documents' frames:

window.parent.document.body.innerHTML = "<a href="http://evil.com">click me</a>";

window.frameElement.ownerDocument.defaultView.location = "http://evil.com";

window.top.document.querySelectorAll("a").forEach((el) => {el.setAttribute("href", "http://evil.com")} );

(a real-world use case would of course be more sophisticated, for example storing/exploiting data in localStorage, installing persistent behaviours like keyboard event listeners, etc.)

So, my suggestion is to add tests in https://github.com/johnfactotum/epub-test/blob/master/epub-test/EPUB/section1.xhtml such as: making sure that window.[top|parent] is not equal to window / window.self, and then see if the host context can be hijacked by injecting "foreign" content (as per the trivial examples above, or something that fits better with the existing testing pattern / methodology).

Note that reading systems can effectively protect themselves against this kind of attack by using proper iframe sandboxing in addition to setting up adequate Content Security Policy, however overriding window.parent = window.self; window.top = window.self; usually doesn't work because the properties are read-only, and also because window.frameElement.ownerDocument.defaultView remains a totally open attack vector anyway.

@johnfactotum johnfactotum added the enhancement New feature or request label Jun 1, 2020
@johnfactotum
Copy link
Owner

Thanks for the suggestion! Very good points.

Note that reading systems can effectively protect themselves against this kind of attack by using proper iframe sandboxing in addition to setting up adequate Content Security Policy

But with sandbox, usually the parent would still want direct access to the child to, e.g., apply custom styles, etc., but granting that access with allow-same-origin means the child would also have access to the parent if allow-script is also set, and there's no good way to prevent that, since, as you mentioned, overriding the window properties is ineffective.

@danielweck
Copy link
Author

Yes indeed.

Advance warning: sorry for the long blurb below, but I've just discovered your work at https://github.com/johnfactotum/foliate (which is great!), so I thought you'd be interested in the nitty-gritty details :)

So, from experience, I know that Reading System "engines" like EPUB.js, Readium, etc. can be integrated in native apps (typically iOS and Android, but also Linux / Windows / Mac desktop application wrappers), in which case a native webview is typically used to embed the actual Reading System's runtime and its associated iframe(s).

When developing for Readium (several years ago now), we found that the file:// URL scheme granted "special" privileges to the host, in contrast with the guest http:// frames which had blocked access to their parent context (i.e. publication resources served via a localhost / 127.0.0.1 HTTP server on any random port) ... but this really was an undocumented hack.

More recently, we used Electron APIs to solve this problem, notably a subsystem to handle custom URL protocols, with IPC to communicate across the isolated process boundaries, and Electron's webview to inject high-privilege preload scripts whilst maintaining the strict sandbox. Naturally, these techniques do not translate easily (if at all) to the open web platform, or other integration frameworks.

Ideally the Reading System would exchange information with iframe-hosted documents only via asynchronous postMessage events, a technique which requires publication documents to contain some sort of initial communcation "boostrapper", i.e. a dedicated runtime injected early-on by the Reading System. Native webview APIs usually provide executeJavascript()-style APIs, so that's an option as well.

We experimented with this on the "server side" (either a proper HTTP server running in a local app, or a custom URL protocol handler), via Service Workers on the client side (which requires HTTPS and imposes lifecycle and scope constraints), or by pre-parsing + pre-processing Blob URLs on the client side (which is computationally expensive, and breaks video/audio streaming). Needless to say, RS-injection of scripts and styles works best when performed on the "server side", where arbitrary pre-processing of publication resources can be done upstream / ahead of time, i.e. before HTML documents actually reach content iframes.

Finally, there is one additional aspect related to security: when publication resources are served from the same web origin (i.e. same localhost IP + HTTP port), they inherit identical privileges / credentials to origin-scoped APIs such as localStorage / indexedDB etc. In concrete terms, this means that publication A can gain access to the data stored by publication B, and vice-versa.
This can be quite problematic in cases where - for example - fixed layout publications generated by the same authoring system (e.g. inDesign + plugins) manipulate the exact same uniquely-named keys in a key-value store (for example, to save the reader-supplied nickname, or some reading / read aloud preference, or analytics information related to reading progress / velocity, etc.).
A corollary issue is that if the port is random in the IP+port origin, then the data saved by scripted publications is lost due to the changed origin. Some Reading Systems may in fact completely flush local storage sessions at each start ... which would also be problematic to publications that rely on some form of persistent data.
Once again we were able to solve this in our Electron implementation ... but other framework integrations probably require different approaches.

@johnfactotum
Copy link
Owner

Thanks for the detailed reply. What I've done recently in Foliate is to add sandbox to the CSP with the <meta> element on the parent, rather than setting the attribute on the iframe. Strangely, even though the sandbox directive isn't supported when set with <meta> (it even shows an error in the console), the sandbox does work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants