CVE-2017-14452, CVE-2017-14453, CVE-2017-14454, CVE-2017-14455
Multiple exploitable buffer overflow vulnerabilities exists in the PubNub message handler for the “control” channel of Insteon Hub running firmware version 1012. Specially crafted replies received from the PubNub service can cause buffer overflows on a global section overwriting arbitrary data. An attacker should impersonate PubNub and answer an HTTPS GET 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-120: Buffer Copy without Checking Size of Input (‘Classic 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 “control”: sub_9d053904
. The function has a similar signature to the callbacks defined in PubNub’s SDK.
A peculiarity of this function is that it’s only called after factory reset, in order to configure the device: a request is made to PubNub to retrieve channels and a key to use during normal operation, and PubNub should answer with a “JSON” containing these information. Example:
- device requests: https://prod.insteon.pubnub.com/subscribe/sub-c-e1c54032-1685-11e4-b69f-02ee2ddab7fe/HUB2-112233-control/0/1234567890?uuid=HUB2-112233&auth=00112233445566778899aabbccddeeff
- PubNub answers:
[
[{
"cc": "112233-cc",
"cc_r": "112233-cc_r",
"ad": "112233-ad",
"ad_r": "112233-ad_r",
"al": "hub2-alert",
"ak": "AABBCCDDAABBCCDDAABBCCDDAABBCCDD"
}], "0987654321"]
Note that “ak” is an authentication key represented as 16 hex-encoded bytes, while “112233” corresponds to the insteon-id (lower 3 octets of the MAC address).
As we can see, PubNub answers with a “JSON” file containing 6 elements. The function sub_9d053904
simply has to parse this string and save it into a global object for later use.
seg000:9D053904 # void __cdecl sub_9D053904(void *pubnub_obj, int state, int http_code, char *channel, char *response, char *msgid, void *cb_data)
seg000:9D053904 sub_9d053904:
seg000:9D053904
seg000:9D053904 src = -0x48
seg000:9D053904 var_44 = -0x44
seg000:9D053904 var_40 = -0x40
seg000:9D053904 var_38 = -0x38
seg000:9D053904 pubnub_obj= -0x34
seg000:9D053904 channel_cc_ptr= -0x30
seg000:9D053904 var_28 = -0x28
seg000:9D053904 var_24 = -0x24
seg000:9D053904 var_20 = -0x20
seg000:9D053904 var_1C = -0x1C
seg000:9D053904 var_18 = -0x18
seg000:9D053904 var_14 = -0x14
seg000:9D053904 var_10 = -0x10
seg000:9D053904 var_C = -0xC
seg000:9D053904 var_8 = -8
seg000:9D053904 var_4 = -4
seg000:9D053904 response= 0x10
seg000:9D053904 msgid = 0x14
seg000:9D053904 cb_data = 0x18
seg000:9D053904
seg000:9D053904 000 A8 FF BD 27 addiu $sp, -0x58
...
seg000:9D053930 058 21 80 80 00 move $s0, $a0 # [1]
...
seg000:9D053938 058 68 00 B2 8F lw $s2, 0x58+response($sp) # [2]
...
seg000:9D05397C 058 00 00 42 82 lb $v0, 0($s2)
seg000:9D053980 058 94 00 40 50 beqzl $v0, loc_9D053BD4 # [3]
...
seg000:9D053988 058 B7 43 41 0F jal cJSON_Parse # [4]
seg000:9D05398C 058 21 20 40 02 move $a0, $s2
seg000:9D053990 058 21 90 40 00 move $s2, $v0
seg000:9D053994 058 21 20 40 00 move $a0, $v0
seg000:9D053998 058 08 9D 05 3C lui $a1, 0x9D08
seg000:9D05399C 058 F3 43 41 0F jal cJSON_GetObjectItem # [5]
seg000:9D0539A0 058 3C 9D A5 24 la $a1, aCc # "cc"
seg000:9D0539A4 058 21 A8 40 00 move $s5, $v0
seg000:9D0539A8 058 21 20 40 02 move $a0, $s2
seg000:9D0539AC 058 08 9D 05 3C lui $a1, 0x9D08
seg000:9D0539B0 058 F3 43 41 0F jal cJSON_GetObjectItem # [6]
seg000:9D0539B4 058 40 9D A5 24 la $a1, aCc_r # "cc_r"
seg000:9D0539B8 058 21 B8 40 00 move $s7, $v0
seg000:9D0539BC 058 21 20 40 02 move $a0, $s2
seg000:9D0539C0 058 08 9D 05 3C lui $a1, 0x9D08
seg000:9D0539C4 058 F3 43 41 0F jal cJSON_GetObjectItem # [7]
seg000:9D0539C8 058 48 9D A5 24 la $a1, aAd # "ad"
seg000:9D0539CC 058 21 B0 40 00 move $s6, $v0
seg000:9D0539D0 058 21 20 40 02 move $a0, $s2
seg000:9D0539D4 058 08 9D 05 3C lui $a1, 0x9D08
seg000:9D0539D8 058 F3 43 41 0F jal cJSON_GetObjectItem # [8]
seg000:9D0539DC 058 4C 9D A5 24 la $a1, aAd_r # "ad_r"
seg000:9D0539E0 058 21 F0 40 00 move $fp, $v0
seg000:9D0539E4 058 21 20 40 02 move $a0, $s2
seg000:9D0539E8 058 08 9D 05 3C lui $a1, 0x9D08
seg000:9D0539EC 058 F3 43 41 0F jal cJSON_GetObjectItem # [9]
seg000:9D0539F0 058 54 9D A5 24 la $a1, aAl # "al"
seg000:9D0539F4 058 20 00 A2 AF sw $v0, 0x58+var_38($sp)
seg000:9D0539F8 058 21 20 40 02 move $a0, $s2
seg000:9D0539FC 058 08 9D 05 3C lui $a1, 0x9D08
seg000:9D053A00 058 F3 43 41 0F jal cJSON_GetObjectItem # [10]
seg000:9D053A04 058 58 9D A5 24 la $a1, aAk # "ak"
seg000:9D053A08 058 67 00 A0 12 beqz $s5, loc_9D053BA8
seg000:9D053A0C 058 21 A0 40 00 move $s4, $v0
seg000:9D053A10 058 66 00 E0 52 beqzl $s7, loc_9D053BAC
seg000:9D053A14 058 A4 00 05 8E lw $a1, pubnub.channel($s0)
seg000:9D053A18 058 64 00 C0 52 beqzl $s6, loc_9D053BAC
seg000:9D053A1C 058 A4 00 05 8E lw $a1, pubnub.channel($s0)
seg000:9D053A20 058 61 00 C0 13 beqz $fp, loc_9D053BA8
seg000:9D053A24 058 20 00 A2 8F lw $v0, 0x58+var_38($sp)
seg000:9D053A28 058 60 00 40 50 beqzl $v0, loc_9D053BAC
seg000:9D053A2C 058 A4 00 05 8E lw $a1, pubnub.channel($s0)
seg000:9D053A30 058 5E 00 80 52 beqzl $s4, loc_9D053BAC
seg000:9D053A34 058 A4 00 05 8E lw $a1, pubnub.channel($s0)
seg000:9D053A38 058 32 E2 41 0F jal strlen
seg000:9D053A3C 058 10 00 A4 8E lw $a0, 0x10($s5) # [11]
seg000:9D053A40 058 21 80 40 00 move $s0, $v0
seg000:9D053A44 058 32 E2 41 0F jal strlen
seg000:9D053A48 058 10 00 C4 8E lw $a0, 0x10($s6) # [12]
seg000:9D053A4C 058 21 80 02 02 addu $s0, $v0
seg000:9D053A50 058 02 00 10 26 addiu $s0, 2
seg000:9D053A54 058 20 00 10 2E sltiu $s0, 32 # [13]
seg000:9D053A58 058 48 00 00 12 beqz $s0, loc_9D053B7C
...
seg000:9D053A8C 058 48 00 22 26 addiu $v0, $s1, insteon_pubnub.channel_cc
seg000:9D053A90 058 28 00 A2 AF sw $v0, 0x58+channel_cc_ptr($sp)
seg000:9D053A94 058 21 20 40 00 move $a0, $v0
seg000:9D053A98 058 1F DF 41 0F jal strcpy # [14]
seg000:9D053A9C 058 10 00 A5 8E lw $a1, 0x10($s5)
seg000:9D053AA0 058 58 00 35 26 addiu $s5, $s1, insteon_pubnub.channel_ad
seg000:9D053AA4 058 21 20 A0 02 move $a0, $s5
seg000:9D053AA8 058 1F DF 41 0F jal strcpy # [15]
seg000:9D053AAC 058 10 00 C5 8E lw $a1, 0x10($s6)
seg000:9D053AB0 058 68 00 36 26 addiu $s6, $s1, insteon_pubnub.channel_cc_r
seg000:9D053AB4 058 21 20 C0 02 move $a0, $s6
seg000:9D053AB8 058 1F DF 41 0F jal strcpy # [16]
seg000:9D053ABC 058 10 00 E5 8E lw $a1, 0x10($s7)
seg000:9D053AC0 058 78 00 37 26 addiu $s7, $s1, insteon_pubnub.channel_ad_r
seg000:9D053AC4 058 21 20 E0 02 move $a0, $s7
seg000:9D053AC8 058 1F DF 41 0F jal strcpy # [17]
seg000:9D053ACC 058 10 00 C5 8F lw $a1, 0x58+src($fp)
seg000:9D053AD0 058 88 00 30 26 addiu $s0, $s1, insteon_pubnub.channel_al
seg000:9D053AD4 058 21 20 00 02 move $a0, $s0
seg000:9D053AD8 058 20 00 A2 8F lw $v0, 0x58+var_38($sp)
seg000:9D053ADC 058 1F DF 41 0F jal strcpy # [18]
seg000:9D053AE0 058 10 00 45 8C lw $a1, 0x10($v0)
seg000:9D053AE4 058 D8 00 33 26 addiu $s3, $s1, insteon_pubnub.channel_ak
seg000:9D053AE8 058 21 20 60 02 move $a0, $s3
seg000:9D053AEC 058 1F DF 41 0F jal strcpy # [19]
seg000:9D053AF0 058 10 00 85 8E lw $a1, 0x10($s4)
The function stores at [1] the pointer to pubnub_obj
, which contains data used to communicate with the remote PubNub servers.
At [2] PubNub’s answer is saved into $s2
and at [3] the code ensures that the answer is not empty. The answer is then passed to cJSON_Parse
[4] and the resulting cJSON
object is saved in $s2
.
The function then retrieves every expected element from the “JSON” ([5], [6], [7], [8], [9], [10]) and ensures that they exist.
Each parameter is then copied into the global insteon_pubnub
structure (which is at 0xa000d1bc
) for later use. Note this structure is bigger than 0x1000 bytes and includes not only strings and callbacks pointers, but also 3 pubnub
structures that again contain several string and function pointers. This clearly makes this buffer a good target for exploitation.
As we can see at [14], [15], [16], [17], [18], [19], each parameter is copied unsafely using strcpy
and this might be used by an attacker to overwrite portions of the insteon_pubnub
structure and execute arbitrary code.
However, despite the fact that the strcpy
calls at [14] and [15] are unsafe, they turn out to be not exploitable, give the restriction imposed at [13]: the sum of the length of “cc” [11] and “ad” [12] must not exceed 29. This still allows for an overflow to occur but it will only overwrite a few bytes in the insteon_pubnub
structure that an attacker already normally controls.
The following is a list of vulnerable strcpy
calls and their Proof-of-Concept.
Each PoC shows only the payload portion of the request and uses the placeholder “OVERFLOW” to highlight the vulnerable parameter, which can be replaced with "A"*0x400
to make the device crash.
A key with value “x” means that its value is irrelevant.
The strcpy
at [16] overflows the buffer insteon_pubnub.channel_cc_r
, which has a size of 16 bytes.
An attacker can send an arbitrarily long “cc_r” parameter [6] in order to exploit this vulnerability:
[[{"cc":"x","cc_r":"{OVERFLOW}","ad":"x","ad_r":"x","al":"x","ak":"x"}],"1"]
The strcpy
at [17] overflows the buffer insteon_pubnub.channel_ad_r
, which has a size of 16 bytes.
An attacker can send an arbitrarily long “ad_r” parameter [8] in order to exploit this vulnerability:
[[{"cc":"x","cc_r":"x","ad":"x","ad_r":"{OVERFLOW}","al":"x","ak":"x"}],"1"]
The strcpy
at [18] overflows the buffer insteon_pubnub.channel_al
, which has a size of 16 bytes.
An attacker can send an arbitrarily long “al” parameter [9] in order to exploit this vulnerability:
[[{"cc":"x","cc_r":"x","ad":"x","ad_r":"x","al":"{OVERFLOW}","ak":"x"}],"1"]
The strcpy
at [19] overflows the buffer insteon_pubnub.channel_ak
, which has a size of 16 bytes.
An attacker can send an arbitrarily long “ak” parameter [10] in order to exploit this vulnerability:
[[{"cc":"x","cc_r":"x","ad":"x","ad_r":"x","al":"x","ak":"{OVERFLOW}"}],"1"]
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.