CVE-2023-22299
An OS command injection vulnerability exists in the vtysh_ubus _get_fw_logs functionality of Milesight UR32L v32.3.0.5. A specially crafted network request can lead to command execution. An attacker can send a network request 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.
Milesight UR32L v32.3.0.5
UR32L - https://www.milesight-iot.com/cellular/router/ur32l/
8.8 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE-78 - Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)
The Milesight UR32L is an industrial cellular router. The router features include support for multiple VPNs, a router console shell, firewall and many others.
The Milesight router offers several functionalities through the /cgi
endpoint. The “core” functionality we are considering is called yruo_debug_firewall
. In this “core” there is a function called “get”. The function that manages this functionality is the vtysh_ubus
’s fw_logs_get
function:
void fw_logs_get(undefined4 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4,
undefined4 *data)
{
[... variable declaration ...]
[... variable initialization ...]
json_msg_output("!! yruo_fw_logs.get params",data);
blob_buf_init(b,0);
data_len = __bswapsi2(*data);
blobmsg_parse(fw_logs_get_policy,2,tb,data + 1,(data_len & 0xffffff) - 4); [1]
if (tb[0] != (blob_attr *)0x0) {
base_value_ptr = (char *)blobmsg_get_string(tb[0]);
strncpy(tb_base_value,base_value_ptr,0x20);
system_ptr = (system_base_struct *)get_handler_bybase(&debug_bases,1,tb_base_value); [2]
if (system_ptr != (system_base_struct *)0x0) {
switch_to_enable_node();
_get_fw_logs = (code *)system_ptr->get_func;
if ((_get_fw_logs != (code *)0x0) &&
(_get_fw_logs = (code *)(*_get_fw_logs)(tb,"get"), _get_fw_logs == (code *)0x0)) [3]
[...]
[...]
}
}
}
[...]
}
The data are transmitted through blobmsg
structures. The two variable that are parsed into the tb
array, at [1]
, are:
- base
: this value must be equal to “firewall_log”, otherwise no functionality will be performed. The value of this data will be parsed in the tb[0]
variable.
- command
: this value will be used as argument of the iptables
shell command. The value of this data will be parsed in the tb[1]
variable.
At [2]
the base
value will be used to get a struct handler that in the code is called system_ptr
. Because there is only, in this case, one valid value for base
, we know the struct fetched has as value of the system_ptr->get_func
the _get_fw_logs
function pointer. At [3]
the _get_fw_logs
function will be called:
void _get_fw_logs(blob_attr **tb, char* type)
{
[... variable declaration ...]
[... variable initialization ...]
= (undefined4 *)zcalloc(1,8);
if (two_word != (undefined4 *)0x0) {
command = tb[1]; [4]
[...]
if ((command != (blob_attr *)0x0) &&
(command_string = (char *)blobmsg_get_string(command), *command_string != '\0')) {
dup_command = zstrdup(1,command_string);
[...]
command_string = (char *)(dup_command + -1);
do {
command_string = command_string + 1;
command_string_cursor = *command_string;
if (command_string_cursor == '\0') {
snprintf(iptables_command,0x100,"iptables -w %s",dup_command); [5]
zlog_debug("exec fw command(%s)\n",iptables_command);
__stream = popen(iptables_command,"r"); [6]
[...]
}
[...]
}
} while (command_string_cursor != '&' && command_string_cursor != ';');
[...]
}
}
[...]
} This function takes as first argument the `fw_logs_get`'s `tb` variable pointer. Then, at `[4]`, `tb[1]` is used to fetch the `command` argument sent in the `/cgi` API. The `command` variable is used, at `[5]`, to compose the `iptables -w <command>` string. This is then used at `[6]` as the argument for the `popen` function, effectively executing the `iptables -w <command>` shell command.
The payload for the /cgi
API to execute the “get” function in the “yruo_debug_firewall” core would look likes this:
{
"id":60,
"execute":10,
"core":"yruo_debug_firewall",
"function":"get",
"values":[
{
"base":"firewall_log",
"command":"-S",
}
]
}
This would execute the iptables -W -S
command listing all the iptables rules.
Because no exhaustive checks are performed on the command
parameters until it reaches the popen
function, this leads to an OS command injection vulnerability.
Since the maintainer of this software did not release a patch during the 90 day window specified in our policy, we have now decided to release the information regarding this vulnerability, to make users of the software aware of this problem. See Cisco’s Coordinated Vulnerability Disclosure Policy for more information: https://tools.cisco.com/security/center/resources/vendor_vulnerability_policy.html
2023-02-14 - Initial Vendor Contact
2023-02-21 - Vendor Disclosure
2023-07-06 - Public Release
Discovered by Francesco Benvenuto of Cisco Talos.