We Decide What You See: Remote Code Execution on a Major IPTV Platform

Research by: Ronen Shustin

Introduction

About a year ago Check Point Research discovered critical vulnerabilities in a Ukrainian TV streaming platform that, if exploited, could leave service providers exposed to a serious breach. The risks would be their entire customer database of personal info and financial details as well as allowing an attacker to potentially stream any content they choose on to the screens of their customer network.

While it is not possible to know the exact number of those potentially exposed, Check Point Research discerned there to be over 1000 providers of this service with quite likely very high numbers of worldwide customers. As this vulnerability has been patched, we can now reveal what was involved.

The Background

Infomir is a Ukrainian manufacturer of IPTV (Internet Protocol Television), OTT (Over-the-Top) and VoD (Video-on Demand) devices such as set-top boxes. These set-top boxes (STB) are basically streamers which connect to a television service provider from one side, and to your television on the other. Each of these STB communicates with and receives its data from a dedicated platform called Ministra (previously named Stalker).

In order to receive the television broadcast, the STB connects to the Ministra and service providers use the Ministra platform to manage their clients. Were an attacker to gain unauthorized access to this platform they could essentially expose the provider’s customer base’s financial details or change the content sent to the service providers’ customers.

While it is impossible to know how many people could have been affected by this vulnerability, due to there being three separate entities involved (Infomir as the vendor of the IPTV product, Service Providers of the Ministra platform and the end user that use the home service), Check Point Research did find there to be around 1000 service providers that have bought the Ministra and provide it to their customers. Unfortunately we don’t know how many customers each of these resellers has, though from our initial scans there are over 1000 resellers around the world, so the number of those exposed could be very high.

In this report, we discuss the major vulnerabilities discovered in the STB managing platform, Ministra, which could have allowed an attacker to get full control over the server.

Responsible Disclosure: Check Point Research reported this vulnerability to Infomir over a year ago who then released a patch after fixing it. As there may be service providers who have yet to implement this patch, we also reported it to CTA Forum where security vendors have acted to update their products to protect against this potential attack.

Due to some resellers likely not to have patched their service, and therefore are still at risk of attack, we advise customers to contact their TV streaming service provider to ensure they have implemented the protection against this Ministra vulnerability.

The Check Point IPS blade provides protections against this threat for Infomir resellers:

“Infomir Ministra SQL Injection Remote Code Execution”

The density of live Ministra instances, quite possibly service providers, by country

Top 10 Countries by number of service providers

(note: this information is only partial and was collected via various methods)

country count United States 199 Netherlands 137 Russia 120 France 117 Canada 105 Bulgaria 85 Ukraine 72 Germany 65 Poland 25 Serbia 22

Attack Surface

Ministra is Infomir’s web management platform that controls the STB devices. It is PHP based, and like most web-based platforms, it has an admin interface that requires authentication. However, we were able to bypass the authentication mechanism and utilize some of the admin AJAX API functions. This lead to SQL Injection chained to PHP Object Injection vulnerabilities, effectively allowing us to remotely execute code on the server.

Authentication Bypass

Some of the administration panel controllers located at the path “admin/src/Controller” contain functions that are intended for Ajax usage. We took a look at one of these functions called `video_categories_list_json` from the `VideoClubController`:

The `$this->isAjax` property is set to true if the ‘X-Requested-With’ header is sent with the contents of ‘XMLHttpRequest’.

As you can see, this method is clearly intended for Ajax usage and it therefore checks the authentication only for that case. However, we managed to bypass the authentication check by not sending this header. As a result, we managed to elicit some unintended behavior.

Unauthenticated SQL Injection

As we could bypass the authentication for some functions (the above pattern is found in other functions as well), this extended our attack surface. One of these functions is called `video_schedule_list_json` from the `VideoClubController`:

As you can see, the variable `$param` is assigned with `$this->data` (GET array data) or `$this->postData` (POST array data) and then passed to the `prepareDataTableParams` function.

The first part of this function checks which columns should be ordered. It loops through the ‘order’ array parameter, and takes the column data from the ‘columns’ array using the column value from the ‘order’ parameter. Afterward, the `$col_name` variable is assigned with the name of the column.

If the column is orderable, the `$col_name` is added to the `$query_param[‘order’]` array. The interesting part here is that by sending similar post data, a user can control the `$col_name` which is a key in the `$query_param` array:

order[][dir]=aaa&order[0][column]=0&columns[][name]=ColumnName&columns[0][orderable]=1

The second part of the function is responsible for filtering the results by a column or a search term. It adds the `$col_name` as a key in `$query_param[‘select’][]`. It also checks if the column is searchable and if so, adds it to the `$query_param[‘like’]` array. Here the user can control the key by controlling `$col_name` and sending similar data:

