Talos Vulnerability Report

TALOS-2018-0573

Samsung SmartThings Hub video-core Camera URL Replace Code Execution Vulnerability

July 26, 2018
CVE Number

CVE-2018-3902

Summary

An exploitable buffer overflow vulnerability exists in the camera "replace" feature of video-core's HTTP server of Samsung SmartThings Hub. The video-core process incorrectly extracts the URL field from a user-controlled JSON payload, leading to a buffer overflow on the stack. An attacker can send an HTTP request to trigger this vulnerability.

Tested Versions

Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17

Product URLs

https://www.smartthings.com/products/smartthings-hub

CVSSv3 Score

9.9 - CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-120: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')

Details

Samsung produces a series of devices aimed at controlling and monitoring a home, such as wall switches, LED bulbs, thermostats and cameras. One of those is the Samsung SmartThings Hub, a central controller which allows an end user to use their smartphone to connect to their house remotely and operate other devices through it. The hub board utilizes several systems on chips. The firmware in question is executed by an i.MX 6 SoloLite processor (Cortex-A9), which has an ARMv7-A architecture.

The firmware is Linux-based, and runs a series of daemons that interface with devices nearby via ethernet, ZigBee, Z-Wave and Bluetooth protocols. Additionally, the hubCore process is responsible for communicating with the remote SmartThings servers via a persistent TLS connection. These servers act as a bridge that allows for secure communication between the smartphone application and the hub. End users can simply install the SmartThings mobile application on their smartphone to control the hub remotely.

One of the features of the hub is that it connects to smart cameras, configures them and looks at their livestreams. For testing, we set up the Samsung SmartCam SNH-V6414BN on the hub. Once done, the livestream can be displayed by the smartphone application by connecting either to the remote SmartThings servers, or directly to the camera, if they're both in the same subnetwork.

Inside the hub, the livestream is handled by the video-core process, which uses ffmpeg to connect via RTSP to the smart camera in its same local network, and at the same time, provides a streamable link for the smartphone application.

The remote SmartThings servers have the possibility to communicate with the video-core process by sending messages in the persistent TLS connection, established by the hubCore process. These messages can encapsulate an HTTP request, which hubCore would relay directly to the HTTP server exposed by video-core. The HTTP server listens on port 3000, bound to the localhost address, so a local connection is needed to perform this request.

We identified a vulnerable request that can be exploited to achieve code execution on the video-core process, which is running as root. By sending a PUT request for the /cameras/<camera-id> path, it's possible to replace the URL of an existing camera.

Such request is handled by function sub_496C4:

