Script Gadgets! Google Docs XSS Vulnerability Walkthrough

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
In this video I want to show you a really really interesting Cross-site scripting vulnerability in google docs. This was reported to Google in May by the bug bounty hunter Nikolay and was rewarded $4133.70. Nikolay found this issue during the time he received a Google VRP grant, which is Google paying money for his work, even if he finds nothing. But if he finds something, like this issue, he gets the bounty too. And in this video we will not only look at the bug to see how it works, but hear from Nikolay how he found it AND I even got a chance to talk to Google engineers to learn their side of the story “why did this vulnerability exist in the first place?”. I think this is awesome and this is only possible thanks to Google sponsoring this video. Okay, let’s start with the bug report from Nikolay and click on his proof of concept URL. On this website we can see an iframe embedding a google spreadsheet, and just a short moment later, we see an alert popup showing the affected origin “docs.google.com”. So let’s have a look at the reproduction steps. How can we do the XSS ourselves. First we have to create a spreadsheet and add some data to be used by an annotation chart. And then add that chart. When you make changes to this chart and look at the requests sent by google docs, you can find a POST request to SAVE the settings of this chart referenced by this CHART_ID. And there is this big JSON blob which also contains options for this chart. Nikolay now wants us to modify this request and inject the following range option, which specifies a ui.type and set it to hlc. We forward the request, and the first important step is done. This is very weird, why do we do that? We will get to that in just a moment but first let’s follow the remaining steps. Next we have to share the spreadsheet, get the URL of it and create a malicious website that iframes this document. And then add code to our website, to execute a postMessage call to this iframe, specifying a URL that points to a javascript with an alert(). okay? If you don’t know what postMessage does, “it enables cross-origin communication between Window objects; e.g., [..] between a page and an iframe embedded within it.” Because of the historical issues of javascript accessing other websites, and the introduction of the same origin policy, there exists now the postMessage API that allows you to exchange messages with other websites. But those websites then also need a postMessage listener to react to it. This way you can have safe communication between different sites. But apparently the postMessage handler from google docs is dangerous, because it takes a link to some javascript, and executes it. This malicious website can simply ask Google docs to execute any script! That is insane?! But obviously google docs doesn’t do this by default, that would be very bad. And that’s where the super confusing ui.type set to hlc comes into play. Check this out, it gets crazy. If you look into the waffle-js-prod-code javascript sources and search for hlc, you can find an hlc() function, which indeed adds an on message event listener, where it takes a URL parameter, and simply creates a script element, with this URL as the source and then appends the script to the document body, which will trigger the execution of this script. Okay, well, this still makes no sense, because: yes, we have a function that does register this dangerous postMessage handler, but how is this hlc() function even executed? And this goes into what gviz, the graph visualization library from google spreadsheet, does with the ui.type option. Deep inside the library there is a function which handles this ui.type option. It gets the specified ui.type “hlc” as a string and looks for an actual existing function, object, variable, or whatever, and when it is found it calls new on it. So it expects the ui.type option to specify a name of a constructor for some kind of ui.type class. But Nikolay realized that you can make it instantiate arbitrary other objects, and he found this hlc() function, which gets executed when used here as a constructor. WHAT?! So to summarise: by specifying “hlc” as a ui.type in the options, Nikolay can force google docs to execute this hlc() function, which registers a postmessage handler that takes any url and creates a script tag of it. When then embedding the spreadsheet into his own website, it allows him to inject an arbitrary script into google docs. Leading to this Cross-Site Scripting issue. WOW! Okay. so how the XSS is executed is clear now. But now I have even more questions. How did Nikolay find this ui.type option and that it gets executed as an arbitrary constructor? How did he find the weird hlc() function? Why does this hlc() function even exist, what is the legit use of this? So many questions… And I’m glad I could talk to Nikolay and Googlers to answer them. So let’s meet the legend - Nikolay. Findings like this one here rarely come out of the blue. This is clearly a very involved exploit and there is probably prior experience and history to this. So let’s hear about Nikolay’s previous work. “I started to test the gviz library, so google visualization API, and I just take care of all the points of integration within like google systems. Spreadsheet, former fusion tables, data studio, public data explorer, they also use this gviz. And I reported actually a lot of bugs in terms of library itself and some bugs were also related to integration. So library itself doesn’t contain it, but taking into account the way integration is made, it’s possible to exploit as an XSS. Also all the issues were XSS only.” This is very important. He knows that gviz is a javascript library used across many google products to draw all kinds of charts, and so he is somewhat specialized on that. He also noted that there are generally two classes of issues he finds. Issues directly in the library itself, which could affect all uses of it. And there are issues of, what he calls, “integration”, where the library is maybe used in an insecure way. Also all of the issues are XSS issues, but that’s no surprise, gviz is a frontend drawing library. What else would it be… Anyway. Let’s have a small detour and look at a different, but related XSS issue that Nikolay found in the past. On some site that used gviz, he found a way to set arbitrary chartTypes in the config. Then when I had a call with Google Security engineers, they showed me this ChartWrapper example. Which only now during making of this video I realize, is exactly what Nikolay was talking about. Here is the chartType. And you can see that the chartType is a string. The googlers then told me that gviz internally calls the google closure library function getObjectByName. So this resolves the string to an actual class or object. In this case the ColumnChart class, so that it can then be instantiated. Notice the similarities to ui.type of this new XSS? We saw that the ui.type as a string “hlc” was also resolved to an object and then instantiated with new. Turns out, handling the ui.type and handling the chartType, results in the same code path. Back then he realized, if he can control the chartType string, he can pass an arbitrary object path and it will be resolved and executed. Some people call this a javascript gadget. This is not directly XSS but it’s a gadget that allows you to basically call some other existing function. So now you have to think, can I execute something that gives me even more power. And he found a very interesting function called “drawFromUrl()” “So if this method is called, the javascript uses the json parameter from the address bar from GET parameter. And it is used as a JSON array for a ChartWrapper. So if I call this method, I can provide the GET parameter JSON and say chartType is table, data source URL is that stuff, view, whatever.” So being able to set an arbitrary chartType and pointing it at this weird drawFromUrl() function, which will eventually execute, allows him to create a NEW CHART, with options he fully controls, based on a GET parameter. And now I’m not sure, but I think you can just select options that basically create a chart with some HTML rendered parts and trigger a XSS through that. This has a lot of similarities with this XSS issue right? But this happened in the past. And this obviously was fixed. “I reported this issue. This issue was accepted, so I get the bounty, then this issue was fixed. I reported one more issue how to get rid of this fix, it was fixed again. And the last fix that was applied, that there is only a whitelist of allowed chartTypes or names that can be supplied as a value of chartType parameter. That fix was applied, as far as I remember, let’s say two years ago, 1 ½ year ago. But recently, before I found this bug we are talking about, due to some reason I don’t know, maybe there were no regression testing or some piece of old code was merged with the new branch, I don’t know this. I noticed that, currently now, it is allowed one more time. It was allowed in the past, to provide any string you want. There were no checks and whitelist, for this case, the option ui.type” So he found that old XSS via the ChartType. It got fixed. A whitelist was introduced. And this is where we come back to our current XSS, triggered by the ui.type. Because this whitelist got removed. So of course I wanted to know from the Google Engineers, WHY? What happened? They jokingly told me that the SECURITY TEAM was actually responsible for this. This was not a regular engineer. The original whitelist fix was tied to a safeMode flag that you can set for your chart. When turned on it sanitizes any HTML rendering and also restricts the calling of arbitrary other constructors through for example chartType. But then recently a google security engineer came in and wanted to restructure this code by moving this flag away from the local setting PER CHART, and have it be a global setting for when the library is loaded. Basically have a legacy and safe mode for the entire library. And the googler admitted that he forgot to wire this new global flag to all the same places where the local flag was used. Causing this issue to regress and resurface. Okay, so the blurry picture about this bug slowly clears up. But there are still remaining questions. When I first got to read this bug report I was wondering where the ui.type even comes from. Inserting this into the options seems sooo arbitrary. And you can’t simply read the source code, because gviz is not open source. You can use it, and it has a lot of documentation on how to create charts, but the ui.type is mentioned nowhere. And the library is compiled, minified and thus kinda obfuscated and difficult to read. So you might be burning to hear how Nikolay found this ui.type option in the first place. And this is where he went back in time again and explained to me how he found the other issue with the chartType because it led to the ui.type. “I found it just by looking at the deobfuscated source code and looking at the places when like chartType is getted from somewhere and injected to somewhere. I found a lot of such places. Let’s say 20, 30. And go step by step, doing like this reverse engineering and checking from what place? Can I influence it, or is it hardcoded or is it getted from something? And that was the reason actually I found this ui.type. It’s not documented.” So he actually simply used tools like jsbeautifier, or what I learned from a googler jsnice, to read through the compiled code and kinda reverse engineer it. And that’s how he learned about the internals. “Ui.type is actually undocumented option of control behaviours. So if you are creating a control, basically they have nothing to do with annotationChart itself. Because you can just provide chartType or controlType as a real control. I mean, rangeSelector or NumberRangeFilter or StringFilter or so, just the documented controls from the visualization library. And if you are providing this ui.type option it is overriding the controlType.” That is just incredible to me. The dedication and perseverance after many years of already looking into this library and STILL finding new XSS issues. Huge respect. So. It’s clear now how he found the undocumented ui.type option. He analyzed the code path of the controlType option and found that there is the undocumented ui.type that can overwrite it. And we know what it leads to: gviz simply gets the object based on the provided name and interprets it as a constructor calling new on it. Nikolay also told me he reported this once before, but then used the old code-reuse technique with drawFromUrl(). Which was then fixed by removing this gadget function. But that’s not a proper rootcause fix. That just removes one of the usable gadgets. So logical progression is now to ask, WHAT OTHER FUNCTION CAN I CALL?! Here is what nikolay said about that: “Initially, I mean 3-4 years ago when I found the drawFromUrl XSS, I just looked at all methods within google.visualization. Not looking at other stuff. And that issue with drawFromUrl was found using this approach. After it was fixed I just started thinking about what can I call else. If I do not have the drawFromUrl method, what can I do?” And yes, then you just start by going through ALL the available functions, one by one, and look if they are useful to you. And eventually you come across this hlc() function that registers a postMessage that you can abuse to inject an arbitrary script. Amazing! I think now I understand Nikolay's side of this bug. It’s clear to me how he found it, how he approached it and how he thought about it. But I also had the chance to talk to Google. And so I wanted to know, why the heck does this function even exist?! And it turns out, it’s the fault of Security Engineers AGAIN! Let’s look where hlc is used. It’s actually NEVER CALLED. It’s only used once, here, where it is embedded as a string. This dlc() function creates an iframe, a sandboxed iframe only allowing scripts to be executed, where it embeds this function. This might look weird, but this is actually a security mitigation. This is a so called JSONP sandbox. JSONP is this horrible design idea to make cross origin exchanges work. Basically instead of returning simply JSON, which with default same-origin-policy couldn’t be read by another website, an API endpoint can wrap the data into a function call. Then when the other website wants this data, it simply creates a function of that name, embeds this as a javascript, which is then executed and passes the data to you. So not only is JSONP used to get around the browser same origin policy in a hacky way, because you can often control the function name, thus able to place arbitrary javascript in here, it can be used as a CSP bypass or even many websites create XSS issues through that. So google doesn't want to execute these jsonp scripts on the main page for security reasons. And instead creates a sandboxed iframe where they register an event handler, which they can use to send over the possibly dangerous JSONP endpoint, it gets executed, and the iframe can respond back with the data. If something evil happened in this iframe, it doesn’t affect the site around it. It’s a very clever security mitigation. Unfortunately it also created a gadget function which Nikolay could re-use with his first gadget, the arbitrary object creation. Resulting in XSS. So google fixed gviz by using a proper whitelist again and also added an origin check for the postMessages in this gadget function, so it can’t be abused in this way anymore. Awesome. Before we wrap up this video, I also asked Nikolay about his background because I think it’s always interesting where people are coming from. Does he have a lot of developer experience, because he can work through compiled javascript code so well. But... “I do not have like real developer background. So I never was a developer. I have basic understanding of Javascript language, understanding of arrays, methods. But I am not a developer so I do not have very huge domain expertise in development itself. A lot of time ago I was a Quality Assurance engineer. Then manager of software development. But I never was a developer. Quality Assurance, interesting. Especially when you look at his company, This is QA! On here he also writes “Our audits are powered by people, not by automated tools.”. And I think this XSS is the perfect example, because, NO automated SCANNER TOOL would have EVER found this. No chance. It’s so complex and intertwined that you need a human like Nikolay to review that. What an amazing XSS. And what I love especially about it, is, that it’s all the Google Security Team’s fault.
Info
Channel: LiveOverflow
Views: 141,455
Rating: undefined out of 5
Keywords: Live Overflow, liveoverflow, hacking tutorial, how to hack, exploit tutorial, google xss, gdocs, google docs, cross site scripting, cross-site scripting, xss, stylesheet, sheets, walkthrough, javascript gadgets, SOP, iframe, DOMxss, postmessage, jsonp sandbox, jsonp, charts, chart api, vulnerability, bug bounty, vrp, bugbounty, bug bounties, vrp grant, thisisqa, thisisqa.com, quality assurance, reverse engineering
Id: aCexqB9qi70
Channel Id: undefined
Length: 18min 57sec (1137 seconds)
Published: Fri Jul 31 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.