Talos Vulnerability Report

TALOS-2024-2038

Wavlink AC3000 update_filter_url.sh argument injection vulnerability

January 14, 2025
CVE Number

CVE-2024-39604

SUMMARY

A command execution vulnerability exists in the update_filter_url.sh functionality of Wavlink AC3000 M33A8.V5030.210505. A specially crafted HTTP request can lead to arbitrary command execution. An attacker can perform a man-in-the-middle attack to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

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

PRODUCT URLS

Wavlink AC3000 - https://www.wavlink.com/en_us/product/WL-WN533A8.html

CVSSv3 SCORE

9.0 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-74 - Improper Neutralization of Special Elements in Output Used by a Downstream Component (‘Injection’)

DETAILS

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/update_filter_url.sh script which is used to update the URL’s for parental controls. The sbin/monitor_process.sh script, run every 30 seconds via crontab, runs this parental control script to check if there are any updates over WAN:

##################### update parental control url lib ########################
if [ "N"`nvram_get parents_ctl_en` == "N1" ] && [ "N"`cat /tmp/internetStatus` == "N1" ] && [ "N"`cat /tmp/url_filter_lib_state` != "N1" ]
then
    update_filter_url.sh&
fi
"sbin/monitor_process.sh"

Continuing into the update_filter_url.sh script:

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"
        exit 0
    fi
fi

curl -k -I -m 5 -o /dev/null -s -w %{http_code} "$url1" > /tmp/url_lib_url1  // [3]
curl -k -I -m 5 -o /dev/null -s -w %{http_code} "$url2" > /tmp/url_lib_url2  // [4]

result=`cat /tmp/url_lib_url1`

if [ "$result" != "200" ]; then
    echo "url1 result:$result"
    result=`cat /tmp/url_lib_url2`
    if [ "$result" != "200" ]; then
        echo "url2 result:$result"
        exit 0
    else
        echo "get js from $url2"
        curl -s -k "$url2" > /tmp/url_lib.tmp   // [5]
    fi
else
    echo "get js from $url1"
    curl -s -k "$url1" > /tmp/url_lib.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/url_lib.tmp file at [5] or [6]. Continuing on in update_filter_url.sh:

lib_url_tmp=`cat /tmp/url_lib.tmp|grep Filter_Url_lib |sed 's/^.*="//g'|sed 's/".*$//g'`  // [7]
[ `echo "$lib_url_tmp"|wc -c` -lt 5 ] && echo "Can not get url lib" && exit 0
lib_url_version="$lib_url_tmp""url_lib_version.txt"  // [8]
lib_url_data="$lib_url_tmp""url_lib.tar"
curl -s -k -m 20 $lib_url_version > /tmp/update_url_lib_version   // [9]

The script expects the webpage to have something of a format like var Filter_Url_lib="<...>", at which point it takes that value and throws it into the lib_url_tmp variable. This variable is joined with url_lib_version.txt at [8] and then a second curl command is made at [9] with this new file in the arguments. Unfortunately since there is no filtering on the contents of lib_url_tmp, and also because the initial URL is an insecure HTTP URL, if we perform a man-in-the-middle attack, we arbitrarily control the contents of lib_url_tmp and subsequently we have argument injection on the curl command. A simple way to leverage this into gaining access to the device would be to inject something like the “-o” flag, so we can arbitrarily overwrite any file on the filesystem, for instance /etc/passwd, quickly allowing us to escalate.

TIMELINE

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

Credit

Discovered by Lilith >_> of Cisco Talos.