Attacking Roku sticks for fun and profit

Hello readers!

It’s really been quite a while, but here I’m back, and this time we’re going to explore Roku and video ads.

TL;DR: Ad fraud is easy on Roku and it’s possible to remotely install arbitrary channels without user interaction from a malicious ad / website.


For those who are unfamiliar, Roku is a line of popular media streamers that allow media streaming from different sources, i.e. “channels”, such as Netflix, Apple TV and more. Channels are developed by 3d party developers and can be found on the “channel store“. They aren’t just playing media, but actually feature rich apps (much like mobile apps) in terms of capabilities: they can respond to user interactions and input from sensors , so you can find games, Reddit clients, etc.

Ad tech

Roku offers channel developers both subscriptions and ads as monetization mechanisms. Roku devices are capable of displaying video ads using various industry standard formats, most notably VAST. For the developer side, they offer both Roku Advertising Framework (RAF) which is an SDK for requesting and rendering video ads, and a self serve platform to promote the channel and increase the audience. For the advertiser side, they offer OneView, which is based on their DataXu acquisition. In order to be eligible for ads monetization, a channel must be approved by Roku, based on its content and engagement metrics.

According to industry rumors, CPM on Roku are significantly higher than traditional web display and are in the rage of tens of dollar, depends on the channel’s content genre and ad type (pre / mid roll) and the device’s IP geolocation. Funnily enough, sometimes the ads CPM can, in fact, be higher then the cost of the Roku device itself, which fits perfectly their strategy, as put out by AdExchanger:

Roku’s go-to-market strategy is simple: Get as many streaming devices into as many homes as cheaply as possible, and monetize them through advertising.

Alison Weissbrot

Ad sec

Everybody in adtech knows that high CPMs attracts fraudsters, and Roku’s case is not different. In the past, several schemes were found and disclosed.

But it’s not just high CPMs. Roku (and Connected TVs in general) has weaker threat model, and it’s easier to manipulate the metrics without getting caught. Since Roku ads aren’t rendered inside a browser or webview of any kind but rather in the platform’s native player, traditional JavasScript based verification tag simply aren’t relevant.

Furthermore, RAF only supports VAST 2 and 3, which doesn’t even include the AdVerification element, nor even the Server Side Ad Insertion verification headers. Generally speaking, what’s left to verification vendor to do is to stuff their pixels inside the VAST Impression and TrackingEvents elements, which only allows them to performs basic checks against the requests timings and device IP, similarly to what I’ve described in the past in my overview of pre-bid filtering solutions. All other values are declared and cloud be easily spoofed by malicious actor.

Here’s a shot list of possible attacks:

  • Channel spoofing (aka counterfeit inventory): there’s no Ads.txt equivalent, so there’s no way to verify the the parameters in the bid request, which means esoteric channels can pretend to be Netflix, NBA, or whoever, in order to attract higher bids.
  • Device spoofing: It’s possible to initiate ad requests with Roku user agent from other agents (apps / browsers). Although detectable by TCP/HTTP fingerprints, Roku supports Server Side Ad Insertion which is ideal for fraud: Only one IP is needed as both requests and tracking events (such as mid quartile of the ad was played) are initiated by design from the publisher’s server.
  • Impression spoofing: with the lack of viewability measurements in CTV, it’s technically possible to initiate ad requests and fire the impression and event pixels with even drawing anything to the screen. All you need is an HTTP and XML parser libraries.

Attacking Roku sticks

All the ad fraud risks detailed above aren’t really Roku specific but rather apply to CTV advertising in general. However, I got myself a Roku stick for the purpose of specifically analyze it, and I found rather interesting findings: Roku exposes “External Control Protocol” over the local network, as described in their docs:

The External Control Protocol (ECP) enables a Roku device to be controlled over a local area network by providing a number of external control services. The Roku devices offering these external control services are discoverable using SSDP (Simple Service Discovery Protocol). ECP is a simple RESTful API that can be accessed by programs in virtually any programming environment.

While very convenient for the users, who can install things such as mobile Roku remote apps and they just work, this mechanism does not include any authentication mechanism, which means anything inside the LAN could use it to issue ECP commands to the Roku device.

So at least in theory, it’s possible to control Roku devices by executing JavaScript code on a browser that is running on the same LAN. Sounds great, isn’t it? Luckily, the default ECP port, 8060, is not blocked by browsers (some other ports are blocked, such as 22 for SSH, because of the exact same attack could be used on other sensitive services).

It means an attacker could, for example, run the malicious JavaScript code through ad networks, i.e. malvertising campaign, and send ECP commands to Roku devices, if the user behind the browser has one (and the attacker is able to locate its IP address, more on that later). I wanted to focus on this specific attack because it’s fairly easy to get your JS running cheaply, in large volumes, by (ab)using ad networks, which is important in order to make the attack practical and economically feasible by having reasonable cost of “user acquisition” relative to its monetization potential.

I wanted to see what’s possible with ECP, and going over the “general commands” in the docs (linked above), and two of them immediately jumped: install/<APP_ID> and keypress/<KEY>. As their names suggests, the former lets you launch the install screen of arbitrary app (channel) id, and the latter allows you to press whatever key you want to, as if you’re holding the Roku’s remote in your hand. Together, they are a powerful combination, since you can install (and lauch) any app on the victim’s device.