columns[0][name]=ColumnName&columns[0][visible]=true&columns[0][searchable]=true&search[value]=myserach

After this function returns, `video_schedule_list_json` manipulates the `$query_param` variable and then calls the `getAllVideoTasks` function:

The `getAllVideoTasks` function executes an SQL query:

As demonstrated earlier, the user can control the order, like and select keys. This immediately raises a red flag. Usually, keys are not sanitized properly because they do not come from user-controlled data. Unfortunately, we didn’t have the source code of the `mysqlInstance` objects implementation because it is encoded with IonCube and the license forbids us to touch the class file. Our only option was to black box test the functions that are responsible for handling the controlled values. After some tests, it was clear that the keys passed to the orderby, select and like functions were vulnerable to SQL Injection. As we control keys in the query, we can perform either blind or reflected SQL injection.

To summarize, the root cause of this vulnerability is that the `prepareDataTableParams` function allows user-controlled data to influence keys which are not sanitized properly later. Also, this function is called from multiple locations in the code, which means this vulnerability can be triggered from other locations.

Next, we chained this SQL injection to a stronger primitive.

Unauthenticated Object Injection

It can be very dangerous to allow user-supplied data to get into the `unserialize` function. One of the controller private functions which calls `unserialize` is `setLinksForVideoLog` from the `VideoClubController`:

This function is called from `video_logs_json` function and also comes from the `VideoClubController`:

This function is vulnerable to the authentication bypass, and also calls `prepareDataTableParams` which is vulnerable to SQL Injection

Later in this function, these lines are called:

The `$query_param` variable is constructed from the `prepareDataTableParams` result, and then passed to the `getVideoLog` function. We can perform a reflected SQL injection here, and as a result, return any arbitrary data we want to `$response[‘data’]`.

The data is later passed to the `setLinksForVideoLog` function which uses the `unserialize` function. So here we have a classic case of an SQL injection leading to Object Injection vulnerability!

Arbitrary File Write (Remote Code Execution)

Our next step was to see how we can leverage this PHP Object Injection into much stronger primitive. The first thing to do when exploiting Object Injections is to look for magic methods. These methods are called on the object we try to instantiate. For example, the method `__destruct` is called at the destruction process of the object. We searched for references to this function, and found one sink in the SwiftMailer library class (`Swift_Transport_SendmailTransport`) that looked interesting:

We can see that in the `__destruct` implementation, there is a call to the `stop` function.

The class name tells us that it is used to handle SMTP related functionality. We can assume that when the `__destruct` function is called, it will end the SMTP session. As we can see, there is a call to `executeCommand` function which probably sends the “QUIT” command to the SMTP server to make sure the session closes properly.

By calling the `write` function, the system tries to either send the `$command` over a socket or write it to a file. However, because we control the `$this->_buffer` property, we can set it to any object we want that implements the `write` function. After searching for interesting classes that implement this function, we came across the `Swift_ByteStream_FileByteStream` class, whose parent implements the `write` function:

As we also control this object’s properties, we can set `$this->_writeBuffer` to be any value we want. Let’s look at `_doWrite`:

The `_doWrite` function calls the `_filter` function, but we can ignore this case by just setting `$this->_filters` to be an empty value. Next, it calls the `_commit` with our controller value.

Interesting… It calls the `fwrite` function (which writes data to a file) with our controlled data. But where does the handle come from?

We can see that if the `$this->_writer` is not set, it will open a handle to the file path that is in `$this->_path`. As we stated previously, we can control all the properties of this class: we can set `$this->_writer` to be false, and `$this->_path` to be any arbitrary path we want.

At the end, we control the path and the content of the file. With this primitive, we can write arbitrary PHP files and get remote code execution on the server. One thing to notice is that the above `write` function appends “QUIT” to the end of the content. Therefore, to get a valid running PHP code, it is better to add a comment at the end of our data (//).

Fun fact: All the classes we used are related to SwiftMailer, so effectively, we found a generic file write gadget. You can now generate this gadget with PHPGGC (SwiftMailer/FW4).

Video Demo of the Attack

Summary

In this blog post, we showed how a small logical bug can sometimes be leveraged to a critical security issue. In this particular case, we used the authentication bypass to perform an SQL Injection on the server. With that knowledge, we escalated this issue to an Object Injection vulnerability, which in turn allowed us to execute arbitrary code on the server, potentially impacting not only the provider but also the provider’s clients.

The vulnerabilities were fixed in version 5.4.1 (Stalker / Ministra) but we strongly suggest vendors to update their system to the latest version.

The Check Point IPS blade provides protections against this threat:

“Infomir Ministra SQL Injection Remote Code Execution”