Covert channels in da soup in the SOP

tl;dr In computer security, a covert channel is a mechanism that allows an attacker to transfer information between objects or processes that were not supposed to be allowed to communicate. In the context of browsers, I specifically refer to the procedures that can be used to transmit information across windows on different domains. Below there are several examples of this technique, that in some case can arrrive to be exploited for leaking information via side channel attacks.


In this website I pretend to list and describe all the possible covert-channels that could be used in a cross-domain communication between windos. Possibly these are well known things (the, location.hash and scrollbar), but I've never heard of them as an all, neither seen this kind of use and demos.

Implementing some of them is a bit tricky, but I got some practice with this sop-error-based paradigm. Even so the code is not too clean, but the intersting things are the concepts.

I will also want to try to categorize them at some point, so these are some possible properties to keep in mind:

Based on these parameters the following summary table has been constructed:

Channel Relation Dependency Rate SIO Logical Very fast
location.hash SIO Logical Very fast
history.length SI Logical Medium
scrollbar IO Logical Medium
window.frames.length IO Logical Fast

By now I'm just focusing on the logical ones. So below there is a more detailed explanation of each channel, a probe-of-concept and if necessary some comments about discrepancies between browsers.

Also note that all these PoCs would be much less practical if cross-domain-error exceptions weren't catcheable. Though this solution would probably break too many things...

Don't doubt to tell me if you find any inaccuracy or erratas :)

Test cases

Originally designed [1] for setting targets for hyperlinks and forms, but it has been used in some frameworks for providing cross-domain communication. For that reason is not really "covert", but it serves as historical example. View more... PoC

This channel can be used in both ways, just sharing a window between sender and receiver, and redirecting the page for sending a message. By using history.back() and history.forward() the network requests can be avoided and the speed increased.

The shared resource can be the main window itself, an iframe or an opened tab.

The maximum length we can transmit is the length of a JS string, what is not fixed and depends on the machine.

Maximum speed? Use setInterval and try/catch for testing the SOP and detect the redirection.

With very long variables the browsers starts hanging, therefore we have to find a balance between message length and transmission speed.


Location hash or fragment identifier was thought to allow navigation towards a subresource in a document. The information in the URI fragment is not sent to the server, and after changing the page won't be reloaded. View more... PoC

This is used in one direction, though it can be duplicated in order to have a bidirectional channel.

The event onhashchange will be triggered every time a new message is received, bringing an asynchronous messaging system, reliable and fast.

How much information can be passed? Again it dependes between browsers, but while testing the boundaries I found 2 bugs: a crash [2] in Firefox, and a weird behaviour in Chrome [3] that makes part of the URL to disappear.


history.length read-only property returns the number of elements in the session history [4], including the currently loaded page. It is maintained during navigation (and shared between iframes) and therefore shared across domains. By modifying this value and mapping it to a byte of information it is possible to create a communication channel.View more... PoC, PoC 2*

I have implemented a communication protocol between frames as demonstration, but we could use it in other ways. Unfortunately, there is a race condition in my example limiting the performance. Probably due to the asynchrony of the history event loop.


  1. The sender emits one character increasing the history.length by N
  2. The receiver is polling the object until it stops growing and reads the value N
  3. The receiver resets the history.length (going back and pushing a new location)
  4. The sender detects the reset and go to 1 for sending the next character or finish the transmision.

The maximum history.length is 50. An the update-value operation is asynchronous after performing some redirection, though it depends on the way it is done (setting location.hash seems to be synchronous, but not setting location). Maybe using other system avoids the race condition and the transmission rate can be increased.

A part from this covert channel, it is possible to abuse this to leak the user's navigation history by probing, as I did some time ago in this probe of concept. I'm still trying to improve the timing constraints to check pages faster, but by now I haven't found a good solution.

*Edit: Ben Hayak suggested to use onpopstate to fix the bug, and now it's actually much more reliable, thanks :)

Iframe's scrollbar position

Modifying location.hash does not only trigger the onhashchange event, but also sets the focus of an element with an equal identifier and potentially moves the scrollbar to its position. In this way, we could stablish a channel by navigating to different elements of a cross-domain document.View more... PoC

