Talos Vulnerability Report

TALOS-2017-0370

Circle with Disney Weak Authentication Vulnerability

October 31, 2017
CVE Number

CVE-2017-2864

Summary

An exploitable vulnerability exists in the generation of authentication token functionality of Circle with Disney. Specially crafted network packets can cause a valid authentication token to be returned to the attacker resulting in authentication bypass. An attacker can send a series of packets to trigger this vulnerability.

Tested Versions

Circle with Disney 2.0.1

Product URLs

https://meetcircle.com/

CVSSv3 Score

8.1 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-307: Improper Restriction of Excessive Authentication Attempts

Details

Circle with Disney is a network device used to monitor internet use of children on a given network.

When making any requests to the Circle, an authenticated token must be provided. To request a token, a client specifies an appid, a unique string used to identify the client, as well as a hash, a SHA1 hash to verify the client should have access to the device. One secret piece of information is a 4 digit pin. The hash is calculated by the following:

hash = SHA1(appid + pin) 

The client provides both the appid and hash. Because the key space for the pin is only 10000, an attacker can brute force this pin to retrieve an authentication token. With the authentication token in hand, an attacker can make available API calls.

Circle implements a lockout mechanism that allows to test only 3 pins every 15 minutes. This is implemented by the apid binary, which writes to the file /tmp/failed_token_info the number of authentication attempts and the latest timestamp:

3 1501762357

This resource is saved in /tmp, which is a "tmpfs" directory shared by many components of the system and has a size of 30MB.

If no failed authentication attempts are made since system boot, failed_token_info won't exist in /tmp. This means that if an attacker is able to keep /tmp completely filled, the apid binary won't ever be able to create failed_token_info, thus making the pin bruteforce possible in practice. Moreover note that if a DoS attack is available for rebooting the device, it can be used to clear the whole /tmp directory. Since failed_token_info is never saved in non-volatile storage, a slow bruteforce can be performed by testing 3 pins per reboot.

Without using a DoS, an attacker needs to fill /tmp before starting the bruteforce. To achieve this, a combination of 4 techniques can be used.

1- /tmp/versions

Once every hour, /mnt/shares/usr/bin/firmware_updater.sh is invoked to check for updates. This script downloads a file containing the latest version of the firmware via HTTP and stores it in /tmp/versions:

...
/tmp/wget -q -O /tmp/versions  "http://download.meetcircle.co/dev/firmware/check_version.php?DEVID=$MAC&FVER=$my_firmware_ver&UVER=$my_updater_ver&DBVER=$my_database_ver&ETH=$eth_connected&IP=$IP$EXTRA"
...

By exploiting this behavior, an attacker can send a 31MB versions file and fill /tmp.

Unfortunately, since many components are using /tmp, the failed_token_info might still be written after some time: the main problematic file is /tmp/iplist, which can shrink depending on the network activity. This is why the techniques described in the following paragraphs are needed as well.

2- /tmp/postfile.bin

apid provides a way for restoring a previously-saved configuration and for upgrading the firmware. This can be done using the api command /api/CONFIG/restore, which is handled in function sub_417528. At high level it works as follows:

if query == "/api/CONFIG/restore" or (query == "/api/UPLOAD_FIRMWARE" and substr(srcip, 0, 10) == "10.123.234")
    save_postfilebin()
    if check_token()
        ...do update/restore...

save_postfilebin() will save the uploaded firmware (or configuration file) to /tmp/postfile.bin. As we can see, this function is called before checking if the user supplied a valid token, allowing any unauthenticated user to upload such file. Note that even if the token turns out to be wrong, the file won't be deleted. The size limit of the upload is almost 5MB, so it cannot be used alone to fill /tmp.

3- /tmp/iplist

The arp2 binary keeps a list of recently-seen hosts on the network by monitoring ARP requests and saves them in /tmp/iplist.

When operating on the file, arp2 uses fopen with mode "w+". This means that as soon as fopen returns the /tmp/iplist file will be truncated. If, at the same time, apid tries to create failed_token_info, it will succeed since in that instant there will be some free space in /tmp.

To avoid space to be freed by iplist, it's important that the bruteforce attack only starts when iplist has a size of 0, so it won't ever be able to grow. To do this, an attacker could upload a /tmp/postfile.bin file (to try to fill the little space left in /tmp) in the same moment that iplist gets truncated by apid. In order to maximize the likelihood of this condition, a series of spoofed ARP requests can be continuously sent to the device, forcing it to update iplist, while in parallel uploading a rather small configuration file.

4- lockout feedback

It's useful, during bruteforce, to know whether pin attempts are discarded because of the lockout mechanism. Indeed, it's possible to get this kind of feedback. At high level this is a simplification of how the token request procedure works:

if (failed_token >= 3 and time() - failed_token_time < 15 * 60)
    return "token request failure"
else
    appid = get_param(query, "appid")
    if not appid:
        return "token request failure - no app id specified"
    if not good_pin(pin):
        return "token request failure"
    else
        return new_token()

As we can see, if a token request is sent without an appid and requests are not locked out, the error returned is "token request failure - no app id specified". Note however that in this case pins are not tested. This feedback can be used every once in a while, just to ensure that pins are effectively verified during bruteforce.

Complete attack

A complete attack sequence could go like this:

1- impersonate the `download.meetcircle.co` server, e.g. via MITM.
2- listen on port 80 for a GET request of "/dev/firmware/check_version.php" and return a big file (> 30MB).
3- in parallel, for about 500 POST requests:
   A- continuously send spoofed ARP packets with different IP source, to trigger `iplist` updates.
   B- continuously send POST requests for writing `postfile.bin`.
4- `/tmp` should be completely full by now.
5- bruteforce each possible PIN and every 10 attempts check whether requests have been locked.

Timeline

2017-07-13- Vendor Disclosure
2017-10-31 - Public Release

Credit

Discovered by Cory Duplantis, Yves Younan, Marcin 'Icewall' Noga, Claudio Bozzato, Lilith Wyatt <(^_^)>, Aleksandar Nikolic, and Richard Johnson of Cisco Talos.