Talos Vulnerability Report

TALOS-2018-0591

Samsung SmartThings Hub video-core AWSELB Cookie Code Execution Vulnerability

July 26, 2018
CVE Number

CVE-2018-3925

Summary

An exploitable buffer overflow vulnerability exists in the remote video-host communication of video-core's HTTP server of Samsung SmartThings Hub. The video-core process insecurely parses the AWSELB cookie while communicating with remote video-host servers, leading to a buffer overflow on the heap. An attacker able to impersonate the remote HTTP servers could 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

8.5 - CVSS:3.0/AV:N/AC:H/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>/streams/<stream-type> path, it's possible to make the hub to start streaming the camera's video (specified by the "camera-id") towards the remote servers. The "stream-type" can have multiple values and represent the kind of stream requested, for example it could be set to "hls1080p". The following is an example of such request:

$ curl "http://127.0.0.1:3000/cameras/<camera-id>/streams/hls1080p" -d '{"streamClass":"hls2segs","oauthToken":"Bearer <oauth-token>"}'

Note that the "oauth-token" is an OAuth 2.0 Bearer Token (canonical UUID), provided by auth-global.api.smartthings.com. Such a request is handled by function sub_4B078, which in turn calls sub_4CDC4 for setting up the communication with a remote video-host server and providing the remote streaming feature:

.text:0004CDC4     sub_4CDC4
.text:0004CDC4
.text:0004CDC4     var_228= -0x228
.text:0004CDC4     var_224= -0x224
.text:0004CDC4     var_220= -0x220
.text:0004CDC4     var_21C= -0x21C
.text:0004CDC4     var_218= -0x218
.text:0004CDC4     var_214= -0x214
.text:0004CDC4     var_210= -0x210
.text:0004CDC4     ptr    = -0x20C
.text:0004CDC4     s      = -0x208
.text:0004CDC4
.text:0004CDC4 000        STMFD           SP!, {R4-R11,LR}
.text:0004CDC8 024        MOV             R3, #0
.text:0004CDCC 024        SUB             SP, SP, #0x204
.text:0004CDD0 228        MOV             R7, R1
.text:0004CDD4 228        MOV             R4, R0
.text:0004CDD8 228        STR             R2, [SP,#0x228+var_220]
.text:0004CDDC 228        STR             R3, [SP,#0x228+var_210]
.text:0004CDE0 228        STR             R3, [SP,#0x228+ptr]
.text:0004CDE4 228        BL              http_resp_packer__buffer_init
.text:0004CDE8 228        MOV             R1, #:lower16:aStreamclassHls    ; "{\"streamClass\": \"hls2segs\"}"
.text:0004CDEC 228        MOV             R9, R0
.text:0004CDF0 228        MOVT            R1, #:upper16:aStreamclassHls    ; "{\"streamClass\": \"hls2segs\"}"
.text:0004CDF4 228        BL              http_build_answer                ; [1]
...
.text:0004CE98 228        BL              http_stream_curl_request         ; [2]
...
.text:0004CEDC 228        ADD             R1, R7, #0x1200
.text:0004CEE0 228        STR             R3, [R4,#0x8F4]
.text:0004CEE4 228        ADD             R2, R7, #0xC
.text:0004CEE8 228        ADD             R1, R1, #0x10
.text:0004CEEC 228        LDR             R0, [SP,#0x228+var_218]
.text:0004CEF0 228        BL              sub_4C570                        ; [3]

At [1], the function starts to build an answer for the request that is being handled. The answer is composed by a JSON string, which will be filled by the answer from the request at [2]. The request [2] is a remote request toward a video-host server: the server's address is stored in video-core's database, and in the hub that we tested this address is "https://vh-na04-useast2.connect.smartthings.com:8300".

The communication generated at [2] is the following:

- video-core --> https://vh-na04-useast2.connect.smartthings.com:8300
PUT /cameras/<camera-id>/streams/hls1080p HTTP/1.1
Host: vh-na04-useast2.connect.smartthings.com:8300
Accept: */*
Content-Type: application/json
Authorization: Bearer <oauth-token>
Content-Length: 27
Connection: close

{"streamClass": "hls2segs"}

- https://vh-na04-useast2.connect.smartthings.com:8300 --> video-core
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, OPTIONS, DELETE
Access-Control-Allow-Origin: *
Cache-control: no-cache="set-cookie"
Content-Type: text/html; charset=utf-8
ETag: W/"5T7hZ36v7ulr9v0HTY4E2g=="
Server: nginx/1.12.0
Set-Cookie: AWSELB=4D83E80E23DA7677A73134EC01A12610C0D45FFDF9389B9D15CD82A43087013F829D09859E3CA0909290FDFFE8AEEB654FC7CEE34F9A088ABB9A8D69753700706BCB37AE10;PATH=/;MAX-AGE=1800
X-Powered-By: Express
Content-Length: 1288
Connection: Close

<json-with-streaming-information>

The HTTP body in the response contains a series of parameters that allow for pushing the camera stream remotely. Moreover, we can see that a cookie with name "AWSELB" is set. At [3], function sub_4C570 parses the cookies, extracting the value of "AWSELB" if present.

.text:0004C570     sub_4C570
.text:0004C570
.text:0004C570     var_210= -0x210
.text:0004C570     var_20C= -0x20C
.text:0004C570     var_208= -0x208
.text:0004C570     save_ptr= -0x204
.text:0004C570     s      = -0x200
.text:0004C570
.text:0004C570 000        STMFD           SP!, {R4-R9,LR}
.text:0004C574 01C        MOV             R8, #debug_log
.text:0004C57C 01C        MOV             R9, R2
.text:0004C580 01C        MOV             R2, #0
.text:0004C584 01C        SUB             SP, SP, #0x1F4
.text:0004C588 210        LDR             R3, [R8]
.text:0004C58C 210        MOV             R4, R0
.text:0004C590 210        MOV             R6, R1
.text:0004C594 210        STR             R2, [R9]
.text:0004C598 210        CMP             R3, #2
.text:0004C59C 210        BHI             loc_4C6CC
.text:0004C5A0
.text:0004C5A0     loc_4C5A0
.text:0004C5A0 210        MOV             R1, #:lower16:0x40001C
.text:0004C5A4 210        MOV             R0, R4
.text:0004C5A8 210        MOVT            R1, #:upper16:0x40001C      ; CURLINFO_COOKIELIST
.text:0004C5AC 210        ADD             R2, SP, #0x210+var_208
.text:0004C5B0 210        BL              curl_easy_getinfo           ; [4]
.text:0004C5B4 210        CMP             R0, #0
.text:0004C5B8 210        BNE             loc_4C71C
.text:0004C5BC 210        LDR             R7, [SP,#0x210+var_208]
.text:0004C5C0 210        CMP             R7, #0
.text:0004C5C4 210        BEQ             loc_4C6A4
.text:0004C5C8
.text:0004C5C8     loc_4C5C8
.text:0004C5C8 210        MOV             R1, #:lower16:asc_C6C8C     ; " \t"
.text:0004C5CC 210        LDR             R0, [R7]
.text:0004C5D0 210        MOVT            R1, #:upper16:asc_C6C8C     ; " \t"
.text:0004C5D4 210        ADD             R2, SP, #0x210+save_ptr
.text:0004C5D8 210        BL              __strtok_r                  ; [5]
.text:0004C5DC 210        SUBS            R4, R0, #0
.text:0004C5E0 210        BEQ             loc_4C698
.text:0004C5E4
.text:0004C5E4     loc_4C5E4
.text:0004C5E4 210        MOV             R0, R4
.text:0004C5E8 210        BL              strlen
.text:0004C5EC 210        CMP             R0, #6
.text:0004C5F0 210        MOV             R1, R4
.text:0004C5F4 210        MOVCC           R2, R0
.text:0004C5F8 210        MOV             R0, #:lower16:aAwselb       ; "AWSELB"
.text:0004C5FC 210        MOVCS           R2, #6
.text:0004C600 210        MOVT            R0, #:upper16:aAwselb       ; "AWSELB"
.text:0004C604 210        BL              strncmp                     ; [6]
.text:0004C608 210        MOV             R1, #:lower16:asc_C6C8C     ; " \t"
.text:0004C60C 210        MOV             R5, R0
.text:0004C610 210        ADD             R2, SP, #0x210+save_ptr
.text:0004C614 210        MOV             R0, #0
.text:0004C618 210        MOVT            R1, #:upper16:asc_C6C8C     ; " \t"
.text:0004C61C 210        BL              __strtok_r
.text:0004C620 210        SUBS            R4, R0, #0
.text:0004C624 210        BEQ             loc_4C698
.text:0004C628 210        CMP             R5, #0
.text:0004C62C 210        BNE             loc_4C5E4
.text:0004C630 210        MOV             R1, R4
.text:0004C634 210        MOV             R0, R6
.text:0004C638 210        BL              strcpy                      ; [7]
.text:0004C63C 210        MOV             R0, R6
.text:0004C640 210        BL              strlen
.text:0004C644 210        LDR             R3, [R8]
.text:0004C648 210        STR             R0, [R9]
.text:0004C64C 210        CMP             R3, #2
.text:0004C650 210        BLS             loc_4C5E4

At [4] the function curl_easy_getinfo is called for extracting the list of cookies, which will be stored in var_208. Then, by using strtok [5], the function loops over all the cookies' values in the response, and if any one of them starts with the string "AWSELB" [6], its value is copied using strcpy [7] into the heap buffer passed as parameter, which has a size of 512 bytes.

Since the cookies are controlled by the remote server and there is no restriction on the length of the copy operation, anyone able to impersonate a video-host server could be able to overflow the heap buffer and execute arbitrary code.

Timeline

2018-05-07 - 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.