CVE-2022-29472
An OS command injection vulnerability exists in the web interface util_set_serial_mac functionality of Abode Systems, Inc. iota All-In-One Security Kit 6.9X and 6.9Z. A specially-crafted HTTP request can lead to arbitrary command execution. An attacker can send an 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.
abode systems, inc. iota All-In-One Security Kit 6.9X
abode systems, inc. iota All-In-One Security Kit 6.9Z
iota All-In-One Security Kit - https://goabode.com/product/iota-security-kit
10.0 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
CWE-78 - Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)
The iota All-In-One Security Kit is a home security gateway containing an HD camera, infrared motion detection sensor, Ethernet, WiFi and Cellular connectivity. The iota gateway orchestrates communications between sensors (cameras, door and window alarms, motion detectors, etc.) distributed on the LAN and the Abode cloud. Users of the iota can communicate with the device through mobile application or web application.
The iota
device is configured at the factory with a unique serial number and MAC address. These unique values are stored into the bootargs
variable in the U-Boot environment, which is ultimately passed to the kernel as its command line.
The device exposes a method for initializing or modifying these values through the /action/factorySerialMacPost
endpoint, which relies on an underlying function titled utils_set_serial_mac
to make the underlying configuration change to the U-Boot environment.
In order to enable the web interface either TALOS-2022-1552 or TALOS-2022-1553 may be used, and access to this endpoint can be conducted without knowledge of the username or password using TALOS-2022-1554.
When an HTTP request is submitted to /action/factorySerialMacPost
it will reach the designated handler function located at offset 0x19BEBC
of the /root/hpgw
included in firmware 6.9Z. The relevant portions of the decompilation of this function have been included, with annotations, below.
int __fastcall factorySerialMacPost(mg_connection *conn, mg_request_info *ri)
{
int payload_len;
unsigned int idx;
_BYTE *mac;
unsigned __int8 *mac;
char serial_no[64];
char mac_addr[64];
int abode_code[16];
char payload[280];
payload_len = http_collect_payload(conn, ri, payload, 256);
memset(serial_no, 0, sizeof(serial_no));
// [1] Extract the `serial` value from the POST payload
mg_get_var(payload, payload_len, "serial", serial_no, 0x3F);
memset(mac_addr, 0, sizeof(mac_addr));
mg_get_var(payload, payload_len, "mac", mac_addr, 0x3F);
// [2] If `serial` (and `mac`) parameters exist
if ( serial_no[0] && mac_addr[0] )
{
...
// [3] Call the vulnerable function
util_set_serial_mac(serial_no, mac);
memset(abode_code, 0, sizeof(abode_code));
mg_get_var(payload, payload_len, "code", abode_code, 0x3F);
util_set_abode_code(abode_code);
sync();
sync();
sync();
return web_success(conn);
}
else
{
err_str = strtable_get("WEB_ERR_PARAM_EMPTY", 19);
return web_error(conn, 0, err_str, "Serial or Mac");
}
}
At [1]
the serial
and mac
parameters are extracted from the HTTP request and checked at [2]
for their existence.
If they exist, then at [3]
they are passed into the vulnerable util_set_serial_mac
function.
The util_set_serial_mac
function is at offset 0xA89E8
of the /root/hpgw
binary in version 6.9Z. It expects two arguments, the serial_no
and mac
of the device, and uses the helper binaries of fw_printenv
and fw_setenv
to read and write to the U-Boot bootargs. The relevant portions of the decompiled function are included below.
int __fastcall util_set_serial_mac(char *serial_no, char *mac)
{
char *original;
char *key;
size_t modified_size;
size_t v8;
char *value;
char *offset;
char *v11;
size_t v12;
size_t v13;
size_t v14;
size_t v15;
size_t v16;
size_t v17;
size_t v19;
size_t v20;
size_t v21;
size_t v22;
char bootargs[512];
char v24[512];
char modified[512];
char command[528];
if ( *serial_no && *mac )
{
if ( !dir_exists("/var/lock") )
mkdir("/var/lock", 0x1FDu);
memset(bootargs, 0, sizeof(bootargs));
// [1] Extract the current `bootargs` setting using `fw_printenv`
if ( popen_read("/gm/tools/env/fw_printenv -n bootargs", bootargs, 511) > 0 && bootargs[0] )
{
bootargs[strcspn(bootargs, "\n")] = 0;
memset(modified, 0, sizeof(modified));
for ( original = bootargs; ; original = 0 )
{
// [2] tokenize each boot arg by splitting on spaces (format is `key_1=value_1 key_2=value_2 ...`)
key = strtok(original, " ");
if ( !key )
break;
if ( startswith(key, "ethaddr=") && *mac )
{
end = strlen(modified);
strncpy(&modified[end], "ethaddr=", 511);
end = strlen(modified);
value = mac;
strncpy(&modified[end], value, 511);
offset = &modified[end];
}
// [3] If the key is the `climax_product` key AND a `serial_no` was provided
else if ( startswith(key, "climax_product=") && *serial_no )
{
// [4] Then update the modified line with a new key=value setting the `climax_product` to the `serial_no`
end = strlen(modified);
strncpy(&modified[end], "climax_product=", 511);
end = strlen(modified);
value = serial_no;
offset = &modified[end];
}
else // [5] If it's any other key, just prepare to append it to the `modified` line with no changes
{
end = strlen(modified);
value = key;
offset = &modified[end];
}
// [6] Append whatever `key=value` was configured earlier and add a space in preparation for the next k/v pair
strncpy(offset, value, 511);
end = strlen(modified);
strncpy(&modified[end], " ", 511);
}
v11 = &command[strlen(modified) + 511];
if ( *(v11 - 1024) == 32 )
*(v11 - 1024) = 0;
LABEL_21:
memset(command, 0, 0x200u);
// [7] Construct the call to `fw_setenv` to replace old bootargs with the modified bootargs
vsnprintf_nullterm(command, 0x1FFu, "/gm/tools/env/fw_setenv bootargs %s", modified);
// [8] Call the process via `popen`
popen_write(command);
return 0;
}
log(4, 20, "\x1B[33mutil_set_serial_mac failed on fw_printenv or no bootargs\x1B[0m");
memset(v24, 0, sizeof(v24));
if ( popen_read("/gm/tools/env/fw_printenv -n cmd2", v24, 511) > 0 && v24[0] )
{
v24[strcspn(v24, "\n")] = 0;
memset(modified, 0, sizeof(modified));
strncpy_0(modified, v24, 511);
if ( *mac )
{
v12 = strlen(modified);
strncpy_0(&modified[v12], " ", 511);
v13 = strlen(modified);
strncpy_0(&modified[v13], "ethaddr=", 511);
v14 = strlen(modified);
strncpy_0(&modified[v14], mac, 511);
}
if ( *serial_no )
{
v15 = strlen(modified);
strncpy_0(&modified[v15], " ", 511);
v16 = strlen(modified);
strncpy_0(&modified[v16], "climax_product=", 511);
v17 = strlen(modified);
strncpy_0(&modified[v17], serial_no, 511);
}
goto LABEL_21;
}
log(4, 20, "\x1B[33mutil_set_serial_mac failed on fw_printenv or no cmd2\x1B[0m");
}
return -1;
}
This function works by [1]
extracting the current bootargs
value from the U-Boot env by calling /gm/tools/env/fw_printenv -n bootargs
. On the device under test, this returns mem=128M gmmem=90M console=ttyS0,115200 user_debug=31 init=/squashfs_init root=/dev/mtdblock2 rootfstype=squashfs ethaddr=B0:C5:CA:XX:XX:XX climax_product=Z3,XXXX,YYYY,ZZZZ
.
It then [2]
tokenizes the value by splitting on space characters. The function begins iterating over each key=value pair and checks if the pair is one of either ethaddr
or climax_product
. If the current key/value pair is not of interest [6]
, it is appended directly to the modified
buffer without change.
If the key is climax_product
[5]
then the entire pair is replaced with the new climax_product={serial_no}
.
Finally, after reconstructing the modified bootargs, the modified
value is used to construct [7]
a command that will be executed using a call to popen
at [8]
.
If an attacker submits a serial
value properly formatted to escape the vulnerable call at [7]
to the /action/factorySerialMacPost
endpoint, then the injected command will be executed by the root user.
POST /action/factorySerialMacPost HTTP/1.1
Host: 10.1.1.201
X-Climax-Tag: Factory-27940001-21245121
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close
Content-Length: 1326
mac=b0:c5:ca:00:00:00&serial=%3b+sleep+10+%23
2022-07-14 - Vendor Disclosure
2022-09-26 - Vendor Patch Release
2022-10-20 - Public Release
Discovered by Matt Wiseman of Cisco Talos.