17 Feb 2013

Cross Origin Madness or Your Frames Are Belong to Us

TL;DR: You have access to internal frames of any (frameable) website. And you can change their locations. And internal frames of internal frames too: frames[x].frames[y].frames[z].
Cross domain receivers, "like" buttons, ads etc. And read/write data in window.name (popular trick for cross domain interactions) and postMessage
Don't trust your subframes, cause they are belong to us.
Vulnerable: IE, Firefox, Chrome, Safari etc. Well, it's a vulnerability in standard.

Madness: The beginning

A week ago @isciurus demonstrated me interesting behavior.
lets open EvilOrigin with iframes:
-Origin2/frame1
-Origin2/frame2
we can reach frame2 from frame1 calling
parent.frames.frame2
This behavior raised a question in my head: What the hell? Why we can do it  through different origin.

Investigation

I fired up console and got some truly interesting results:
>for(var i in window){ if(frames[0][i]){console.log(i)}}

top
window
location
parent
frames (array of internal page frames. why?)
self
length (amount of frames on the page, frames.length. why?)
history (history object with go/forward/back methods. why?)
postMessage (we can post messages to any origin)
close (used to close windows)
blur
focus
opener (we can get the whole chain: opener.opener.opener.opener..why?)

So let's look at why-s we got.
opener case looks rather strange than malicious. Why should we have access to opener of opener of our opener? Alright, it's minor.

history, this is worse one. with go/forward/back methods you can control history created not by your origin, like a puppet. Yep, you cannot see URLs of course, but you can do stupid things like double form posting and messing with navigation.

length and frames – these are the most interesting attributes. We cannot access <form>s, <div>s or javascript methods of different origins. But we can access internal frames. And change their locations if our origin is parent to them (won't work if target is opener or window.open(target), only with frames)

Attack Vectors


Vector 1.
If i can execute any [a-z.] function on origin2 i can use it as CSRF:
origin2/execute?callback=parent.frames.otherorigin2.forms.csrf_form.submit

Vector 2. 
We can just deface any frame inside of this website. You might say: the site is already frameable and URL in top bar is different, but putting our content in internal frames look way more trustful.

Vector 3. 
window.name is a common way for cross domain data sharing. Origin2 sets own window.name, redirects back to Origin1 domain and parent now can read its name and receive data. Problem is Origin1 fully trusts in Origin2 because frame was created by Origin1. We can both read/write it by redirecting iframe to:

data:text/html,<script>alert('Stolen name'+window.name);window.name='hacked';location.replace('about:blank');</script>

origin1 url or about:blank are same origin with its parent, it reads frames[0].name equal "hacked".

Vector 4. 
postMessage accepts two arguments: data and receiver origin. When your window receives onmessage event it should always check event.origin.
But when you created <iframe id=my_frame src=my_src> and do my_frame.postMessage are you sure in my_src? You are! It's you who created the iframe, your src attribute, nothing to be afraid of huh? You possibly used my_frame.postMessage('secret', '*') and since you can replace locations for your internal frames we can hijack your data:

data:text/html,<script>window.onmessage=function(e){alert(e.data);}</script>



Proofs of concept:

1) frame pwner http://homakov.github.com/pwn_frame.html simply replaces locations of internal frames to homakov.blogspot.com, you can set different pwn_url value in javascript though.
2) sinatra app demonstrating some vectors

Conclusion

despite of attack restrictions(only frameable websites) it opens a lot of vectors since the website trusts its internal iframes, it shares information with it and receives it back. Because they are supposed to be created by this website, not by our Malicious parent origin.

Ideally .frames attribute should not be available for cross domain interaction. Restricting .location assignment for internal frames looks good to me too though. You should only be able to control your direct children locations.

I reported it to Chrome security team and they replied that problem is kind of  known, it was even worse back in 2007. Really interesting paper on this topic: Protecting Browsers from Frame Hijacking Attacks