Posted by Felix Weyne, May 2016.
Author contact: Twitter | LinkedIn
Tags: reverse engineering, tinder, vulnerability disclosure, statistical analysis, randomness test, fiddler, android
In today’s blog post I'll be talking about hunting for (security) vulnerabilities in Tinder. For those who are not familiar with Tinder: Tinder is a dating app used by more than 50 million users. Tinder has a pretty simple concept: it shows you people nearby and lets you anonymously like or pass on them. If someone you like happens to like you back, Tinder makes an introduction and lets you chat with them. What makes Tinder addictive is the instant gratification people get from swiping and judging prospect.
Tinder like most internet connected mobile apps, uses an HTTP based Application Program Interface (API) under the hood. When you open the Tinder app, your mobile phone makes a request for profiles of Tinder users nearby you. The different profile properties that are visible on your mobile phone are: the users first name, the users age, common Facebook interests, common Facebook friends, some pictures of the user and how far away the user is (in miles). I played around with the API one lazy Sunday afternoon (full setup tutorial can be found below), and noticed some interesting responses from the Tinder server. One of the captured responses is displayed below.
Image 1: Captured response to a profile view.
The server’s response to a profile view reveals more data than what actually is visible in the graphical interface of Tinder. In the next sections, I will discuss three interesting response parameters. During the investigation, I discovered that a particular response property (user_birthday) was susceptible to an (OWASP TOP 10) sensitive data exposure attack. I’ve reported this (security) vulnerability to Tinder and within forty-eight hours, Tinder fixed the vulnerability
Parameter one: birth date
To perform my tests, I created a test Facebook account. I registered on Tinder using my Facebook test account, at which moment Tinder copied some general profile information (name, birthdate, likes, friends, attended schools & subset of pictures). Tinder stores this information on its servers and uses this information to generate a Tinder profile. Some sensitive Facebook profile information does not end up on a Tinder profile, because a Tinder profile always is available publicly. Sensitive information like a user’s last name or birthdate is either fuzzed or removed. A user would not want this information to be publicly available on its Tinder profile, because not only would this uniquely identify him/her, but it could also lead to identity fraud.
When we take a look at a response from the Tinder server (info on how to easily record such a response can be found in the setup section), we can see that the Tinder server sends a birth_date parameter. This birthdate does not match the ‘real’ birthdate of the Facebook test profile. The birth_date parameter is needed to display an approximation of a user’s age, so that the age can be display on his Tinder profile. The birth_date_info parameter confirms that the birthdate is fuzzed: i.e. it is modified slightly. The fact that Tinder fuzzes a user’s birthday is a good thing. However, I’ve discovered that it is possible to unfuzz this birthdate (i.e. retrieve a user’s real birthdate) by making use of a statistical attack.
Tinder modifies a user’s birthdate by a few days each time that the Tinder app retrieves the user’s profile via the Tinder API. This behavior suggests that the Tinder server adds or subtracts a random number of days from the user’s real birthdate. By recalculating the fuzzed birthday on each request, I had a feeling that this method may be susceptible to a statistical attack. To confirm this, I decided to send a bunch of requests to retrieve my test profile. I’ve visualized the occurrences of the responses to those request in a graph. To automate the requests, I wrote a simple python script. The python script and the graph are both displayed below.
import httplib import json for x in range(0, 10000): requestHeader = { "platform": "android", "X-Auth-Token": "0ffcf284-XX-XX-XX-XXc7a", "User-Agent":"Tinder Android Version 4.5.5", "os-version": 23, "app-version": 854, "Content-Type": "application/json", "Accept-Language": "nl" } conn = httplib.HTTPSConnection("api.gotinder.com:443") conn.request("GET", "/user/567570b-XX", "", requestHeader) response = conn.getresponse() jdata = json.loads(response.read()) birthdate = jdata['results']['birth_date']+"\n" print birthdate.replace("T00:00:00.000Z","") with open("c:\\tinder\\dates.txt", "a") as text_file: text_file.write(birthdate.replace("T00:00:00.000Z",""))
Image 2: 10.000 birthday responses to a Tinder profile request visualised.
The graphical representation of 10.000 birthday responses to a Tinder profile request clearly proves that a uniformly distributed random function is used to fuzz the birthdate. The random function is uniformly distributed, because each fuzzed birthdate is returned about the same number of times (i.e. 700 times). When we map the real birthdate of my test profile to the graph, it is possible to deduce that a birthday parameter response to a profile request is calculated as follows:
Fuzzed birthday = (real birthdate) – random[minimum 1, maximum 14] days
Calculating a fuzzed birthday this way, is a (security) vulnerability that would enable a cybercriminal to retrieve the exact birthdate of millions of Tinder users. The cybercriminal only needs to make a few tens of requests to each Tinder profile and map them. The user’s real birthday is one day after the greatest received fuzzed birthday. This bug has been reported to Tinder and has quickly been fixed .
Parameter two: distance miles
Another interesting parameter is “distance_mi”. A distance indication does not reveal an (approximate) geolocation of a user. However, the ability to change your own geolocation programmatically at a very fast rate, does. I noticed that it is possible to fake my own geolocation by sending different longitude and latitude coordinates to the Tinder API. Changing my location could be done limitlessly as long as the successive distance changes were not too large. I used this technique to discover the location of my test profile that was connected to my testing secondary mobile phone, which was 10 miles away from my primary mobile phone.
Via the API, I changed my primary mobiles geolocation towards a random direction. Then I queried the “distance miles” parameter from my test profile. When “distance miles” decreased, I kept changing my location towards the same direction. An increase in “distance miles” indicated that I needed to move towards another direction. Below I created a visual representation of the “two-miles-away” hits (distance_mi=2). The red markers depict the locations that resulted in a “two-miles-away” response. The location of my test profile is visualized with a yellow circle. The red markers inside the rectangle are false positives (i.e. incorrectly parsed responses), they should be invisible “one-mile-away” markers.
Image 3: Visualisation of tracking the geolocation of my test profile. Yellow circle = actual location, red marker = "two-miles-away" response. Blue circle = 1 mile radius.
I was a bit surprised that the “two-miles-away” markers didn’t form a circular pattern. Instead, they form a rectangular pattern. This indicates that the “miles-away” parameter is fuzzed: even when my spoofed location was less than two miles away from the location of the test profile, I still sometimes got a “two-miles-away” response.
Fuzzing the miles-away parameter is a good implementation to protect a user’s privacy, although it's still possible to get an approximate user location. If you compare the center of the rectangular “two-miles-away-marker” pattern to the real location of my test profile, you can see that they are pretty close (i.e. less then a mile away: the blue circle depicts a one-mile range). I have tested this multiples times (test two, test three), and the difference between the calculated (guessed) location and the real location was always less than half a mile.
Parameter three: ping time
The last interesting parameter is ping time. Ping time shows when the users was last active on Tinder. This “last-active” indication once was a feature on Tinder, but this feature has been removed as indicated on Tinder’s website. By inspecting the responses, one can still determine to the second when a user was last active.
Image 4: Tinder FAQ: last active feature has been disabled.
Vulnerability disclosure & Tinder’s response
I’ll end my blog by briefly describing Tinder’s response to the birthday parameter information disclosure vulnerability. I mailed Tinder mid April 2016 about this vulnerability. Within the hour, I got a response that they would look into it. They fixed it in less than fourty-eight hours (this is very fast!), and they were very chill and polite in their responses. Awesome attitude, Tinder .
Image 5: Tinder’s response to the vulnerability disclosure.
Test Setup
I based my test setup on the article 'Hacking Tinder for fun and profit'. Some information regarding the test setup is copied from that article, all credit regarding the test setup goes to Youri De Souza.
To reverse engineer Tinder's network traffic, we need to capture it and understand it. My tool of preference for capturing HTTP traffic is Fiddler. One of Fiddler’s cooler features is its ability to decrypt secure traffic over HTTPS. It does this using a “man-in-the-middle” approach to intercept the secure packets.
To the client (the mobile app) Fiddler impersonates the API web server. And, to the API web server, Fiddler impersonates the client (the mobile app).
Image 6: Fiddler setup (source: ydesouza.com).
To impersonate the secure web server, Fiddler needs a SSL certificate. Fiddler dynamically generates a SSL certificate for this purpose. However, since this certificate is not signed by a Trusted Root Certification Authority, it won’t be trusted by the client (the mobile app). If the mobile app does not trust the web server, it will not talk to it. This can be easily fixed by installing Fiddler’s cert on the mobile device. Fiddler’s cert can be exported by pulling up Fiddler Options from the Tools menu. The easiest way to import the certificate on the mobile phone is to mail it to yourself (the printscreens are in Dutch, sorry for that).
Image 7: Importing Fiddler's certificate on Android.
Once you have installed Fiddler’s cert on the mobile device, you need to route all traffic from the mobile phone to Fiddler. There are multiple ways to do this. One easy way is to proxy the traffic to the computer running Fiddler. Fiddler’s proxy server listens on port 8888 by default. Let’s assume the local IP address of the computer is 192.168.1.173. On an Android device, proxy settings can be set along with the WiFi settings by checking “Show advanced options” as below (the printscreens are in Dutch, sorry for that). If needed, also shut down your Windows firewall.
Once you have completed the above steps, you will start seeing Tinder API traffic flowing trough Fiddler. An example of that traffic is shown in image one.