Table of Contents
- Introduction to WebSockets
- WebSocket Security Fundamentals
- Common WebSocket Vulnerabilities
- Attack Scenarios and Mitigations
- Security Testing for WebSockets
- Best Practices for Secure WebSocket Implementation
1. Introduction to WebSockets
What are WebSockets?
WebSockets are a protocol that enables full-duplex, persistent communication channels between a client (typically a browser) and a server. Unlike traditional HTTP, which follows a request-response model, WebSockets allow real-time, bidirectional messaging over a single, long-lived TCP connection.
How WebSockets Work
WebSockets begin with a handshake using regular HTTP headers:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Origin: https://example.com
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Once the handshake succeeds, the connection is upgraded and stays open, allowing arbitrary JSON, XML, or binary data to flow freely between client and server.
WebSockets vs. HTTP
| Feature | HTTP | WebSockets |
|---|---|---|
| Communication | Request–response only | Full-duplex, bidirectional |
| Connection | New connection per request | Persistent connection |
| Latency | Higher (connection overhead) | Lower (real-time communication) |
| State | Stateless | Stateful |
| Use Cases | Standard web requests | Real-time apps, chat, notifications |
2. WebSocket Security Fundamentals
WebSocket Handshake Process
The WebSocket handshake is an HTTP Upgrade request that transitions the connection from HTTP to WebSocket. During this process:
- The client sends an HTTP request with specific headers
- The server validates the request and sends a response
- If successful, the connection is upgraded to WebSocket
Key Headers and Security Implications
Origin: Indicates where the request originated fromSec-WebSocket-Key: Random value to prevent caching proxy errorsSec-WebSocket-Protocol: Specifies sub-protocolsCookie: Contains session information
ws:// vs. wss://
ws://: Unencrypted WebSocket connection (similar to http://)wss://: Encrypted WebSocket connection over TLS (similar to https://)
Using wss:// is critical for:
- Preventing eavesdropping
- Protecting against man-in-the-middle attacks
- Securing sensitive data transmission
3. Common WebSocket Vulnerabilities
3.1 Message Manipulation Attacks
- Description: Tampering with WebSocket messages to exploit logic flaws
- Impact: Data manipulation, command injection, XSS
- Common in: Chat applications, real-time collaboration tools
3.2 Handshake Manipulation Attacks
- Description: Exploiting vulnerabilities in the WebSocket handshake process
- Impact: Authentication bypass, privilege escalation
- Common in: Applications with weak session handling
3.3 Cross-Site WebSocket Hijacking (CSWSH)
- Description: Cross-site request forgery (CSRF) vulnerability on a WebSocket handshake
- Impact: Account takeover, unauthorized actions, data exfiltration
- Common in: Applications without proper origin validation
3.4 Denial of Service Attacks
- Description: Overwhelming the server with connections or messages
- Impact: Service unavailability
- Common in: Applications without connection limits
3.5 Authentication and Authorization Issues
- Description: Weak or missing authentication/authorization checks
- Impact: Unauthorized access to sensitive data or functionality
- Common in: Applications that trust client-controlled data
3.6 Input Validation Failures
- Description: Insufficient validation of WebSocket message content
- Impact: XSS, SQL injection, command injection
- Common in: Applications that trust message content
3.7 Information Disclosure
- Description: Leaking sensitive information through WebSocket messages
- Impact: Data breach, privacy violation
- Common in: Applications that transmit sensitive data without encryption
4. Attack Scenarios and Mitigations
4.1 Message Manipulation Attack
Vulnerable Code Example
// Server-side code (Node.js)
wss.on('message', function incoming(message) {
const data = JSON.parse(message);
// Vulnerability: No validation of 'command' parameter
if (data.command === 'deleteUser') {
db.query(`DELETE FROM users WHERE id = ${data.userId}`);
}
});
Exploitation Steps
Attacker connects to the WebSocket
Attacker sends a crafted message:
{ "command": "deleteUser", "userId": "1 OR 1=1" }Server executes the command without proper validation
All users are deleted from the database
Fix
// Server-side code (Node.js)
wss.on('message', function incoming(message) {
try {
const data = JSON.parse(message);
// Validate command parameter
if (data.command === 'deleteUser' && isNumber(data.userId)) {
// Use parameterized queries to prevent SQL injection
db.query('DELETE FROM users WHERE id = ?', [data.userId]);
}
} catch (error) {
console.error('Invalid message format');
}
});
function isNumber(value) {
return typeof value === 'number' && !isNaN(value);
}
4.2 Cross-Site WebSocket Hijacking (CSWSH)
Vulnerable Code Example
// Server-side code (Node.js)
wss.on('connection', function connection(ws) {
// Vulnerability: No origin validation
// The server accepts any WebSocket connection
ws.on('message', function incoming(message) {
// Process message without proper authentication
const data = JSON.parse(message);
if (data.action === 'changeEmail') {
// Directly update email without verification
db.query(`UPDATE users SET email = '${data.email}' WHERE id = ${data.userId}`);
}
});
});
Exploitation Steps
Attacker creates a malicious website with JavaScript code:
<script> const socket = new WebSocket('wss://target-app.com/chat'); socket.onopen = function() { // Send message to change victim's email to attacker's email socket.send(JSON.stringify({ action: 'changeEmail', userId: '123', // Victim's user ID email: 'attacker@evil.com' })); }; </script>Victim, who is logged into the target application, visits the malicious site
The browser automatically includes the victim’s session cookies in the WebSocket handshake
The server accepts the connection and processes the message
The victim’s email is changed to the attacker’s email
Fix
// Server-side code (Node.js)
wss.on('connection', function connection(ws, req) {
// Validate origin header
const origin = req.headers.origin;
if (origin !== 'https://trusted-domain.com') {
ws.close(1008, 'Policy violation');
return;
}
// Extract and validate session token
const cookies = req.headers.cookie || '';
const sessionToken = getSessionToken(cookies);
if (!isValidSession(sessionToken)) {
ws.close(1008, 'Authentication required');
return;
}
// Get user information from session
const user = getUserFromSession(sessionToken);
ws.on('message', function incoming(message) {
try {
const data = JSON.parse(message);
// Additional validation for sensitive operations
if (data.action === 'changeEmail') {
// Verify the user is changing their own email
if (data.userId !== user.id) {
ws.send(JSON.stringify({error: 'Unauthorized operation'}));
return;
}
// Validate email format
if (!isValidEmail(data.email)) {
ws.send(JSON.stringify({error: 'Invalid email format'}));
return;
}
// Use parameterized queries
db.query('UPDATE users SET email = ? WHERE id = ?', [data.email, data.userId]);
ws.send(JSON.stringify({success: true}));
}
} catch (error) {
ws.send(JSON.stringify({error: 'Invalid message format'}));
}
});
});
4.3 XSS via WebSocket Messages
Vulnerable Code Example
// Client-side code
socket.onmessage = function(event) {
const message = JSON.parse(event.data);
// Vulnerability: Direct insertion of message content into DOM
document.getElementById('chat').innerHTML +=
`<div><b>${message.username}:</b> ${message.content}</div>`;
};
Exploitation Steps
Attacker sends a message containing malicious JavaScript:
{ "username": "Attacker", "content": "<img src=x onerror=alert('XSS')>" }Server forwards the message to all connected clients
Client browsers render the message without sanitization
The malicious JavaScript executes in all victims’ browsers
Fix
// Client-side code
socket.onmessage = function(event) {
try {
const message = JSON.parse(event.data);
// Sanitize message content before inserting into DOM
const sanitizedContent = escapeHtml(message.content);
const sanitizedUsername = escapeHtml(message.username);
// Use textContent instead of innerHTML when possible
const messageElement = document.createElement('div');
messageElement.innerHTML = `<b>${sanitizedUsername}:</b> ${sanitizedContent}`;
document.getElementById('chat').appendChild(messageElement);
} catch (error) {
console.error('Error processing message:', error);
}
};
// HTML escaping function
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
4.4 Authentication Bypass via Handshake Manipulation
Vulnerable Code Example
// Server-side code (Node.js)
wss.on('connection', function connection(ws, req) {
// Vulnerability: Trusting client-controlled headers for authentication
const isAdmin = req.headers['x-admin'] === 'true';
if (isAdmin) {
// Grant admin privileges without proper verification
ws.admin = true;
}
ws.on('message', function incoming(message) {
const data = JSON.parse(message);
if (data.action === 'deleteUser' && ws.admin) {
// Delete user without additional checks
db.query(`DELETE FROM users WHERE id = ${data.userId}`);
}
});
});
Exploitation Steps
Attacker connects to the WebSocket with a custom header:
X-Admin: trueServer grants admin privileges based on the header
Attacker sends a message to delete any user:
{ "action": "deleteUser", "userId": "123" }Server executes the command without proper authentication
Fix
// Server-side code (Node.js)
wss.on('connection', function connection(ws, req) {
// Extract session token from cookies or headers
const cookies = req.headers.cookie || '';
const sessionToken = getSessionToken(cookies);
// Verify session token and get user information
const user = getUserFromSession(sessionToken);
if (!user) {
ws.close(1008, 'Authentication required');
return;
}
// Check if user has admin privileges based on server-side data
ws.admin = user.role === 'admin';
ws.on('message', function incoming(message) {
try {
const data = JSON.parse(message);
if (data.action === 'deleteUser' && ws.admin) {
// Additional validation: Check if user ID is valid
if (!isValidUserId(data.userId)) {
ws.send(JSON.stringify({error: 'Invalid user ID'}));
return;
}
// Use parameterized queries
db.query('DELETE FROM users WHERE id = ?', [data.userId]);
ws.send(JSON.stringify({success: true}));
}
} catch (error) {
ws.send(JSON.stringify({error: 'Invalid message format'}));
}
});
});
5. Security Testing for WebSockets
Tools for WebSocket Security Testing
Burp Suite
- WebSocket interception and modification
- Repeater for manual testing
- Intruder for automated fuzzing
OWASP ZAP
- WebSocket proxy
- Fuzzing capabilities
- Automated scanning
WebSocket Fuzzing Tools
- WSFuzzer
- WebSocket-Scanner
Testing Methodology
Handshake Testing
- Test for missing origin validation
- Check for authentication bypass
- Verify proper use of TLS
Message Testing
- Test for input validation flaws
- Check for authorization issues
- Verify proper sanitization of output
State Management Testing
- Test session handling
- Check for state manipulation
- Verify proper cleanup
Denial of Service Testing
- Test connection limits
- Check for resource exhaustion
- Verify message size limits
6. Best Practices for Secure WebSocket Implementation
Server-Side Recommendations
Use WSS (WebSockets over TLS)
- Always use
wss://in production - Implement proper certificate validation
- Disable weak ciphers and protocols
- Always use
Implement Proper Authentication
- Authenticate during the handshake
- Validate session tokens
- Re-authenticate sensitive operations
Validate the Origin Header
// Example origin validation wss.on('connection', function connection(ws, req) { const origin = req.headers.origin; if (!isAllowedOrigin(origin)) { ws.close(1008, 'Policy violation'); return; } // Continue with connection });Implement Rate Limiting
- Limit connection attempts per IP
- Limit message frequency
- Implement connection timeouts
Validate All Input
- Validate message format and content
- Use parameterized queries for database operations
- Implement strict type checking
Implement Proper Authorization
- Check permissions for each operation
- Use principle of least privilege
- Implement proper role-based access control
Client-Side Recommendations
Hardcode WebSocket URLs
// Good practice const socket = new WebSocket('wss://yourdomain.com/ws'); // Bad practice (vulnerable to SSRF) const host = getQueryParam('host'); const socket = new WebSocket(`wss://${host}/ws`);Sanitize All Data
- Escape HTML when inserting into DOM
- Use textContent instead of innerHTML when possible
- Implement CSP headers
Implement Error Handling
- Handle connection failures gracefully
- Validate message format
- Implement proper logging
Secure Cookie Configuration
- Set
Secureflag for HTTPS-only transmission - Set
HttpOnlyto prevent JavaScript access - Consider
SameSite=Strictfor CSRF protection
- Set
Deployment Considerations
Use a Reverse Proxy
- Implement Web Application Firewall (WAF)
- Enable DDoS protection
- Monitor for unusual traffic patterns
Implement Logging and Monitoring
- Log connection attempts and errors
- Monitor for suspicious activity
- Implement alerting for security events
Regular Security Testing
- Conduct penetration testing
- Perform code reviews
- Use automated security scanning tools
Keep Dependencies Updated
- Regularly update WebSocket libraries
- Apply security patches promptly
- Monitor for security advisories
By following these best practices and understanding common vulnerabilities, developers can create more secure WebSocket implementations that protect against a wide range of attacks.