Now, let’s assume we have developed and uploaded an app to the Roku channel store, and we want to install it on as many users as possible. We want to do so because we can:

  • Create a paid channel and collect the payments from the victims. Remember, they didn’t choose to install anything, we will force their device to install our app with ECP. Easy, but considered as CC fraud and the FBI will find you 🙂
  • Create free channel and monetize it with ads. The challenge is that in order to display ads, the channel must be launched. Although it’s possible to launch it with ECP, it’s not stealthy. The user will notice that an unwanted channel is running and will close it and probably remove it. One solution to this problem is to find a bug in BrightScript (Roku’s app programming language) or other system component and exploit it for privileges escalation, then installing a daemon directly on the OS (Roku OS is Linux), but that’s hard. A much easier solution would be to install a screensaver, which, according to the docs, “is a channel which is run automatically when the system has been idle for a period of time.”

Now that we have two viable monetization options which we can execute by (ab)using ECP, the next missing step is finding the Roku’s IP address. Normally we would use SSDP search request, but unfortunately in our malvertising scenario, we can not read the SSDP response due to the browser’s Same Origin Policy, so another approach is needed – we have to do local network scanning in JavaScript, from the browser 🙂

The first step would be to find the address range that we need to scan, so WebRTC Peer Connection immediately comes to mind, because it uses ICE for NAT traversal and used the expose the internal IP of the client through SDP. This feature was abused in the past by fingerprinting vendors, even famously on NYTimes website by WhiteOps. Unfortunately (or fortunately, depends on how you look at this), not so long ago relative to time of this writing, browsers started to implement a proposal to use mDNS to protect users privacy when exposing ICE candidates, which means you’ll get an address that looks like 1f528c83-551f-4f34-ad3a-67524d2fed83.local instead of

There are three blocks of IP addresses reserved for private networks: – (10/8 prefix) – (172.16/12 prefix) – (192.168/16 prefix)

For the sake of simplicity of our PoC, we are going to scan by brute force only the last one (192.168/16), which is most commonly used by home routers anyway, and they usually assign new clients IP (using DHCP) in the same subnet.

So what do we need now? A way to differentiate between the following options:

  1. an address that do not exist on the network
  2. an address that do exists, but doesn’t belong to a Roku device
  3. an address that exists and belongs to a Roku device

Achieving this goal was possible using the following method:

For each IP address, initiate HTTP GET request with Roku’s ECP port (8060), with Roku specific URL path (for example, For 1, error event will be fired after the browser’s timeout passed, because the address is unreachable. For 2, error event will be fired because it’s not a valid URL. For 2, load event will be fired and we could use it to identify the Roku’s IP address.

Note that the requests must be HTTP and not HTTPS, as the Roku ECP doesn’t support HTTPS. Up until recently it was not a problem, because by using passive mixed content we could initiate HTTP requests from HTTPS, so we could run this network scan logic directly inside the malicious ad’s creative code.

However, Chrome recently started blocking mixed content (HTTP content on HTTPS websites) by default, without falling back to HTTP when the content isn’t available over HTTPS. Since most websites / ads are served nowadays over HTTPS, it would make it impossible to use this method directly in the ad, but still it’s possible to do so from an attacker controlled HTTP web page, either by making it the destination of the ad’s landing page (and buying PPC), or just programmatically force redirecting the user to such a page, which is a commonly used malvertising practice.

The following PoC code demonstrates this logic:

EDIT: After giving it a second thought, I won’t publicly share the PoC code. Although all the details needed in order to implement it are available here in this post, I don’t want to help script kiddies to launch attacks 🙂

So, after we found the IP address, all we have left is to send the ECP commands to install our channel:

let rokuAddress = ''; // found previously with the scanner 
let appId = 14; // MLB is just used for demonstration, replace with our malicious channel id
let install = '/install/' + appId;
let keypress = '/keypress/'
let select = keypress + 'Select';
let home = keypress + 'Home';
function postToRoku (cmd) {
return fetch(rokuAddress + cmd, {method: 'POST', mode: 'no-cors'});
// launch the install screen
postToRoku(install).then(res => {
// press the intall button
postToRoku(select).then(res => {
// go back to home screen

And, voila! Our channel just got installed on the victim’s Roku.

I really think this attack is not only theoretical, but really is a practical one. It’s possible to fix it be including a token in the initial SSDP response, that must be included in any subsequent ECP request. This way the Roku device could verify that the ECP requests are coming from a party that’s able to read the response, such as remote control mobile app, and not from a browser. Unfortunately, this is not backward compatible and would probably break existing apps that are built on top of ECP.

If you liked this post, are interested in ad security and want to chat – don’t hesitate to contact me! I’m also currently looking for new projects.


26 thoughts on “Attacking Roku sticks for fun and profit

  1. I think Roku took note of this article. App install via REST API is now triggering a challenge dialog for the user to input some 4 digit code displayed to the user at the prompt. :clap:


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s