Talos Vulnerability Report

TALOS-2018-0634

CUJO Smart Firewall dhcpd.conf verified boot bypass

March 19, 2019
CVE Number

CVE-2018-3969

Summary

An exploitable vulnerability exists in the verified boot protection of the CUJO Smart Firewall. It is possible to add arbitrary shell commands into the dhcpd.conf file, that persist across reboots and firmware updates, and thus allow for executing unverified commands. To trigger this vulnerability, a local attacker needs to be able to write into /config/dhcpd.conf.

Tested Versions

CUJO Smart Firewall - Firmware version 7003

Product URLs

https://www.getcujo.com/smart-firewall-cujo/

CVSSv3 Score

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

CWE

CWE-264: Permissions, Privileges, and Access Controls

Details

CUJO AI produces CUJO Smart Firewall, a device aimed at protecting home networks from a variety of threats, such as malware, phishing websites and hacking attempts. It also provides a way to monitor specific devices in the network and limit their internet access.

To achieve this, CUJO works as a gateway and splits the home network in two: a monitored network and an unmonitored network (where the main home router is). This way, it can inspect (and block) malicious traffic going through the Internet. They also provide Android and iOS applications for managing the device.

The board utilizes an OCTEON III CN7020 processor produced by Cavium Networks, which has a cnMIPS64 microarchitecture.

The firmware is present in the external eMMC and is based on OCTEON's SDK, which results in a Linux-based operating system running a kernel with PaX patches.

Boot process

The boot process involves reading three bootloader stages from the lower 16MB of the eMMC. The first bootloader is OCTEON's eMMC bootloader, while the other two are U-Boots.
On normal conditions, the third-stage bootloader reads an encrypted FIT image file named cujo.rd0 from the first FAT partition. The bootloader also reads seed.rd0 which is used in combination with an external cryptographic IC to derive the key needed to decrypt cujo.rd0.

After decryption, we can see that the FIT image contains a kernel image and a configuration:

$ dumpimage -l cujo.rd0.7003.dec
FIT description: CUJO
Created:         unavailable
 Image 0 (kernel@1)
  Description:  kernel
  Created:      unavailable
  Type:         Kernel Image
  Compression:  uncompressed
  Data Size:    34669392 Bytes = 33856.83 KiB = 33.06 MiB
  Architecture: MIPS
  OS:           Linux
  Load Address: 0x20000000
  Entry Point:  0x20000000
  Hash algo:    sha256
  Hash value:   ebddfb7308925f7b47625523e729e00c8ee7df5876a4d8dfa0053fc2af312a42
 Default Configuration: 'conf@1'
 Configuration 0 (conf@1)
  Description:  linux
  Kernel:       kernel@1
  Sign algo:    sha256,rsa4096
  Sign value:   [redacted]

The kernel image embeds an initramfs, so this image alone is enough to boot the device.

At this stage, the third-stage bootloader tries to boot the system by passing the decrypted FIT image to U-Boot's bootm command.

As we can see, the FIT image has a signed configuration that contains the kernel's hash, signed using RSA-4096. This is part of U-Boot's Verified Boot. The bootm command checks the signature, and boots the kernel only if the verification succeeds.

Additionally, the first 16MB of the eMMC have been permanently write-protected, as we can see from /usr/libexec/mmc_lock:

#!/bin/sh
...

if ! /sbin/mmc cujo format "${ROOTDEV}"; then                    # [1]
    echo "/sbin/mmc failed"
    test_error
fi

old_sha512="$(dd if=${ROOTDEV} bs=512 count=32768 | \
    /usr/bin/openssl sha512 -binary | base64)"
...
if ! dd if=/dev/zero of="${ROOTDEV}" bs=512 count=32768; then    # [2]
    echo "failed to overwrite boot blocks"
    test_error
fi
...
new_sha512="$(dd if=${ROOTDEV} bs=512 count=32768 | \
    /usr/bin/openssl sha512 -binary | base64)"
