3 Vulnerabilities in Kaspersky-backed TinyCheck

In our latest research, we found 3 different vulnerabilities in TinyCheck, an open-source tool developed and published by Félix Aimé, one of Kaspersky’s GReAT experts. Each one of the vulnerabilities has a high severity by itself. Once combined into a chain, a remote attacker could exploit it to get an RCE (remote code execution) on the remote TinyCheck machine.

In a nutshell, we used the default credentials of TinyCheck to edit two sections in a configuration file:

  • In the first, we added a malicious payload, which will be executed later via command injection vulnerability.
  • In the second, we added a URL to a list that will cause SSRF, which later will trigger the malicious payload from the first section.

What is TinyCheck

TinyCheck lets the user analyze a device’s network. The user can connect his device to TinyCheck’s proxy WiFi, which will capture the network activity. This activity can be analyzed for malicious traffic going through the network.

Since we have experience with proxy in Android, we knew that a simple proxy should not have too much trouble handling potentially malicious traffic. But TinyCheck is more feature rich, offering better sniffing capability than a simple proxy script does. The source code is available on GitHub which made it possible to examine it.

TinyCheck has 3 relevant services:

  1. “Backend” – a Flask server that is open to the world and protected by username/password (basic auth). The server mostly focuses on configuration editing.
  2. “Frontend” – a Flask server that runs on localhost, has no password and its main job is to start/stop/manage network capture.
  3. “Watchers” – a watchdog service that will iterate over a list of URLs and will do an HTTP GET request to these.

Vulnerability #1 – Default Credentials

This is the simplest one of the 3 vulnerabilities, but essential for the whole attack. By default, TinyCheck comes with the default credentials of “tinycheck” as the username and password.

Default credentials vulnerability
From the GitHub readme page

Having such default credentials without requiring the user to change these at first use, presents an unnecessary risk.

By using the default credentials, we had access to a variety of endpoints in the “backend” server, which gave us a wider attack surface.

The important endpoint that we will use later was the one that edits the YAML configuration file.

https://tinycheck.local/config/edit/CATEGORY/KEY/VALUE

More on how we used it below.

Vulnerability #2 – SSRF

Server side request forget (SSRF) is always exciting and many times overlooked by developers or security researchers. By exploiting this vulnerability, we were able to force the server to make an HTTP request. It is useful for firewall bypass or for accessing the internal network, which was not accessible before.

We found that the server has a list of URLs stored in the YAML configuration file under watchers/iocs:

Watchers IOCs URLs
TincyCheck watchers section in the YAML file

Using the first vulnerability, we were able to rewrite the list and change it to our own URLs:

Custom Watchers IOCs URLs
watchers section after we rewrote with our own URLs

Interestingly, now we were able to make the “watchers” service do an HTTP GET request to any URL we choose. Most importantly, we could hit the “frontend” server as it runs in localhost once the “watchers” service is reloaded.
Again, that gave us a wider attack surface to reach more endpoints and more logic we could exploit.

Vulnerability #3 – Command Injection

We started to investigate the “frontend” server. Don’t be confused by the name, this is a Flask server, not just a JS frontend one.

Having a whole new world of endpoints from the SSRF vulnerability, we examined the service classes, functions and utilities that might be exploitable.
After reading the source code and going deep into the function call stack we stumbled upon this code:

Popen("tshark -i {}".format(self.iface), shell=True)

Popen is the way to call a subprocess in Python. The shell=True and the format are the exploitable parts, meaning that the command that will run will be interpreted as a string. While the original intention here does work if self.iface is just a string like “eth0”, we realised that if we were able to inject maliciously crafted string, we would be able to run arbitrary code.

Luckily for us, the self.iface was actually read from the configuration file under the network/in section, its original value suppose to point to a network interface.

self.iface = read_config("network/in")

Like the SSRF vulnerability, we injected our payload using the same configuration edit endpoint.

The injection itself was a simple one:

; whoami>/tmp/exploit.out

That means that with our payload, the original command will get formatted as:

Popen("tshark -i ; whoami>/tmp/exploit.out", shell=True)

Indeed, the file contained the word root, which means we could take control over the system, as the server is running as root and not a dedicated low privileged user.

But we had to trigger this code. Right now, our payload was executed just when the user starts to capture network activity via the UI.

The Full Exploit Chain

To tie everything together, we needed a way to trigger our payload.

Our malicious payload that was injected into the network configuration

We did so by leveraging the SSRF to call one of the local “frontend” endpoints, which starts the network capture function so we don’t have to wait for the user to do so.

Capture Network SSRF
Injected the frontend server endpoint to trigger the network capture functionality

This called the vulnerable code from the “watchers” service and for the command injection to be executed with our malicious payload.

Disclosure to Kaspersky Security Team

The vulnerability disclosure to Kaspersky’s security team was a remarkably good experience for us. Shutout for them for reaching back to us very quickly and getting the product lead, Félix Aimé, involved who solved all the vulnerabilities in a matter of a day or two.

Disclosure Timeline