Overview
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 window.name
, 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:
- Relation between sender's and receiver's windows:
SAME
window (after navigation),IFRAME
orOPENER
. - Dependency: logical, browser implementation or host's machine architecture dependant.
- Transmission rate.
- ???
Based on these parameters the following summary table has been constructed:
Channel | Relation | Dependency | Rate |
---|---|---|---|
window.name |
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
window.name
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
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
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.
Protocol:
- The sender emits one character increasing the
history.length
by N - The receiver is polling the object until it stops growing and reads the value N
- The receiver resets the
history.length
(going back and pushing a new location) - 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.
window.frames.length
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:
Receiver
loads and storessender.length
asinit
valueSender
waits thereferee
, creates N iframes and switchreferee
Receiver
waits forreferee
, readssender.length
and gets N assender.length-init
- 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 :)
Main thread's event loop
The event loop of the main thread is a shared resource (between cross-origins parent and iframe) that run JavaScript, creating contention on it can be used to transmit information. View more... 	 PoC