...
if [ "${old_sha512}" != "${new_sha512}" ]; then                  # [3]
    echo "checksum mismatch"
    test_error
fi

touch /config/mmc_locked
echo "eMMC lock test OK"

The mmc binary [1] is called to lock the first 16MB of the eMMC. This is simply a modified version of mmc-utils that adds support for a simplified locking operation:

# grep -a "mmc-utils has" /sbin/mmc
mmc-utils has been compiled without MMC_IOC_MULTI_CMD support, needed by FFU.

# mmc -h|tail
    mmc cujo format <device>
        Marks the first 16MB of <device> as read-only.

    mmc help|--help|-h
        Show the help.

    mmc <cmd> --help
        Show detailed help for a command or subset of commands.

0.1

After locking, the memory is overwritten with zeros [2] and the write-protection is verified by ensuring that the last write operation didn't succeed [3].

Besides bootloaders, the eMMC has the following partition layout:

p1 *      32768 1671168 1638401  800M  c W95 FAT32 (LBA)   /boot
p2      1671169 1802241  131073   64M 83 Linux             /config
p3      1802242 2621442  819201  400M 83 Linux             /update
p4      2621443 3735551 1114109  544M 83 Linux             /var

The first partition contains the encrypted FIT image (cujo.rd0). The rest of the eMMC partitions only contain configuration data ("/config"), runtime data ("/var") and empty space meant to be used during system updates ("/update").

Persistent root

Since the bootloaders are present in the lower 16MB of the flash, which is write-protected, attackers can't bypass the verified boot by disabling the FIT signature checks from the third-stage bootloader. Ideally, this should prevent attackers that manage to execute arbitrary commands on the device from achieving a persistent root that survives across reboots.

However, we identified a weak implementation decision in the architecture, that allows to achieve a persistent root surviving both reboots and firmware updates (unless the firmware update formats the "/config" partition, which isn't usually the case).

For normal usage, CUJO can be configured in "bridge" or "dhcp" mode, depending on how the network was initially set up.

When CUJO is in "dhcp" mode, it runs its own DHCP server, namely ISC DHCP.

The server is configured using the file /config/dhcpd.conf, which typically looks like this:

default-lease-time 86400;
max-lease-time 86400;

authoritative;

subnet 192.168.0.0 netmask 255.255.255.0 {
pool {
range 192.168.0.2 192.168.0.254;
}
option routers 192.168.0.1;
option domain-name-servers 10.0.0.1,8.8.4.4;
group {
}
}

Since this is a configuration file that is needed for the correct function of the device, CUJO normally doesn't delete it across reboots or firmware updates, unless unexpected issues are detected (e.g. a filesystem corruption).

This configuration file allows to insert an "on commit" event (which happens every time the server leases an IP address) for performing an "execute" statement, which executes any arbitrary command with root privileges. Thus attackers can, for example, store any malicious binary in the "/config" directory and have it called by the dhcpd daemon.

Exploit Proof of Concept

The following proof of concept shows how to modify the dhcpd.conf file to achieve a persistent root. To execute arbitrary commands on every lease, it's enough to add the following lines to dhcpd.conf:

on commit {
  execute("/bin/sh", "/config/root.sh");
}

Where root.sh is the attacker's script.

Note that the configuration file is usually overwritten when modifications are applied to the dhcp settings from the smartphone application, and this would remove the on commit statement. To avoid this, it's enough to set the immutable attribute to the file using:

# chattr +i /config/dhcpd.conf`

Timeline

2018-07-20 - Vendor Disclosure
2019-03-19 - Public Release

Credit

Discovered by Claudio Bozzato, Yves Younan, Aleksandar Nikolic, Martin Zeiser, Marcin 'Icewall' Noga, Lilith Wyatt <(^_^)>, Cory Duplantis, Tyler Bohan of Cisco Talos.