Last week, I discovered a stored cross-site scripting vulnerability in the tribe chat, allowing malicious users to inject arbitrary JavaScript script via (1) message and (2) name parameters in the chat room. While the vulnerability is severe, Tribe is currently in an alpha release, so it was a great time to catch and remediate the issue before widespread use. In this post, I’ll describe how client-side valdiation was evaded by modifying raw web socket requests.


🙊 What is MonkeyType?

Monkeytype is a popular typing-test application with a growing online community. Started in early 2020, it’s quickly become one of the top typing test applications, boasting over 40 million page views. In late May, the lead developer announced some pretty impressive statistics for the site,

  • 100k daily unique visitors
  • 228k registered accounts
  • 139 million started tests
  • 54 million completed tests
  • 2.22 billion seconds which is around 70 years of typing

☠️ Vulnerability Disclosure

Authenticated Stored Cross-Site Scripting

The monkeytype Tribe Chat is vulnerable to stored cross-site scripting via the (1) message and (2) name parameters. While client side validation as implemented, it fell short of protecting malicious payloads that were injected directly in modified socket requests. To be exploited, a malicious user could create or join a chat room, send a normal message, intercepted the socket request and inject a payload into the name or message fields.

💥 Impact

The impact of XSS in a chat room is severe. Once injected, the payload would be executed in the context of other user’s browsers who were within the same room. These payloads could be used to steal users authentication tokens, allowing complete account takeover. Making the vulnerability worse is the fact that tribe allows users to invite other’s to specefic chat rooms. Once a room has been successfully exploited with a XSS payload, a malicious user could then share the link to the specific chat room to others, increasing the number of potential victims.

⏱️ Timeline & Remediation

I disclose the vulnerability to MonkeyType developers on Discord and Github
27 March 2021
MonkeyType developers implement input validation and output encoding
27 March 2021

🔌 What are Web Sockets?

Web sockets are a two way interactive sessions between a client and server. Unlike standard static web pages that require a user to refresh to load content, web sockets can receive responses in near real time. This benefit makes them ideal for chat applications, where multiple clients can be in the same chat room and receive simultaneous updates without requiring any interaction. More information about sockets can be found below,

⚡ Tribe Socket Implementation

To implement sockets for tribe chat, monkeytype uses, a popular socket framework for javascript. While similiar in it’s implementation to traditional web sockets, is slightly different. According to it’s documentation, is “NOT a WebSocket implementation. Although Socket.IO indeed uses WebSocket as a transport when possible, it adds additional metadata to each packet. That is why a WebSocket client will not be able to successfully connect to a Socket.IO server, and a Socket.IO client will not be able to connect to a plain WebSocket server either”. The overall socket process for the tribe application looks like the following.

[Client Server]    User joins/create a chatroom, socket connection 42 initiates


[Client Server]    User sends a message to chat room, client sends server message


[Client Server]    Server Pushes Message to Chat


💣 Exploiting Web Sockets

Exploiting XSS in web sockets is fairly trivial when no input validation or output encoding is used. Just as you would normally intercept a post request and add an XSS payload with BurpSuite, you can do the same with web sockets. In this case, since the name and message is both displayed on the chat page, we can use both as valid injection points.

[Client Server]    Malicious User Injects XSS in Socket Message

42["mp_chat_message",{"isSystem":false,"isLeader":true,"message":"test<svg/onclick=alert`xss #1`>","from":{"id":"i6ZO4keqlEgwQHY-AAAm","name":"pwnville<svg/onclick=alert`xss #2`>"}}]