CVE-2021-22909- DIGGING INTO A UBIQUITI FIRMWARE UPDATE BUG

How to get root on Ubuntu 20.04 by pretending nobody’s /home

Original text by Vincent Lee

Back In February, Ubiquiti released a new firmware update for the Ubiquiti EdgeRouter, fixing CVE-2021-22909/ZDI-21-601. The vulnerability lies in the firmware update procedure and allows a man-in-the-middle (MiTM) attacker to execute code as root on the device by serving a malicious firmware image when the system performs an automatic firmware update. The vulnerability was discovered and reported to the ZDI program by the researcher known as awxylitol.

This vulnerability may sound contrived; a bad actor gives bad firmware to the device and bad things happen. However, insecure download vulnerabilities have been the backbone of multiple Pwn2Own winning entries in the router category since its inception. The impact of this vulnerability is quite nuanced and worthy of further discussion.

How exactly does the router perform a firmware update?

According to Ubiquiti documentation, the new templated operational command add system image can be used to update the firmware of the router through the command line interface (CLI). A templated operational command allows the user to quickly modify the operational state of the router without fiddling with complex command-line parameters. This simplifies the process for day-to-day operations and minimizes user errors. I am sure we have all heard of horror stories of system administrators who accidentally deleted critical files, locked themselves out of equipment that is far away from civilization, and so forth. Templated commands attempt to mitigate these issues.

The templating system used by the Ubiquiti EdgeRouter is provided by the vyatta-op package. The command add system image is defined in the /opt/vyatta/share/vyatta-op/templates/add/system/image/node.def file.

$ cat node.def
help: Add a new image to the system
run: sudo /usr/sbin/ubnt-fw-latest —upgrade

view rawCVE-2021-22909-snippet-1.console hosted with ❤ by GitHub

By running this operational command, the user is effectively invoking the ubnt-fw-latest script with the --upgrade option. This option causes the ubnt-fw-latest script to run the upgrade_firmware() function, which will check with a Ubiquiti update server to get information about the latest firmware release, including the firmware download URLs.

#!/bin/bash
#——————————————————————————-
STATUS_FILE=»/var/run/fw-latest-status»
UPGRADING_FILE=»/var/run/upgrading»
REBOOT_NEEDED_FILE=»/var/run/needsareboot»
DOWNLOADING_FILE=»/var/run/downloading»
URL=»https://fw-update.ubnt.com/api/firmware-latest»
ACTION=»refresh»
CHANNEL=»release»
DEFAULT_URL=»https://localhost/eat/my/shorts.tar»
#——————————————————————————-
while [[ $# -gt 0 ]]
do
key=»$1″
case $key in
-r|—refresh) # Refresh status of latest firmware by
ACTION=»refresh» # fetching it from fw-update.ubnt.com
shift
;;
-s|—status) # Read latest firmware status from cache
ACTION=»status»
shift
;;
-u|—upgrade) # Upgrade to latest firmware
ACTION=»upgrade»
shift
;;
-c|—channel) # Target channel (release or public-beta)
CHANNEL=»$2″
shift
shift
;;
*) # Ignore unknown arguments
shift
;;
esac
done
# …
upgrade_firmware() {
# Fetch version number of latest firmware
echo -n «Fetching version number of latest firmware… «
refresh_status_file @> /dev/null
# Parse status file
local fw_version=`cat $STATUS_FILE | jq -r .version 2> /dev/null` || fw_version=»»
local fw_url=`cat $STATUS_FILE | jq -r .url 2> /dev/null` || fw_version=»»
local fw_md5=`cat $STATUS_FILE | jq -r .md5 2> /dev/null` || fw_version=»»
local fw_state=`cat $STATUS_FILE | jq -r .state 2> /dev/null` || fw_version=»»
if [ -z «$fw_version» ] || [ «$fw_url» = «$DEFAULT_URL» ]; then
echo «failed»
exit 42
else
echo «ok»
echo » > version : $fw_version»
echo » > url : $fw_url»
echo » > md5 : $fw_md5″
echo » > state : $fw_state»
echo
fi
if [ «$fw_state» == «can-upgrade» ]; then
echo «New firmware $fw_version is available»
echo
sudo /usr/bin/ubnt-upgrade —upgrade-force-prompt «$fw_url»
elif [ «$fw_state» == «up-to-date» ]; then
echo «Current firmware is already up-to-date (!!!)»
echo
sudo /usr/bin/ubnt-upgrade —upgrade-force-prompt «$fw_url»
elif [ «$fw_state» == «reboot-needed» ]; then
echo «Reboot is needed before upgrading to version $fw_version»
else
echo «Upgrade is already in progress»
fi
}
#——————————————————————————-
if [ «$ACTION» == «refresh» ]; then
refresh_status_file
elif [ «$ACTION» == «status» ]; then
read_status_file
elif [ «$ACTION» == «upgrade» ]; then
upgrade_firmware
fi