The example is pretty simple and probably uninteresting, but this behaviour has been abused in the real world, in combination with a CSS scroll bar detection trick, in order to probe elements of a cross-domain page and, for example, detect logged users.

In summary, some browsers allow to set the scroll bar brackground via CSS, the trick consists on requesting an image from the attacker domain which will set a cookie. After that we can inspect the document.cookie to check if the scroll bar was requested.

As well, there is a much more sofisticated (and tricky) attack by Eduardo Vela [5], where he tries to leak the content of the pages abusing this same principle and being very clever.


Similar to the history object, the window.frames is shared cross-domain and allows us to inspect its length (shorlink via window.length), as well as navigate across iframes. This channel creates N frames in a web page, allowing the receiver to read that value.View more... PoC

Apparently, the maximum number of iframes in Chrome is 1000. Firefox does not seem to limit it, but bigger numbers degredate the performance too much. In any case using a dictionary again with values arround 50 works fine enought.

The protocol uses an iframe as referee to detect when a message has been send/read:

  1. Receiver loads and stores sender.length as init value
  2. Sender waits the referee, creates N iframes and switch referee
  3. Receiver waits for referee, reads sender.length and gets N as sender.length-init
  4. If not EOF, go to 2

Notice a little degredation when sending values with bigger N, this could be improved using a frequency-ordered alphabet.

Deletion of cross-domain-referenceable properties (Only chrome. Bug? [11])

Modern browsers provide a few public functions accessible cross-domainly: postMessage, blur, focus, close. Thought they can not be modified, Chrome allows to delete them, throwing an error in cross-domain pages when accessed after deletion or not doing it in the other case.View more... PoC

Additionally to these functions, some other cross-readable properties can be deleted: parent, opener, length, frames, closed.

This gives us uppon 9 bits (1 byte + EOF) of information to transmit between windows.

The main limitation is that after deletion we can not restore the original function until refreshing the sender page, however we can easily maintain the execution trace between refreshes using the location, document.cookie, localStorage or any other persistent mechanism.

This demo is a little bogus, but serves well enough as example.

It also works for window.__proto__.toString.

Symbol.toStringTag (on Chrome with --enable-javascript-harmony

Symbol is a new ES6 API [10] for creating immutable data types. It contains a special property called Symbol.toStringTag which is used for the default description of an object (when the Object.prototype.toString() is called). Since the symbols are available even cross domain, is it possible to use it as a channel.View more... PoC

Actually, use the toStringTag property is the more direct way. But if the symbols table is shared across domain, it would be also possible to test which keys are used in the parent, for example, and therefore to send information.

Special thanks to Michal Bentkowski for the reference :)

Side channels

The Same Origin Policy protects the content in one domain from being read by another one, however, the examples above show us that there are some "shared" objects that actually don't follow this restriction. Side-channel attacks aim to exploit this fact: that two isolated processes share some resource (variable, CPU, memory, atmosphere, etc.) and this resource, if observed, can give some information about the other process.

This kind of attacks have been extensively studied in multiple contexts, especially in cryptography, but there is not too much work [6] on the browsers land, at least from an application security point of view (there are many papers on privacy issues with side-channel timing attacks on the web). Anyway, browsers have grown in complexity, most of the applications these days are web pages and all of them (including malicious ones) run on and share, despite the SOP, the same resources. This and the ubiquitous of Javascript motivates, in my opinion, the study of side-channels in browsers.

Some examples of these attacks starting to move to the web are "The Spy in the Sandbox" [7] (attacking the host's cache with Javascript from the browser) or the implementation of RowhammerJS [8] (flipping arbitrary DRAM bits also from the browser).

In [9] you can see a blog post, from Eduardo again, explaining the problem and showing some very nice examples (it's awesome). I would love to see if those timing attacks against Javascript applications, a well known issue, are really practical.

External references

  1. [1]
  2. [2]
  3. [3]
  4. [4]
  5. [5]
  6. [6]
  7. [7]
  8. [8]
  9. [9]
  10. [10]
  11. [11]