CVE-2017-2864
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.
Circle with Disney 2.0.1
8.1 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-307: Improper Restriction of Excessive Authentication Attempts
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.
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_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.
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
.
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.
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.
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.
2017-07-13- Vendor Disclosure
2017-10-31 - Public Release
Discovered by Cory Duplantis, Yves Younan, Marcin 'Icewall' Noga, Claudio Bozzato, Lilith Wyatt <(^_^)>, Aleksandar Nikolic, and Richard Johnson of Cisco Talos.