CVE-2018-3969
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
.
CUJO Smart Firewall - Firmware version 7003
https://www.getcujo.com/smart-firewall-cujo/
8.2 - CVSS:3.0/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
CWE-264: Permissions, Privileges, and Access Controls
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.
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”).
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.
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`
2018-07-20 - Vendor Disclosure
2019-03-19 - Public Release
Discovered by Claudio Bozzato, Yves Younan, Aleksandar Nikolic, Martin Zeiser, Marcin 'Icewall' Noga, Lilith Wyatt <(^_^)>, Cory Duplantis, Tyler Bohan of Cisco Talos.