The following are various notes and findings on trying to create a “Bot” for Voxer. Voxer doesn’t really have any options for web hooks, and the SDK is still in beta. We’ll be exploring sending messages to a channel, how to use Curl or Fetch to send messages. Unfortunately, signing in does not appear to be easy to automate.
Looking at Voxer
Voxer is primarily used via the modile apps, but there is also a web version at web.voxer.com
Using Firefox, we can play with the Web Tools to get a better idea of what is going on.
All the Javascript is easily readable. api.js is of interest. This can helps us understand how messages are sent.
The Web interface seems to be somewhat buggy, could be it just doesn’t like going through burp, but had better luck monitoring the channel for new messages from a phone.
Proxying the traffic through Burp is tricky. The log in does not appear to work while the proxy is active, but you can log in, and then activate the proxy to capture and replay sending messages.
If you get “Voxer is open in another tab. Please click ‘OK’ then close this tab.” clear all voxer cookies and log in again. Seems to happen quite often.
Interestingly, if you send a message, then send that message POST request to Repeater, change the text and resubmit it, it “Updates” the text of the message. So if you know the message_id, or maybe the create_time, you can change the text in messages.
About Sending Messages
When you send a message, there are 2 POST requests sent. The first one sends the message and the second one consumes the message. It looks like you really only need the first post message request to actually send messages. Think the consume message post is for the browser to trigger a refresh on the messages in the message list.
There are two variables that change message to message
message_id and create_time
Looking into the api.js file we see that create_time is done with the following code
now = new Date().getTime() / 1000,
And here is the code used to generate the message_id. First part is the time, the next part is random.
window.generate_message_id = function (type) { var message_id = new Date().getTime() + "_" + Math.floor((Math.random() * 10000000000) + 1000); if (type && type === "image") { message_id += "_v1.jpg"; } return message_id; };
Structure of the POST request
The following entries are slightly abbreviated
Cookies
There are a bunch of tracking cookies, the session cookie is the one we are interested in. The Rv_session_key is what will allow us to actually send messages. As a side note, it appears that every time we send a message, there is analytics that is also sent, saying who the message was sent to, and when.
session={"gcp1-prod":{"user_id":"MyVoxerUser_ID","Rv_session_key":"db0bc8a069148140151a38bab2098a01"}}
Body
{"message_id":"1681191108600_7318671093","create_time":1681191108,"model":"User_Agent","content_type":"text","from":"MyVoxerUser_ID","subject":"Walkie","body":"This is the text of the message","thread_id":"This.is.the.thread_id"}
Using JavaScript / fetch
After some trial an error, the following solution finally worked. You can copy the following code, and run with “node file.js”
// Change variables as needed let body = "Hello World!" // let body = process.argv.slice(2) // You can use this if you want to pass in the message as an argumant. i.e. node voxer.js "Voxer Message" const threadID = 'ThreadID' const fromID = 'UserID' let cookie = `session={"gcp1-prod":{"user_id":"${fromID}","Rv_session_key":"RVSESSIONID"}}` let time = new Date().getTime() / 1000 let messageID = new Date().getTime() + '_' + Math.floor(Math.random() * 10000000000 + 1000) // Send message. function sendMessage () { fetch(`https://gcp1-prod-nr60.voxer.com/2/cs/post_message?now=${time}`, { credentials: 'include', credentials: 'same-origin', headers: { 'User-Agent': 'UserAgent', Accept: '*/*', 'Accept-Language': 'en-US,en;q=0.5', 'Content-Type': 'text/plain', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-site', 'sec-ch-ua-platform': '"Windows"', 'sec-ch-ua': '"Opera";v="97", "Chromium";v="97", "Not=A?Brand";v="24"', 'sec-ch-ua-mobile': '?0', Cookie: cookie }, referrer: 'https://web.voxer.com/', body: `{\"message_id\":\"${messageID}\",\"create_time\":${time},\"model\":\"\",\"content_type\":\"text\",\"from\":\"${fromID}\",\"subject\":\"CHANGING\",\"body\":\"${body}\\n\",\"thread_id\":\"${threadID}\"}\r\n`, method: 'POST', mode: 'cors' })} sendMessage()
You can find the thread_id, user_id, session cookie by toggling the developer console, logging into Voxer, and send a message.