Grab some popcorn and launch Popcorn Time

August 2, 2015

Movie and TV Show Piracy has been going on for years. There are many websites that offer movie streaming online for free, as well as many torrent sites that provide you with links to download and watch anything. However, even pirates want simplicity and friendly user interfaces from time to time. This is where Popcorn Time comes to play. It provides an easy-to-use, Netflix-style UI for people to browse for Movies, TV Series and Anime.

The Easy-To-Use UI of Popcorn Time

It is available across many platforms like Windows, Mac OS X and Linux.

Due to its simplicity, Popcorn Time earned a large amount of users quickly that simply want to enjoy movies and, according to the slogan, only need popcorn.

Let us now see some technical information regarding the application.

The client that runs on every computer achieves its cross-platform-ness since it is written in NodeJS, a framework to create desktop and standalone applications in pure JavaScript. Additionally, it is Open Source and everyone can view the code or modify it.

Now let’s see some of the internals and how it works. When it launches, it connects to an API server to get the status and some other information:

GET / HTTP/1.1
Host: eztvapi.re
Connection: close

And the remote API replies with:

{"status":"online","uptime":1065957,"server":"nl.ptnet"}

Then, another HTTP request is sent:

GET /api/v2/list_movies_pct.json?sort_by=seeds&limit=50&with_rt_ratings=true&page=1&genre=All&lang=en HTTP/1.1
Host: xor.image.yt
User-Agent: Mozilla/5.0 (Linux) AppleWebkit/534.30 (KHTML, like Gecko) PT/3.8.0
accept: application/json
Connection: close

This time, the client requests from the api (v2) to list all the movies, sorted by seeds (which is used to infer popularity), only return the first 50, include ratings and contain movies from All genres in english.

At this point, if you look at the code, you notice that Popcorn Time is doing something really smart to bypass ISP-level blocking (like the one imposed in the U.K.). It uses CloudFlare for the website xor.image.yt and then makes a request to:

http://cloudflare.com/api/v2/list_movies_pct.json?sort_by=seeds&limit=50&with_rt_ratings=true&page=1&genre=All&lang=en

and simply sends the following headers:

Host: xor.image.yt
User-Agent: Mozilla/5.0 (Linux) AppleWebkit/534.30 (KHTML, like Gecko) PT/3.8.0

That means the computer will initiate a connection by resolving in the DNS level cloudflare.com and not xor.image.yt. The ISP cannot block it via DNS without banning the CloudFlare website.

Later on, when CloudFlare receives the request, it checks for the Host header to redirect the traffic to the Popcorn Time API server. If an ISP wanted to ban that API, it would require Deep Packet Inspection to check in the HTTP Protocol Level every host. This is far more expensive than DNS blocking.

The response it receives from the server is something like this:

{
    "status": "ok",
    "status_message": "Query was successful",
    "data": {
        "movie_count": 4308,
        "limit": 50,
        "page_number": 1,
        "movies": [{
            "id": 4332,
            "imdb_code": "tt2967224",
            "title": "Hot Pursuit",
            "title_english": "Hot Pursuit",
            "year": 2015,
            "rating": 4.9,
            "trending_score": 13,
            "runtime": 87,
            "genres": ["Action", "Comedy"],
            "language": "English",
            "mpa_rating": "PG-13",
            "yt_trailer_code": "fUeOBdxSjc8",
            "description_full": "An uptight and by-the-book cop tries to protect the outgoing widow of a drug boss as they race through Texas pursued by crooked cops and murderous gunmen.",
            "background_image": "http:\/\/s.ynet.io\/assets\/images\/movies\/hot_pursuit_2015\/background.jpg",
            "background_image_original": "http:\/\/s.ynet.io\/assets\/images\/movies\/hot_pursuit_2015\/background_original.jpg",
            "medium_cover_image": "http:\/\/s.ynet.io\/assets\/images\/movies\/hot_pursuit_2015\/medium-cover.jpg",
            "large_cover_image": "http:\/\/s.ynet.io\/assets\/images\/movies\/hot_pursuit_2015\/large-cover.jpg",
            "rt_critics_score": 8,
            "rt_critics_rating": "Rotten",
            "rt_audience_score": 42,
            "rt_audience_rating": "Spilled",
            "directors": [{
                "name": "Anne Fletcher",
                "small_image": "http:\/\/s.ynet.io\/assets\/images\/directors\/thumb\/nm0281945.jpg",
                "medium_image": "http:\/\/s.ynet.io\/assets\/images\/directors\/nm0281945.jpg",
                "imdb_code": "nm0281945"
            }]
    },

    [...]

    "@meta": {
        "server_time": 1438191104,
        "server_timezone": "Pacific\/Auckland",
        "api_version": 2,
        "execution_time": "180.89 ms"
    }
}

In this response, only one movie is included, but this would normally include 49 more, since we asked for the top 50 movies.

Afterwards, the client stores all that information in the local cache and then displays the UI.

This looks pretty simple and normal. Ask if everything is okay, then ask for all the movies, and then display them with all the information required.

However, there are a few huge mistakes in the process. First of all, the request to Cloudflare is initiated over plain HTTP. That means both the request and the response can be changed by someone with a Man In The Middle position (Local Attacker, Network Administrator, ISP, Government, etc.). The second mistake is that there is no input sanitization whatsoever. That means, there are no checks in place to ensure the validity of the data received. The third mistake is that they make the previous two mistakes in a NodeJS application.

Let’s see what we can achieve by exploiting the above weaknesses.

From what we know, every time Popcorn Time launches, it sends an HTTP request and then displays the content. Let’s intercept this request and change it.

By configuring mitmproxy on Mac OS X and changing the Proxy Settings from the System Preferences, we discover that Popcorn Time does not follow these settings and we can’t see any traffic.

Let’s try something else. Every Mac OS X computer comes with php and sudo preinstalled. We create a directory, for example in the ~/Documents folder, and then we issue in the terminal, while inside this directory:

mkdir -p ./api/v2/

This will end up with a file structure that looks like this:

/Users/DaKnOb/Documents/PopcornTime-Pentest/api/v2/

We place the normal JSON server response that we received from Popcorn Time API (that looks like the one above) in a file called list_movies_pct.json and we make a small change, for example change the first movie’s title to Hello World.

Then, we go to the folder we created inside Documents (the one with the api subfolder) on terminal and issue:

sudo php -S 127.0.0.1:80

I know that running PHP with sudo isn’t smart, but it’s the simplest and fastest way to launch a web server on port 80.

Now we edit the file /etc/hosts with the editor we prefer, and add in the first line of the file:

127.0.0.1 cloudflare.com

This will render the Cloudflare website temporarily inaccessible.

Afterwards, we open Popcorn Time and check the movie catalog. It is recommended to launch the executable with the --reset command line option to clear all cache and settings. In Mac OS X this can be done by issuing in the terminal:

/Applications/Popcorn-Time.app/Contents/MacOS/nwjs --reset

When the interface loads, and after accepting the Terms and Conditions again, we see something like this:

Popcorn Time Content Spoofing

We have successfully performed a “Content Spoofing” attack. Now the first movie, instead of “Hot Pursuit” is called “Hello World”. We can change any other information we like, but that’s not exactly much fun.

Let’s try something even more fun. What if the first movie, instead of Hot Pursuit, was actually called Hot Pursuit <script>alert('XSS');</script>?

Let’s find out: Popcorn Time XSS

This is called an XSS attack. We have injected malicious JavaScript and the client application executed the code. Using this attack we can show fake messages or even do something smarter. Since the application is written in NodeJS, if you find an XSS vulnerability, you are able to control the entire application. This essentially is Remote Code Execution on the computer that runs Popcorn Time. You can do anything the computer user could do. Let’s see some of these:

NodeJS can read files on the computer:

Popcorn Time Local File Read

This file is the beginning of the /etc/passwd, a file containing all the users of a computer. In this picture I used JavaScript’s document.write(); that removes all the content and writes a string to the window. Instead, you can use the typical alert();:

Popcorn Time Local File Read Alert

Of course, we can modify the code to send the content of one or more files to a remote server that we control.

Additionally, NodeJS offers the ability of executing shell commands (bash, cmd.exe, etc.), so the time you all hackers have been waiting for is here:

Popcorn Time RCE

We are able to execute arbitrary shell commands and get their output. We can even set up a remote reverse shell in JavaScript over HTTP.

Things get even worse when you have a local privilege escalation vulnerability and you are able to get full root access to the computer.

Using these three weaknesses (and of course Popcorn Time), I have demonstrated how someone can get complete control of a computer assuming they have a Man In The Middle position in the network between your computer and Cloudflare.


I do not endorse piracy in any way shape or form and I am not publishing this blog post because I don’t like pirates and I want them hacked. I am releasing this for pure educational use in hope that both users and developers will learn something out of it. I am not responsible for any damage that can or will be caused by exploiting these vulnerabilities.

To justify the educational character of this blog post, let’s see what developers can learn out of it:

First of all, you have to understand something very important:

HTTP is insecure. There’s nothing you can do to change this.

Please, use HTTPS everywhere, especially in applications that don’t run inside a web browser.

Second, sanitize your input. Even if you receive something over TLS v1.2 using a Client Certificate, it still isn’t secure! Always perform client-side checks of the server response.

Last but not least, just because something is Open Source doesn’t mean it’s audited and secure. Discovering and exploiting this vulnerability was literally one hour of work, including the time to write all the JavaScript payloads and come up with cool stuff to do.

But the users can also benefit from these discoveries:

Don’t just download and run software on your computer just because they promise to make your life easier. Every program you run can be a virus, and just because you have an Anti-Virus, doesn’t mean it will protect you. Additionally, even if something is not bad and malicious, if it was weaknesses in its code, an attacker can use it to hack your computer.

UPDATE 1: Technical Information

  • A GitHub Repository with PoC Code and Payloads can be found here.
  • The issue opened in the Popcorn Time Gitlab is here.