Skip to content

feat: add WebSocket security vulnerability module (Issue #527)#597

Open
cisaula wants to merge 2 commits into
SasanLabs:masterfrom
cisaula:feat/websocket-vulnerability
Open

feat: add WebSocket security vulnerability module (Issue #527)#597
cisaula wants to merge 2 commits into
SasanLabs:masterfrom
cisaula:feat/websocket-vulnerability

Conversation

@cisaula
Copy link
Copy Markdown

@cisaula cisaula commented Apr 24, 2026

Closes #527

Adds a new WebSocket Security Vulnerability module with 4 levels demonstrating common WebSocket security flaws and their secure fix.

Level 1 (CWE-306): Unauthenticated WebSocket — endpoint accepts all connections without any authentication

Level 2 (CWE-20): Message Injection — user input reflected back without sanitization, enabling XSS and injection attacks

Level 3 (CWE-346): No Origin Validation — server never checks the Origin header, enabling Cross-Site WebSocket Hijacking

Level 4 (Secure): Token-based authentication, origin restricted to localhost, HTML sanitization via StringEscapeUtils.escapeHtml4()

Testing
33 unit tests added (21 handler tests + 12 controller tests), all passing

Manually tested all 4 levels in browser against running app

Follows existing project patterns: @VulnerableAppRestController, @AttackVector, @VulnerableAppRequestMapping

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.13793% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.67%. Comparing base (f765968) to head (a3fb938).

Files with missing lines Patch % Lines
...bility/websocket/WebSocketVulnerabilityConfig.java 0.00% 10 Missing ⚠️
...erability/websocket/InjectionWebSocketHandler.java 81.81% 2 Missing ⚠️
...ity/websocket/UnauthenticatedWebSocketHandler.java 83.33% 2 Missing ⚠️
...ulnerability/websocket/SecureWebSocketHandler.java 90.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #597      +/-   ##
============================================
+ Coverage     51.21%   51.67%   +0.46%     
- Complexity      465      486      +21     
============================================
  Files            70       76       +6     
  Lines          2718     2775      +57     
  Branches        287      288       +1     
============================================
+ Hits           1392     1434      +42     
- Misses         1195     1209      +14     
- Partials        131      132       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Member

@preetkaran20 preetkaran20 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a strong functional addition and the level progression is well thought out.

One architectural concern: unlike REST vulnerabilities which are annotation-driven (@AttackVector, @VulnerableAppRequestMapping), WebSocket handlers are manually registered via WebSocketConfigurer. This creates an inconsistency in how vulnerability modules are defined and extended.

Suggestion:

  • Introduce a @VulnerableWebSocketEndpoint annotation (similar to REST)
  • Auto-register handlers by scanning annotated beans (you can look at existing code where we are scanning annotations)
  • Align WebSocket handlers with @AttackVector metadata instead of duplicating payload/config in multiple places

This would make WebSocket vulnerabilities first-class citizens in the platform and improve extensibility and maintainability.

vulnerabilityExposed = VulnerabilityType.WEBSOCKET_MISSING_AUTHENTICATION,
description = "WEBSOCKET_NO_AUTHENTICATION",
payload = "WEBSOCKET_PAYLOAD_LEVEL_1")
@VulnerableAppRequestMapping(value = LevelConstants.LEVEL_1, htmlTemplate = "LEVEL_1/WebSocket")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can create annotations for Websocket as well similar to how rest is being handled.

value = "WebSocketVulnerability")
public class WebSocketVulnerability {

private static final String WS_LEVEL1_PATH = "/ws/websocket-vulnerability/level1";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These paths can be derived from the annotations.

@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String query = session.getUri() != null ? session.getUri().getQuery() : null;
if (query == null || !query.contains("token=" + VALID_TOKEN)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think better way is to use spring utility to parse the query params. This is a bit fragile way to handle token.

if (!config) return;

const wsScheme = window.location.protocol === "https:" ? "wss" : "ws";
let wsUrl =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please look at VulnerableApp.js and see if any of the methods you can use to construct entire url?


log("Connecting to: " + wsUrl, "info");

try {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please abstract it out as a method.

if (config.requiresToken) {
const token = document.getElementById("wsToken").value.trim();
if (token) {
wsUrl += "?token=" + encodeURIComponent(token);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have this not as part of URL as this is security risk. may be create a level where it is not sent as query param?

Why not in query param? Query params are logged by various things like Load balancer, reverse proxy etc so it is not good to send sensitive information in the url. also this might get retained by browser too.

}
}

log("Connecting to: " + wsUrl, "info");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like here we are logging.

}

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even if this is unauthenticated but it is not doing anything harmful. we should give user a way to exploit this level too.

String payload = message.getPayload();
LOGGER.info("Received message without validation: {}", payload);
// Vulnerability: user input reflected directly without sanitization
session.sendMessage(new TextMessage("Server processed: " + payload));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how can we exploit it will XSS payload?
Similarly with Drop table payload? I think we should either tie it and make it exploitable.

@preetkaran20
Copy link
Copy Markdown
Member

@cisaula are you working on this?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WebSocket security Vulnerability

3 participants