Canopy was announced to me through my child’s school. The company offers a cross-platform parental control app claiming various capabilities to limit and monitor the use of protected devices. Access to Canopy is billed monthly and includes a compelling list of features for affected parents:
Several of these features require the application to have privileged access to the protected device and can intercept TLS connections to filter content. This privileged access can introduce a significant risk to the security of protected devices and the privacy of children using those devices.
Where to start?
The published Android app provides a starting point for analysis before creating an account. The apk file of the app can be easily obtained from a Play Store indexing site such as APKPure.
APK content is easily extracted and converted to smali (a 2GL language for Android). There are several clues in the release as to what Canopy is doing.
1. The name of the package is com.canopy.vpn.filter.parent
a. From this we can deduce that it implements a VPN connection.
2. The smali recovered from APK includes TensorFlow / lite
a. This indicates that the product is performing some AI (probably image classification) on the device. (It’s a good thing for privacy.)
3. The recovered smali includes com.netspark.android
a. A quick Google for NetSpark reveals that they offer VPN filtering technology.
Installation of the application
After creating an account, I added surveillance protection to an Android handset. The app provided links to download the app as well as help documentation to support the installation. The installation process involved allowing a wide range of permissions, including accessibility support, the ability to rely on other applications, installing a root CA, and configuring a VPN. The app can also (optionally) act as the device administrator to prevent the app from being deleted.
The installation of the app was not as smooth as it could have been. On my test device (updated Google Pixel 3a) the expected prompts did not work for installing the root CA and I had to find and install it manually. At the end of this process the VPN was not properly configured and I had to reinstall the app. The second install attempt worked better and I was able to confirm that the device’s contents were being inspected:
With protection in place, I attempted to load a blocked site and was greeted with a block page:
The blocking page includes a button allowing the child to request access to the requested page. I sent a request to see how it worked and for good measure I added a simple message:
I decided to deny the request and reinsert an XSS payload as explanatory text. The protected phone received a notification about the response.
When I opened this notification, I was greeted again with my XSS pop-up.
The system fails to clean up user entries, resulting in cross-site scripting. An attacker (for example the monitored child) can embed an attack payload in an exception request. While there could be a wide range of ways a smart kid could abuse this vulnerability, the most obvious would be to automatically approve a request. The input field did not appear to have been cleaned up and allowed 50 characters, which was enough to generate an external script. My first test was a payload to automatically click to approve the incoming request. It worked fine and I quickly got another payload to work to automatically suspend the watchdog protection.
Find more XSS
The value of the url in this request is displayed on the main page of the parent dashboard, making it a more attractive target for script injection than request details. I did a quick test to add a script tag in the url and loaded the parent console. While this didn’t trigger my XSS, the page rendering was broken which allowed me to take a closer look. Looking at the source code for the page, I could see that the payload was not triggering because it was trapped in quotes. I updated my payload to include “> but the result has not yet been triggered. Another look showed that HTML now uses a single quote. After a few modifications, I discovered that this system could be tricked into combining double and single quotes.
Bring him into the danger zone
This doesn’t bode well for the Canopy parental control system, but at the same time, you might be wondering if this is really a big deal. After all, most children monitored with this system won’t have any idea about XSS or won’t have access to a parent console to develop an exploit payload. Unfortunately, the attack surface for this vulnerability is somewhat larger than what was previously discussed with the explanation text of the application. Since this attack involves the blocking of a specially crafted URL, it becomes possible for attacks to come from completely external third-party sources. Anyone who can get a child to use the protected device to click a link can now potentially attack parents who are monitoring that account. Once the URL is loaded, it is enough to convince the child to click the request access button on the resulting block page.
More frighteningly, the design of the Canopy API would even allow the external attacker to directly plant a cross-site script payload on a parent account by guessing the parent account ID. Account IDs are short numeric values, so it seems quite plausible that an attacker could sow the attack payload on each parent account by simply issuing a block exception request for each ID value in order.
The external attacker can use it to redirect the parent to ads, exploits, or other malicious content. Alternatively, an attacker could crash a payload to hijack access to the parental control app and extract the GPS coordinates of protected devices on the account. From my perspective, this is a pretty fundamental failure for app advertising that can protect kids online.
After that ?
I have contacted Canopy by phone and email on several occasions. Ultimately, they produced a fix for child-to-parent XSS, but did nothing to protect against parent-to-child XSS or XSS via URL of a blocked page request. before no longer answering. Canopy should implement disinfection of all user input fields, but has not done so. After repeated attempts to work with the supplier, we are releasing this report (with some details removed) so that others can learn from it and act upon it.