CVE-2024-21786
An OS command injection vulnerability exists in the web interface configuration upload functionality of MC Technologies MC LR Router 2.10.5. A specially crafted HTTP request can lead to arbitrary command execution. An attacker can make an authenticated HTTP 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.
MC Technologies MC LR Router 2.10.5 (QEMU)
MC LR Router - https://mc-technologies.com/en/produkt/100800/
7.2 - CVSS:3.1/AV:N/AC:L/PR:H/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 MC LR Router from MC Technologies comes in two-port and four-port variants, as well as models that support transparent serial-to-TCP translations and 1-in/1-out digital I/O. The router boasts support for IPsec and and OpenVPN VPN implementations, firewall capabilities, remote management via HTTP and SNMP, and configurable alerting via SMS and email.
The MC LR Router can be configured via a locally-hosted HTTP interface. The HTTP server in use is busybox’s httpd, hosting a variety of cgi-bin files written in a mix of C and shell scripts. There is an authenticated OS command injection vulnerabilities in the cgi-bin file responsible for handling configuration file import and export functionality. During the handling of this request, the user-provided filename
parameter will be injected directly into an OS command that is called with root privileges via calls to run_shell
, which is a thin wrapper over system
.
The vulnerable file is located at /wbm/cgi-bin/p/adm/cfg
and is accessible via HTTP at the URI /cgi-bin/p/adm/cfg
. The vulnerable function begins at offset 0x9190
, which we refer to as form_handler
. This function is responsible for handling HTTP requests made to the cfg
endpoint. A partial, annotated decompilation is included below for reference. Prior to reviewing the below function, it is important to note that this function can (and in this case, must) be called multiple times per request, with varying arguments. The first chunk of code shown is the initial execution, when HTTP parameters are extracted and parsed and the global state of the request is updated. Following that we will cover a subsequent call, where the vulnerability occurs, as the request is being finalized. Technically, both of these chunks of code originate within the same monolithic function, but for readability we have split them up.
int form_handler(
cgi_t* cgi,
param_t* params,
int num_params,
REQUEST_TYPE req_type
) {
// [1] The vulnerable request must have a content-type of multipart/form-data
if (req_type == MULTIPART_FORM_DATA)
{
if (num_params > 1)
{
char* l_filename = NULL;
param_t* current_param = params;
int num_params_parsed = 1;
int is_upload = 0;
do {
// [2] Skip the first parameter, which for `multipart/form-data` is always the "boundary" parameter
param_t* current_param = ¤t_param[1];
if (current_param != 0 && strcasecmp(current_param->ptr->key, "Content-Disposition") == 0)
{
// [3] Extract, validate, and duplicate the provided filename parameter, storing it into the local variable `l_filename`
if (l_filename == 0 && strcmp(current_param->key, "filename") == 0)
{
char* filename = strdup(valid_filename(current_param->value));
if (filename)
{
l_filename = filename;
}
else
{
free(filename);
}
}
// [4] Alternatively, if the parameter was `name` we recover the type of submission -
else if (strcmp(current_param->key, "name") == 0)
{
if (strcmp(current_param->value, "cfg_submit") == 0)
{
cgi->is_submit = 1;
}
else
{
if (strcmp(current_param->value, "cfg_upload") == 0)
{
is_upload = 1;
}
...
}
}
}
current_param = ¶ms[++num_parsed_params];
} while (num_params_parsed != num_params);
if (l_filename)
{
if (is_upload == 1)
{
cgi->filename = l_filename;
}
else
{
free(l_filename);
}
}
... // Code related to storing uploaded file into a temporary file
}
A pre-condition of this request is enforced at [1]
, ensuring that the Content-Type
of the request is multipart/form-data
. If the condition is met, then it is assumed that the first parameter is the boundary and can be safely skipped. From there, steps [2]
, [3]
, and [4]
iterate over the provided parameters, updating various fields of the structure we refer to as cgi
. Of significant importance to this vulnerability is the cgi->filename
field.
On subsequent calls to this function, after all necessary fields of cgi
have been appropriately updated, the actions associated with submitting a configuration file are executed. Those actions occur in the portion of the function decompilation included below.
...
if (cgi->is_submit)
{
if (cgi->filename) == 0)
{
html_printf(cgi->is_submit, "<pre>%s</pre>", "missing filename");
}
else
{
// [5] Store a pointer to the file extension of the provided filename into `file_extension` (including prefix period)
char* file_extension = strrchr(cgi->filename, '.');
char* basedir;
char* extension;
if (file_extension == 0)
{
basedir = cgi->basedir;
file_type = &'\0';
}
else if (file_extension == -1)
{
file_type = &'\0';
}
else
{
// [6] If the file extension was found, store a pointer to the non-period-prefixed file extension into `file_type`
// e.g. If `file_extension` points to `.xml` then `file_type` will be `xml`
basedir = cgi->basedir;
file_type = &file_extension[1];
}
// [7] Finally, construct and execute an OS command with root privileges
// `run_shell` is a variadic utility wrapper around `system`
run_shell(0x1000, "/usr/sbin/import_cfg /tmp/cfg_import %s/new_config %s", basedir, file_type);
...
}
...
}
...
}
...
}
...
}
At [5]
, an attempt to recover the right-most file extension is made, and, if successful, a pointer to the file type string (commonly “xml” or “tgz”) is stored into file_type
. This file_type
variable is both user-controlled via HTTP POST parameter, and used without sanitization or validation in the construction of an OS command that will be executed with root privileges. An authenticated attacker who submits a multipart/form-data
POST request to the vulnerable endpoint can cause an arbitrary OS command to be executed, so long as that command follows the final period of the parameter.
2024-03-07 - Initial Vendor Contact
2024-03-12 - Vendor Disclosure
2024-04-02 - Request confirmation of receipt
2024-05-15 - Announcement of upcoming 90 day period expiration sent to vendor
2024-06-04 - Phone call to vendor. Vendor Disclosure sent to different recipient.
2024-06-24 - Email from vendor. Content unclear. Matter potentially resolved.
2024-07-02 - Status request sent to vendor
2024-08-21 - Announcement of upcoming 90 day period expiration sent to vendor
2024-09-03 - Upcoming release announcement
2024-11-21 - Public Release
Discovered by Matt Wiseman of Cisco Talos.