view rawCVE-2021-22909-snippet-2.bash hosted with ❤ by GitHub

The function proceeds to parse and compare the results from the server with the current firmware version. If an update is available, the script will invoke ubnt-upgrade to fetch the firmware from the fw-download.ubnt.com domain provided by the upgrade server. It will then perform the actual firmware upgrade.

The Bug — ZDI-21-601

The issue lies in the way the /usr/bin/ubnt-upgrade bash script downloads the firmware. The get_tar_by_url() function uses the curl command to perform the fetch. However, the developers specified the -k option (also known as the –insecure option), which disables certificate verification for TLS connections.

get_tar_by_url ()
{
mkdir $TMP_DIR
if [ «$NOPROMPT» -eq 0 ]; then
echo «Trying to get upgrade file from $TAR»
fi
if [ -n «$USERNAME» ]; then
auth=»-u $USERNAME:$PASSWORD»
else
auth=»»
fi
filename=»${TMP_DIR}/${TAR##*/}»
if [ «$NOPROMPT» -eq 0 ]; then
curl -k $auth -f -L -o $filename $TAR # <——
else
curl -k $auth -f -s -L -o $filename $TAR # <——
fi
if [ $? -ne 0 ]; then
echo «Unable to get upgrade file from $TAR»
rm -f $filename
rm -f $DOWNLOADING
exit 1
fi
if [ ! -e $filename ]; then
echo «Download of $TAR failed»
rm -f $DOWNLOADING
exit 1
fi
if [ «$NOPROMPT» -eq 0 ]; then
echo «Download succeeded»
fi
TAR=$filename
}

view rawCVE-2021-22909-snippet-3.bash hosted with ❤ by GitHub

Since /usr/sbin/ubnt-upgrade does not check for the validity of the certificate, an attacker can use a self-signed certificate to spoof the fw-download.ubnt.com domain without triggering any warnings or complaints on the device to alert the user. This vulnerability significantly reduces the skill barrier needed to launch a successful attack.

To exploit this vulnerability, the attacker can modify an existing EdgeRouter firmware image and fix up the checksum contained in the file. In the submitted proof-of-concept, the researcher modified the rc.local file to connect back to the attacker with a reverse shell. A reboot is part of the upgrade process, triggering the rc.local script.

Conclusion

If an attacker inserts themselves as MiTM, they can then impersonate the `fw-download.ubnt.com` domain controlled by Ubiquiti. However, to successfully serve up malicious firmware from this domain, the attackers would normally need to obtain a valid certificate with private key for the domain. To proceed, the attackers would probably need to hack into Ubiquiti or convince a trusted certificate authority (CA) to issue the attackers a certificate for the Ubiquiti domain, which is no insignificant feat. However, due to this bug, there is no need to obtain the certificate.

The heart of the problem is the lack of authentication on the firmware binary. The function of a secure communications channel is to provide confidentiality, integrity, and authentication. In TLS, encryption provides confidentiality, a message digest (or AEAD in the case of TLS 1.3) provides integrity, and certificate verification provides authentication. Without the verification of certificates, clients are foregoing authentication in the communications channel. In this scenario, it is possible for the client to be speaking to a malicious actor “securely”, as it were.

It should also be noted how checksums are not replacements for cryptographic signatures. Checksums can help to detect random errors in transmission but do not provide hard proof of data authenticity.

One final consideration is the possibility that a vendor’s website could become compromised. In that case, the firmware along with its associated hash could both be replaced with malicious versions. This situation can be mitigated only by applying a cryptographic signature to the firmware file itself. Perhaps Ubiquity will make the switch to signing their firmware binaries cryptographically, which would improve the overall security of its customers.

Ubiquity addressed this bug in their v2.0.9-hotfix.1 security update by removing the -k (--insecure) flag from the templated command.

This was the first submission to the program from awxylitol, and we hope to see more research from them in the future. Until then, you can find me on Twitter @TrendyTofu, and follow the team for the latest in exploit techniques and security patches.

Footnote

Cautious users of the EdgeRouter seeking advice on how to upgrade the device properly should avoid the use of automatic upgrade feature for this update. They may want to download the firmware file manually from a browser and verify the hashes of the firmware before performing a manual upgrade by uploading the firmware file to the device through the web interface.