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:
- “Backend” – a Flask server that is open to the world and protected by username/password (basic auth). The server mostly focuses on configuration editing.
- “Frontend” – a Flask server that runs on localhost, has no password and its main job is to start/stop/manage network capture.
- “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.
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
:
Using the first vulnerability, we were able to rewrite the list and change it to 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.
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.
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
- 2020-12-14: Detailed report sent to [email protected]
- 2020-12-15: First response from Kaspersky’s Product Security Team
- 2020-12-18: All the vulnerabilities patched + advising to upgrade to the patched version
- 2021-01-22: CVEs issued
- 2021-02-11: Blog post published
Want to Hear More?
A free consulting meeting included.