WhatsApp shares your contacts’ status to you.

TL;DR: You can protect yourself from this hack by changing your account privacy settings. By default, WhatsApp share your status to others.
Since nobody is changing settings nowadays, This hack works almost all the time.


WhatsApp in Android

Exploit the feature

I want to exploit this feature to track users (for science). My first question is: How does this feature work exactly ?

To make things up, I am using https://web.whatsapp.com/ in my laptop web browser instead of my Android phone. So I’ll deal with regular web reverse engineering to get this exploit done.

I am taking a friend of mine’s phone and look at how its status updates on my side.

Initially, the status is offline, and in this case, WhatsApp gives you a last seen date, 16/03/2020 at 15:40.

I’m unlocking the friend’s phone and opening an app (not WhatsApp). Doing that for a minute, nothing on my side.
Ok, Now switching to WhatsApp. The status has changed to online within the next 10 seconds. I didn’t go to the conversations I am sharing with this phone/contact to verify that the status is shared without this condition.

The online status will remains until leaving WhatsApp or shutting off the screen on the targeted phone.
And then, it reverts to a new last seen date once offline again.

So to summarize the analysis,

  • We won’t be able to track someone using his phone globally (hopefully !)
  • But we can track the WhatsApp usage of anyone we have in our contacts
  • The info leaked are last seen date and the live online status per contact
  • We can expect to have at least 1 minute precision for the last seen date
  • And the online status is raised once the WhatsApp is up for at least 5-10 seconds

Technical analysis

I’m opening the Firefox debugger (Proudly using Firefox again !) to see how the front of WhatsApp web is fetching the data coveted.

The front is using a web socket communication to gather the data in real time, somewhat every 10-15 seconds.

If we look carefully, the front seems to poke the server every ~15 seconds with ?,, and most of the time it replies a !{timestamp}. A kind of keep alive stuff. Not interesting for us.

The server pushes to us another kind of message when the status of the contact change.


The id value I partially cover in black is the phone number, type is the available/unavailable flag, t is the timestamp of our last seen date. The whole payload being encapsulated in a Presence object, easy to recognize.
The timestamp is matching what we read in the UI. Thus we also know the seconds.


using https://www.epochconverter.com/

Limitations

In order to receive the presence events from the server via the web socket com, we (the front) subscribe to a specific phone number (id). This is triggered when we select another conversation/contact with the web interface.

So the conception is made to only receive the active contact’s presence events. In other words, we can only track one contact at a time in the web socket connection. Good one !

WhatsApp also prevents us from opening several instances at the same time (same cookies, same session). So we can’t open two websocket channels concurrently. Would have been too easy !

And finally, this one-WhatsApp-web-session-at-a-time behaviour still apply when trying two independent sessions (not the same cookies, not the same session; WhatsApp allow several ‘Logged in devices’). Technically, the server closes the oldest active web socket channel to do this behaviour. Seems hard to tackle.

Another expected limitation, the validity of our session is limited in time. Mine will expire the 22/10/2020, in 6 months+. Really odd to retrieve this info in the front side like this. I might be misinterpreting this one.

Naïve implementation

Previously, we’ve seen what was the status in WhatsApp and how we could misuse it to track our contacts.
We also looked at the technical implementation and for a possible easy-security-flaw. And well, multi-tasking will be hard.

We could re-code the web socket communication exchange to retrieve the status data, but this will be complex. Too-complex if we can just track one contact. We will start with a high-level techno and accept the current known limitations and see where it goes.

My idea is to see where we can go with cheap hacking work before going further. When hacking a thing, the time/cost we spend and the maintainability matter !

I’ll decompose the proof of concept into 3 problems:

  • Gather the data
  • Store the data
  • Visualize the data

I’ll scrape the data using Node.js and Puppeteer; Puppeteer allows us to control a browser and interact the same way a user would do with the mouse and keyboard. That said, this avoids doing complex reverse engineering at the web socket level.

We got the core stuff in 38 lines of code.

In order to continue, we need to parse the last seen today at 13:15 format into a proper date format. To do that, I’m using the so-wonderful chrono-node js package.

Finally, we add a loop in the code to scan at regular pace the status and we store the results into InfluxDB 2.0.
InfluxDB is a time series database, that’s perfect for our use case.

I will derive the last seen date into an offline since UInteger. It will be the amount of seconds since the last seen date.
offline since will be 0 when the status is online.
Deriving our data is turning our event-based data into time-series data. This design fits better for InfluxDB and Grafana which will display our data. And that’s stateless, I like that.

To store the data into InfluxDB 2.0, I’m using the Node.js client with the line protocol format of InfluxDB.

measurementName,tagKey=tagValue fieldKey="fieldValue" 1465839830100400200
--------------- --------------- --------------------- -------------------
       |               |                  |                    |
  Measurement       Tag set           Field set            Timestamp

The data we store looks like this:

status,contactName=Toto offlineSince=8275u 1465839830100400200
status,contactName=Toto offlineSince=8280u 1465839830100400200
status,contactName=Toto offlineSince=0u 1465839830100400200
status,contactName=Tata offlineSince=0u 1465839830100400200
------ ---------------  ----------------- -------------------
  |            |                |                  |
Measurement Tag set         Field set          Timestamp

The code implementation:

There is an edge case I want to handle: Sometimes, the status does not display at all in WhatsApp.
In this case, we won’t enter a offlineSince measure into the database because we don’t have one. Instead, we will log a statusAvailable measure (being 0 or 1) each time we scan the status.

We now connect Grafana to InfluxDB, and create a dashboard to monitor our acquisition. And voilà.

You can find the source code of this proof of concept here.
We will try to improve this hack later one, for another blog entry maybe !