Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
PoC WhatsApp enumeration of phonenumbers, profile pics, about texts and online statuses (floated div)
/****** I've created a Chrome extension from this script, take a look at ********/
PoC WhatsApp enumeration of phonenumbers, profile pics, about texts and online statuses
Floated div edition
(c) 2017 - Loran Kloeze -
This script creates a UI on top of the WhatsApp Web interface. It enumerates certain kinds
of information from a range of phonenumbers. It doesn't matter if these numbers are part
of your contact list. At the end a table is displayed containing phonenumbers, profile pics,
about texts and online statuses. The online statuses are being updated every
10 seconds.
Check for an explanation:
- Open WhatsApp web
- Make sure the phone is connected to the WhatsApp Web (past the QR-code screen)
- Open up the console (F12) (Firefox users: type 'allow pasting' if you haven't done so yet)
- Select the contents of this complete file and copy/paste it to the console
- Never, NEVER do something like this if you're not 100% sure this file is from a thrustworthy source!
- You'll see a UI with 2 textboxes and a button
- You may close the console now
- Enter a range of phonenumbers you want to enumerate, more than 500 numbers is probably a little much
- After a few sec you'll see a table of phonenumbers, profile pics, about texts and on/offline statuses
- Every 10 sec, the script checks if someone is online and places that number at the beginning of the table
- If someone is currently online, the left border of the profile picture becomes green
You can drop this script in Tampermonkey or something like that. It only depends on libraries
provided by WhatsApp Web.
(function() {
'use strict';
// Prevent huge traffic/mem usage
var maxNrClients = 1500;
// Standard phone numbers for the 2 text boxes in the UI
var firstNumberStd = 31642101000;
var lastNumberStd = 31642101100;
function setupEventListeners() {
var btnStartIndexer = document.getElementById('btnStartIndexer');
btnStartIndexer.addEventListener("click", function( e ) {
var firstNr = document.getElementById('inpFirstNumber').value;
var lastNr = document.getElementById('inpLastNumber').value;
var divClientBoxes = document.getElementById('divClientBoxes');
divClientBoxes.innerHTML = "";
firstNr = parseInt(firstNr, 10);
lastNr = parseInt(lastNr, 10);
if (isNaN(firstNr) || isNaN(lastNr) ) {
console.log('Numbers should be integers');
return null;
if (lastNr - firstNr > maxNrClients) {
console.log('Don\'t query more than ' + maxNrClients + ' numbers right now');
return null;
var clientNr = firstNr;
var clientBoxCreateT = window.setInterval(function(){
console.log('Next 100...');
var lastClientNrForLoop = clientNr + 100;
for(;clientNr < lastClientNrForLoop && clientNr < lastNr; clientNr++) {
divClientBoxes.appendChild(createClientBox(clientNr, "", "" ));
if (clientNr === lastNr)
}, 500);
// The UI that's added to the current DOM
function createDOM() {
var body = document.getElementsByTagName('body')[0];
var containerDiv = document.createElement("div"); = 'statusIndexer';
var inputFirstNumberLabel = document.createElement("label");
inputFirstNumberLabel.innerHTML = 'First phone number';
var inputFirstNumber = document.createElement("input");
inputFirstNumber.type = "text";
inputFirstNumber.placeholder = "31612345678";
inputFirstNumber.value = firstNumberStd; = "inpFirstNumber";
var inputLastNumberLabel = document.createElement("label");
inputLastNumberLabel.innerHTML = 'Last phone number';
var inputLastNumber = document.createElement("input");
inputLastNumber.type = "text";
inputLastNumber.placeholder = "31612345678";
inputLastNumber.value = lastNumberStd; = "inpLastNumber";
var btnStartIndexer = document.createElement("button"); = 'btnStartIndexer';
btnStartIndexer.innerHTML = 'Start indexer';
var clientBoxesDiv = document.createElement("div"); = 'divClientBoxes';
var style = "";
style += "#statusIndexer {position: absolute; top: 0px; left: 0px; min-height: 50%; overflow: scroll; max-height: 95%; background-color: rgba(230,230,230,0.95); z-index: 99999999; width: 95vw; padding: 50px; border-bottom: solid 3px #58e870; box-shadow: black 0px 1px 62px 0px;}";
style += "#statusIndexer label {margin: 0 15px 0 0;}";
style += "#statusIndexer input {margin: 0 25px 0 0; padding: 5px;}";
style += "#statusIndexer button {margin: 10px 0; border: solid 1px black; padding: 3px; border-radius: 3px; }";
style += "#statusIndexer button:hover {background-color: #58e870;}";
style += "#statusIndexer .indexerClientBox {float: left; width: 120px; text-align: center; margin: 15px; height: 65px;}";
style += "#statusIndexer img {width: 32px; height: 32px;}";
style += "#statusIndexer img.isOffline {border-left: solid 5px orange;}";
style += "#statusIndexer img.isOnline {border-left: solid 5px green;}";
style += "#statusIndexer .indexerPhone {font-size: 13px; margin: 2px; font-weight: bold;}";
style += "#statusIndexer .indexerStatus {font-size: 11px; margin: 2px;}";
var styleEl = document.createElement("style");
styleEl.innerHTML = style;
// Create a floated div per phonenumber and execute WhatsApp API queries
function createClientBox(phonenumber) {
var divBox = document.createElement("div");
divBox.classList.add('indexerClientBox'); = 'p'+phonenumber;
var imgSrcNoneFound = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAIAAAC3LO29AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABDRJREFUeNrsmj1IW1EUgGMNWLBGUIigIlVoq4LUQqsuXerP4uLf4qDSLh1aaRehCk6CDl0q2qFDf6yDi1YXF0272EFLoQWLkQoKGgsKKZggjaCkX97Bx0s01qA2Uc4jhHd/c757zj33nEuSgsGg7UI/l2wX/VFCJVRCJVRCJVRCJVRCJVRCJVRCJVRCJVRCJVRCJVTCUyDcGP3gnXJZi1uzX44e8uvN0PaC+9wQbo6Nux8+MiWmuDU7d/SQ1f7Bf67C6T72k453OH52dN6anIioB3vX57c70lKLi6z1JSPvkx0OXuBMLS7cXliUPtI/JTfncm6OdQZqdjzr9LQbo+JAmH2/FbVge9kP2szKpY5OLDa9ogwAvotevTSblnt608vL8p62zze3ALbn8wU869ITVJBKJyeAtM6w6/OVjAzzHjdPAxuQCGquPcJBhVhojI1K8dCBmdWVt2c+ZTU1oE/ASg1D+D3lkhmM4cN5Tx7H35ciBMu/0tMrRdlmmTVVfKMlPjv78BFPekU53yk52fRBb9gh1ojGWBSKojRnU338CZEsv7vLG1r7RYrJjjS+EVRao+GFz5AWMaFpEccZ/j/OQzTGR6jQDCKu9PQhJbuOSmdTQ0yzZdRUMQNbEXPAjcWTEOtK2Xd9qBG7Emd47Xkfx8bXu/fYVGxI0z1ah9BZVEfR9LfSSn9GBTye1f6BK0ZThJJje4IJ+fxZ88jL+ut3M1evn2Qquy0hnx/NLRybqA5DLejuOslUSYn5TwX2sPgYawxwoQg1t0hUwmjJBza5+mIgEH70SeX5ICT/IAo9IvlgyxH3RRzuUhn/3OJ40XmbGeKcAyvFcjjKP+ffQDPITXF5PyLFCKnEur7V1tGBbmKTnN1bc2HGSbwye/MOH3NsKDl+OySVB42TbtGaTp+QsIOAg5wA6TdHx/HmpE6iorX+QYlXSKnoQM+ljmdibxF5fUZ1JWkH8zDWvCVgaaghjo/IkqEiNiLzoJWmWK8IYiYk2uLnJZMALNOIIY2w242IGKQcX2tGPhWIEjcDzxCCMsm2pLLAiPvIxWi1blfv9EcWbjPkpeaY3HpvciaE32vrvNMuZ2O9qAs8IHEhG2PjyIcELDlhd2pRYVZjfbTTnEm23YssR8QNwKGphlVp/K7kXGdFiPaQj8WW9M/8VeoxJKeBxJYjqbOm/Ac9J8rH5FgRq/8UEpqMfN9hTVwo5j1tl0+syb49xk0Yui/BnRA0hgT1+8VuUd2uz29mvexPxI124yTKxxvthTtY9hjWAR4dWCPJNiXDnm9uxW/JnQ0bMqY7m5ijNnRo7Idcfg8RxcyoNN9DO2fKhSowpx3jDsZsBZulYTlkEsmMJMKGh+VjoJi9qUzRmLyLAR9q2BqXKqESKqESKqESKqESKqESKqESKqESKqESKqESKqESKqHtrwADAMxLRItbnk5RAAAAAElFTkSuQmCC";
var clientImgA = document.createElement("a");
var clientImg = document.createElement("img");
var profilePicRoutine = function(nr) {
Store.ProfilePicThumb.find( nr + '').then(function(d){
var imgATag = document.getElementById('p'+nr).getElementsByTagName('a')[0];
var imgTag = document.getElementById('p'+nr).getElementsByTagName('img')[0];
if (d.img === null) {
if (d.img === undefined) {
imgTag.src = imgSrcNoneFound;
imgATag.href = '';
} else {
imgTag.src = d.img;
imgATag.href = '#';
imgATag.addEventListener('click', function(){
imgTag.src = d.imgFull;, '_blank');
}); = '_blank';
}, function(e){
// Server is throttling/rate limiting, we try it again
var clientPhone = document.createElement("div");
clientPhone.innerHTML = phonenumber;
var clientStatus = document.createElement("div");
var statusFindRoutine = function(nr) {
Store.Wap.statusFind( nr + '').then(function(d){
document.getElementById('p'+nr).getElementsByClassName('indexerStatus')[0].innerHTML = d.status;
}, function(e){
// Server is throttling/rate limiting, we try it again
Store.Presence.find( phonenumber + '').then(function(d){
if (d.isOnline)
return divBox;
// Check online/offline status every 10 sec
window.setInterval(function() {
for(var i=0; i < Store.Presence.models.length; i++) {
var m = Store.Presence.models[i];
var id = 'p' +, -5);
var clientBox = document.getElementById(id);
if (clientBox !== null) {
var img = clientBox.getElementsByTagName('img')[0];
if (m.isOnline) {
console.log(id + ' is online');
} else {
}, 10000);
// Let's setup the UI
// Small delay in case the script is executed from something like Tampermonkey
}, 500);

C0dekid commented May 9, 2017

Thanks! It actually worked 👍

hipio commented May 9, 2017

This is such an insane privacy breach. I'm baffled that Facebook/WhatsApp won't fix this.

Impressive find.


LoranKloeze commented May 9, 2017

Thanks guys!

Nice exploit!

@hipio but it isn't really a security breach. You actually allow "Everyone" to see your profile picture. It really means everyone. You could do this exact thing by just adding a ton of random contacts to your phone and view their WhatsApp picture there (which would be a huge waste of time.. ;p)


LoranKloeze commented May 9, 2017

Thx and I agree with you that it's not a security breach in the sense of getting access to restricted stuff. It's more a privacy breach. Beware of adding a lot of contacts: some people tell me it may get you banned. Since using the API calls is something else than adding contacts, it seems the banning doesn't apply to this script.

sh4ka commented May 12, 2017

You just automated the exploit of a feature that can be disabled by the user. If something, this adds to the fact that we are responsible for what we share. It is a really nice example of what can be done with enough knowledge, good one.

Typo on line 54 I think. You prob want if (isNaN(firstNr) || isNaN(secondNr)), but as-is you are just checking the first number twice 😉

shapeshed commented May 12, 2017


Seems like you have access to Presence, Last Seen, Picture, Status, Phone Number

Is Presence equivalent to online? If so it would be possible to monitor the network and say person A is online when people N are also online. If the pattern is consistent you can infer the identity of a group. This would seem to be a bad privacy leak.

Very impressive!


LoranKloeze commented May 12, 2017

@ejinotti, you're right, fixed it, thx!

Now it returns me only 404.


LoranKloeze commented May 12, 2017

I just checked it, it still works. Did you copy/paste the code in the console?

asaks commented May 12, 2017

Does not work in Firefox.


LoranKloeze commented May 12, 2017

It should, just tested it in FF. Did you type 'allow pasting' in the console before the copy/paste?

asaks commented May 12, 2017

Yes. The input fields appear, but by clicking on the button, nothing happens.


LoranKloeze commented May 12, 2017

Any error messages in the console? Very, very old FF's don't support WebSockets, I take it your FireFox is recent enough?

asaks commented May 12, 2017

FF 53.0.2. In the console, only the inscription "undefined"


LoranKloeze commented May 12, 2017

Ok, and does it give you a line number or a reference into the code?

@LoranKloeze Here in Brazil we "added" 9 before all our numbers (to support more numbers). I tried without the 9 (I was trying using the 9 when it was not working) and it worked.

01CGAT commented May 12, 2017

"stalkingmode" And if you see in the profile pic a girl or guy to one's liking, you can copy paste the phonenumber in Facebook's "Find friends" field and get even more information/pictures."/stalkingmode"

MZorzy commented May 12, 2017

6 years ago. generate a big .vcf files, dump sqlite's contact's file. take only WA number. delete non WA from contact.

over year people going old, married, make babies, buy a pet, leave wa ...

but this is a much better .awesome

NbR404 commented May 12, 2017

It works! Great!

Does this exploit still work if users have restricted their access to their profile picture to people in their address book in the settings?


LoranKloeze commented May 12, 2017

I'm not sure. I hear about people who have restricted all access in those settings and even then others still can see their on/offline statuses using these API calls.

What does '429' in status text mean? Could it be a place-holder for non-used numbers?

stek29 commented May 13, 2017

@eratoxam Error 429 usually means "Too Many Requests". Not sure about WA.

Premx commented May 13, 2017

telegram > whatsapp lmao

Nice, this means someone could now write a script with yowsup to inform every user about how to update his or her privacy settings. Or to switch to telegram 👍

bammat commented May 13, 2017

is the script still working. my firefox want to debug the script because a error. at the time the firefox automaticly closed

i allways get the follwing massage:
Uncaught ReferenceError: Store is not defined
at profilePicRoutine (:146:13)
at createClientBox (:172:9)
at :69:48
profilePicRoutine @ VM100:146
createClientBox @ VM100:172
(anonymous) @ VM100:69
Whats wrong? thx for your help.

bammat commented May 15, 2017

i think it don´t working at the time


LoranKloeze commented May 15, 2017

I can confirm the script still works.


LoranKloeze commented May 15, 2017

@Systemfloh, what browser + version?

bammat commented May 15, 2017

hm, that´s my message

A script on this page may be busy or no longer responds. You can now stop the script, open it in the debugger, or continue.


console message:
Error: Script terminated by timeout at:

Do you start the script at:

i had the same problem, because was not on this site.

@LoranKloeze, but this still doesn't allow to enumerate all phone numbers of registered users, yes? Because it returns the same data for unregistered user and user without an avatar and with default status, so you can't distinguish them.


ghost commented May 15, 2017

Throws " GET 404 (Not Found) GET 404 (Not Found)"


LoranKloeze commented May 15, 2017

This script is a Proof of Concept, not a complete software package that has been tested in all kinds of environments and browsers. So yes, you may encounter error messages. I've created and tested this script in Chrome 58.0.3029.96 (64-bit) on Windows. And, as stated in the instructions, you should drop this script in an open and authenticated page. Otherwise you will see error messages about missing global vars like Storage.


LoranKloeze commented May 15, 2017

@TarasZelyk At this moment the script doesn't tell you if a specific phone number is connected to a WhatsApp account. But that information may be available, it just doesn't bubble up into the UI. I don't know for sure.

Obre commented May 15, 2017

would large modifications be necessary to make this work in Safari?


LoranKloeze commented May 15, 2017

No modifications needed: it should work in Safari out of the box. Just tested it. Make sure you have enabled developer tools in Safari's settings. Then you can copy/paste this script in the console.

Obre commented May 15, 2017

wow this is huge

stopped to work this error
WebSocket connection to 'wss://' failed: Failed to send WebSocket frame.

2 edit:
aand it works again

bammat commented May 15, 2017

Is it possible that my internet connection is to slow because of this message: A script on this page may be busy or no longer responds. You can now stop the script, open it in the debugger, or continue.

is it possible to export the results to excel / word or something else ?

MacOS 10.12, Crome Version 58.0.3029.110 (64-bit)
thx a lot


LoranKloeze commented May 16, 2017

@bammat It's unlikely that a slow internet connection would break javascript in WhatsApp Web. A very very buggy internet connection might break it when WhatsApp Web keeps trying to create new WebSockets.

Exporting tot Excel/Word is not as easy as it sounds since creating a downloadable file from javascript is not possible as far as I know. But it is possible to create the data in csv format so you can copy/paste it. I would have to edit the script for that to work ;)


LoranKloeze commented May 16, 2017

@SystemFlow As @bammat pointed out: do you drop the script in the same tab as where is running?

@LoranKloeze no i dont :-( i will try

bammat commented May 16, 2017

yes, i try it within the tab. i will try it tomorrow with another computer and a faster internet connection


LoranKloeze commented May 16, 2017

I've created a Chrome extension from this script, take a look at

has anyone modified the script to export (in csv) online times (with timestamp) ?

Couldn't this be used to also include the name of a number? Because I have seen that in group chats, numbers I do not know with a name after them.

Is there any way to find out if someone blocks me? I guess state "401" is an indicator, but it not sufficient.

jegue commented Jul 24, 2017

Very impressive! 2

but the code doesn't makes a timeline or doesn't it work right in my webconsole?

MadIshX commented Aug 18, 2017

did not work for sri lankan numbers.
eg: 0712699345- 0712699369

great if you could improve this. Excellent work!

I need some help to develop a business tool out of this. Please contact me if you already have or want to start a paid development.

deounix commented Dec 15, 2017

how can I start new chat with some number not saved in my contact ?

I'm trying

var Chats = Store.Chat.models;
var contact = '961xxxxxxxxx';

var user = Store.Contact.models.find(function (e) { return!=-1 });

Store.Chat.add({ id: user.__x_id, }, { merge: true, add: true, });

but it didn't working

duzaq commented Dec 31, 2017


X_sendMessage = function(to = null, body = null) {
if (to == null || body == null) return {
'status': 401,
'status_msg': "Invalid user or body text"
var id = X_makeID();
var msg = Store.Msg.models[0]; = id; = to + "";
msg.type = "chat";
msg.body = body; = to + "";
msg.t = Math.ceil(new Date().getTime() / 1000);

X_makeID = function() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 20; i++) text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
};X_sendMessage(961xxxxxxxxx, "test");

Text message sending is working.. Have you Any javascript for send Media from ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment