CVE-2018-3925
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.
Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17
https://www.smartthings.com/products/smartthings-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’)
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.
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
Discovered by Claudio Bozzato of Cisco Talos.