CVE-2024-39273
A firmware update vulnerability exists in the fw_check.sh functionality of Wavlink AC3000 M33A8.V5030.210505. A specially crafted HTTP request can lead to arbitrary firmware update. An attacker can perform a man-in-the-middle attack to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Wavlink AC3000 M33A8.V5030.210505
Wavlink AC3000 - https://www.wavlink.com/en_us/product/WL-WN533A8.html
9.0 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H
CWE-306 - Missing Authentication for Critical Function
The Wavlink AC3000 wireless router is predominately one of the most popular gigabit routers in the US, in part due to both its potential wireless and wired speed capabilities and extremely low price point (costing at the time of this writing ~$60 USD). Among the configuration options, it’s also able to act as a standalone wireless gateway, a basic network router, or a wireless repeater.
As part of the updating functionality of the Wavlink AC3000, there exists the sbin/fw_check.sh
script which is used to check for firmware updates. After NTP is setup correctly (which occurs immediately after the WAN side is connected) the sbin/schedule.sh
script adds fw_check.sh
to the crontab as such:
if [ "$mesh" = "2" ]; then
echo "0 4 * * * fw_check.sh" >> /var/spool/cron/crontabs/"$user"
else
echo "30 4 * * * fw_check.sh" >> /var/spool/cron/crontabs/"$user"
fi
Continuing within fw_check.sh
:
url1=`nvram_get 2860 FW_CheckLink1` // [1]
url2=`nvram_get 2860 FW_CheckLink2` // [2]
if [ "$url1" = "" ]; then
echo "url1 is null"
if [ "$url2" = "" ]; then
echo "url2 is null"
echo 0 > /tmp/update &
exit 0
fi
fi
echo 1 > /tmp/update &
curl -k -I -m 5 -o /dev/null -s -w %{http_code} "$url1" > /tmp/url1 // [3]
curl -k -I -m 5 -o /dev/null -s -w %{http_code} "$url2" > /tmp/url2 // [4]
#sleep 6
result=`cat /tmp/url1`
if [ "$result" != "200" ]; then
echo "url1 result:$result"
result=`cat /tmp/url2`
if [ "$result" != "200" ]; then
echo "url2 result:$result"
echo 0 > /tmp/update &
exit 0
else
echo "get js from $url2"
curl -s -k "$url2" > /tmp/fw.tmp // [5]
fi
else
echo "get js from $url1"
curl -s -k "$url1" > /tmp/fw.tmp // [6]
fi
To summarize, at [1] and [2], two URLs are grabbed from the nvram and read into bash variables. These URLS are by default as follows:
FW_CheckLink1=http://fw.iqs.link/firmware/router/WN533A8-WAVLINK-WO.js
FW_CheckLink2=http://fw.wavlink.com/firmware/router/WN533A8-WAVLINK-WO.js
Very important to note that these are both HTTP and not HTTPS, as that will allow us to actually perform a Man-In-The-Middle attack here. Continuing, at [3] and [4], both of these URLS are curled and the HTTP codes retrieved are stored into files. Assuming that either URL has a valid HTTP 200 return code, the contents of that web page are then stored into the /tmp/fw.tmp
file at [5] or [6]. Continuing on in fw_check.sh
:
fw_url=`cat /tmp/fw.tmp |grep FW_URL |sed 's/^.*="//g'|sed 's/".*$//g'`
fw_md5=`cat /tmp/fw.tmp |grep FW_MD5 |sed 's/^.*="//g'|sed 's/".*$//g'`
fw_update=`cat /tmp/fw.tmp |grep FW_UPDATE |sed 's/^.*="//g'|sed 's/".*$//g'`
FW_VER=`cat /tmp/fw.tmp |grep FW_VER |sed 's/^.*="//g'|sed 's/".*$//g'`
fw_version=`cat /tmp/fw.tmp |grep FW_VER |sed 's/^.*="//g'|sed 's/\..*$//g'`
now_version=`web 2860 sys sdkVersion |sed 's/\..*$//g'`
now_date=`web 2860 sys sdkVersion |sed 's/^.*\.//g'`
curl -k -I -m 5 -o /dev/null -s -w %{http_code} "$fw_url" > /tmp/url_firmware
if [ "$now_version" = "$fw_version" ]; then
echo "the right fw version for $fw_version"
if [ "$now_date" -lt "$fw_update" ]; then
echo 3 > /tmp/update &
# sleep 6
get_url=`cat /tmp/url_firmware`
if [ "$get_url" = "200" ]; then
echo "down load firmware from $fw_url"
curl -s -k "$fw_url" > /var/tmpFW // [5]
echo 4 > /tmp/update &
The script expects the webpage to have something of a format like var FW_URL="<...>";
, at which point it takes that value and curls it into the /var/tmpFW
file at [5] . Continuing in the same script:
md5=`openssl dgst -sha256 /var/tmpFW | sed 's/^.*= //g'`
size=`wc -c /var/tmpFW | sed -re 's/[^0-9]*([0-9]*).*$/\1/;'`
if [ "$md5" = "$fw_md5" ]; then // [6]
echo "update firmware $FW_VER $size"
killall -SIGHUP monitor&
gpio l 16 2 2 4000 0 4000
/bin/mtd_write -o 0 -l "$size" write /var/tmpFW Kernel // [7]
echo 5 > /tmp/update &
sleep 10
reboot
else
echo 0 > /tmp/update &
rm /tmp/fw.tmp &
rm /var/tmpFW &
echo "md5 is different, fw:$fw_md5, md5sum:$md5"
fi
Assuming that the sha256 sum of the firmware file matches that provided by the FW_MD5
variable listed in the curled web page from before, then we enter the branch at [6] and immediately flash this firmware with no further validation at [7]. Thus, if an attacker can perform a Man-In-The-Middle attack on the original HTTP request of fw_check.sh
, they can flash arbitrary firmware onto the device.
2024-07-25 - Initial Vendor Contact
2024-07-29 - Requesting reply from vendor
2024-07-30 - Vendor confirms receipt
2024-07-30 - Vendor Disclosure
2024-07-30 - Vendor confirms receipt
2024-09-02 - Status update request sent
2024-10-15 - Status update request. Upcoming expiration date announced.
2024-10-22 - Vendor replies product has been discontinued, but patches are being worked on
2024-11-04 - Status update request for patch release dates
2024-11-12 TALOS advisory release date announced
2025-01-14 - Public Release
Discovered by Lilith >_> of Cisco Talos.