.text:000496C4     sub_496C4
.text:000496C4
.text:000496C4     dest   = -0x2D0C
.text:000496C4     var_2D00= -0x2D00
.text:000496C4     var_2CC0= -0x2CC0
.text:000496C4     var_2B00= -0x2B00
.text:000496C4     var_2AC0= -0x2AC0
.text:000496C4     var_2040= -0x2040
.text:000496C4     var_2000= -0x2000
.text:000496C4     src    =  4
.text:000496C4     value_size=  8
.text:000496C4     arg_8  =  0xC
.text:000496C4
.text:000496C4 000        STMFD           SP!, {R4-R11,LR}
.text:000496C8 024        ADD             R11, SP, #0x20
...
.text:00049744 2D18       BL              http_required_json_parameters    ; [1]
.text:00049748 2D18       CMP             R0, R6
.text:0004974C 2D18       BNE             loc_49770
...
.text:000497CC 000        MOV             R0, R4
.text:000497D0 000        BL              json_tokener_parse               ; [2]
...
.text:000497F4 000        MOV             R1, #:lower16:aCameraid_1        ; "cameraId"
.text:000497F8 000        SUB             R2, R11, #-var_2D00
.text:000497FC 000        SUB             R2, R2, #0xC
.text:00049800 000        MOVT            R1, #:upper16:aCameraid_1        ; "cameraId"
.text:00049804 000        MOV             R0, R7                           ; jso
.text:00049808 000        BL              json_object_object_get_ex
.text:0004980C 000        CMP             R0, #0
.text:00049810 000        BNE             loc_49820
...
.text:00049820     loc_49820
.text:00049820 000        LDR             R0, [R5,#-0xCE8]
.text:00049824 000        BL              json_object_to_json_string
.text:00049828 000        MOV             R4, R0
.text:0004982C 000        BL              strlen
.text:00049830 000        SUB             R3, R11, #-var_2040
.text:00049834 000        MOV             R2, R0
.text:00049838 000        SUB             R3, R3, #0x24
.text:0004983C 000        MOV             R1, R4
.text:00049840 000        ADD             R0, R3, #4
.text:00049844 000        BL              memcpy                           ; [3]
.text:00049848 000        MOV             R0, R4
.text:0004984C 000        BL              strlen
.text:00049850 000        MOV             R1, #:lower16:aUrl_0             ; "url"
.text:00049854 000        SUB             R2, R11, #-var_2D00
.text:00049858 000        STR             R0, [R5,#-0x40]
.text:0004985C 000        SUB             R2, R2, #0xC
.text:00049860 000        MOVT            R1, #:upper16:aUrl_0             ; "url"
.text:00049864 000        MOV             R0, R7                           ; jso
.text:00049868 000        BL              json_object_object_get_ex
.text:0004986C 000        CMP             R0, #0
.text:00049870 000        BNE             loc_49880
...
.text:00049880     loc_49880
.text:00049880 000        SUB             R3, R11, #-var_2040
.text:00049884 000        LDR             R0, [R5,#-0xCE8]
.text:00049888 000        SUB             R3, R3, #0x24
.text:0004988C 000        ADD             R9, R3, #0x610
.text:00049890 000        BL              json_object_to_json_string
.text:00049894 000        MOV             R4, R0
.text:00049898 000        BL              strlen                           ; [4]
.text:0004989C 000        MOV             R1, R4
.text:000498A0 000        MOV             R2, R0
.text:000498A4 000        MOV             R0, R9
.text:000498A8 000        BL              memcpy                           ; [5]
.text:000498AC 000        MOV             R0, R4
.text:000498B0 000        BL              strlen
.text:000498B4 000        SUB             R3, R11, #-var_2040
.text:000498B8 000        MOV             R12, R0
.text:000498BC 000        SUB             R3, R3, #0x24
.text:000498C0 000        LDR             R1, [R5,#-0x40]                  ; where_size
.text:000498C4 000        ADD             R4, R3, #4
.text:000498C8 000        STR             R9, [SP,#-4+src]                 ; value
.text:000498CC 000        STR             R12, [SP,#-4+value_size]         ; value_size
.text:000498D0 000        MOV             R3, #3                           ; column_size
.text:000498D4 000        MOV             R0, R4                           ; where
.text:000498D8 000        LDR             R2, =aUrl_1                      ; column
.text:000498DC 000        STR             R12, [R5,#0x5CC]
.text:000498E0 000        BL              db_update_wrapper                ; [6]
.text:000498E4 000        CMP             R0, #0
.text:000498E8 000        LDR             R3, [R6]
.text:000498EC 000        BLT             loc_49960
...
.text:0004994C     loc_4994C
.text:0004994C 000        MOV             R0, R7
.text:00049950 000        BL              json_object_put
.text:00049954 000        MOV             R0, R4
.text:00049958 000        SUB             SP, R11, #0x20
.text:0004995C 024        LDMFD           SP!, {R4-R11,PC}
...
.text:00049960     loc_49960
.text:00049960 000        CMP             R3, #0
.text:00049964 000        BNE             loc_49A9C
.text:00049968
.text:00049968     loc_49968
.text:00049968 000        MOV             R0, #0
.text:0004996C 000        MOV             R4, #0x1F4
.text:00049970 000        BL              http_error_message
.text:00049974 000        MOV             R1, R0
.text:00049978 000        LDR             R0, [R11,#value_size]            ; [8]
.text:0004997C 000        BL              sub_47340                        ; [7]
.text:00049980 000        B               loc_4994C

Note that the binary embeds the "json-c" library that is used to manage JSON objects.

The function initially calls http_required_json_parameters at [1] to verify that all the required parameters are specified in the JSON request. The parameters are: cameraId, locationId, dni, url. At [2] the function parses the JSON payload received in the request using json_tokener_parse. It then extracts the "cameraId" [3] and the "url" field in stack buffers. Regarding the "url" field, we can see that the length value for the memcpy call [5] is set from the strlen [4] output of the source string itself. At high level, this would be:

memcpy(stack_buffer, json_parameter, strlen(json_parameter));

Since json_parameter is controlled by the user, there is no restriction on the length of the copy operation, which allows for overflowing the stack buffer and execute arbitrary code.

We identified two different vectors that allow for exploiting this vulnerability:

  • Anyone able to impersonate the remote SmartThings servers can send arbitrary HTTP requests to hubCore that would be relayed without modification to the vulnerable video-core process.
  • SmartThings SmartApps allow for creating custom applications that can be either published directly into the device itself or on the public marketplace. A SmartApp is executed inside the hubCore process and is allowed to make any localhost connection. It is thus possible for a SmartApp to send arbitrary HTTP requests directly to the vulnerable video-core process.

A third vector might exist, but we decided not to test it to avoid damaging any live infrastructure. This would consist of sending a malicious request from the SmartThings mobile application to the remote SmartThings servers. In turn, depending on the remote APIs available, the servers could relay the malicious payload back to the device via the persistent TLS connection. To use this vector, an attacker would need to own a valid OAuth bearer token, or the relative username and password pair to obtain it.

Exploit Proof of Concept

Note that right after the memcpy call at [5], the database is updated to replace the supplied URL [6], using the following query:

UPDATE camera SET url='<url>' WHERE cameraId='<camera-id>'

If the query fails (for example if the supplied "cameraId" doesn't exist), then an error is generated at [7], and the error message in written in a pointer stored in the stack at [8] (if the query succeeds a similar execution happens). To reach the end of the function and overwrite the stack arbitrarily, an attacker would need to provide a pointer to a writable address at r11 + value_size.

Although ASLR is enabled, its entropy is low, and the process spawns 14 threads in total, some of them with large stacks. This makes exploitation reliable, as it's possible to easily predict a writable address.

The following proof of concept shows how to crash the video-core process by overwriting the saved-PC with 0x41414141 and also write beyond it:

$ curl -X PUT "http://127.0.0.1:3000/cameras/666f8370-05b7-424d-a5e2-28a2dc8477f3" -d '{"cameraId":"x","locationId":"x","dni":"x","url":"'$(perl -e 'print "X"x6740,"AAAABBBB","\x55\x55\x8a\x75","C"x100')'"}'

Note that while the proof of concept uses a fixed stack address to simplify the demonstration (0x758a5555), in practice it would be possible to just overwrite the saved-PC with any pivoting gadget.

Timeline

2018-04-16 - Vendor Disclosure
2018-05-23 - Discussion with vendor/review of timeline for disclosure
2018-07-17 - Vendor patched
2018-07-26 - Public Release

Credit

Discovered by Claudio Bozzato of Cisco Talos.