🔎 Last updated · Editorial policy · Report an error
TL;DR: Can You Tamper with Juice Shop’s Live WebSocket Messages?
Yes—OWASP Juice Shop’s live chat feature runs over a WebSocket that accepts client-supplied JSON frames with minimal server-side validation. In this hands-on lab, you’ll capture frames in Chrome DevTools, replay them verbatim from the console, mutate fields like user IDs and admin flags, and even intercept traffic with Burp Suite. The results expose exactly where message-integrity controls belong: on the server.
Why WebSocket Message Integrity Matters
HTTP penetration testing has a well-established playbook: intercept the request, modify the parameter, observe the response. WebSocket tampering breaks part of that mental model. Once the handshake completes, traffic flows bidirectionally over a single TCP connection, and many developers treat frames after the handshake as implicitly trusted—”the client already authenticated, so the messages must be legitimate.” That assumption is the vulnerability.
The server has no more reason to trust frame contents than it does an HTTP request body. But in practice, WebSocket handlers frequently skip schema validation, skip session binding, and accept client-supplied identifiers at face value. You’re not attacking a transport—you’re attacking a message-processing pipeline that was never taught to distrust its input. OWASP’s WebSocket security guidance (part of the Web Security Testing Guide, WSTG-INPV-11) flags exactly this: origin checking, input validation, and denial-of-service resilience for WS endpoints are routinely missing.
Juice Shop makes an ideal lab because its live chat support bot (the “Support Chat” feature, backed by a socket.io-style connection) is a real, functioning WebSocket with a challenge or two attached—and because it’s designed to be attacked. Everything below happens on a local instance you own.
Lab Setup: Running Juice Shop Locally
The fastest path is Docker:
docker pull bkimminich/juice-shopdocker run --rm -p 3000:3000 bkimminich/juice-shop
Or via Node (Node 18+ recommended for recent Juice Shop releases, v16.x at time of writing):
git clone https://github.com/juice-shop/juice-shop.gitcd juice-shop && npm install && npm start
Confirm the app is up by browsing to http://localhost:3000. To make sure challenge-solving is enabled (you’ll want to see challenge notifications fire), check that application.yaml or your environment config doesn’t set challenges.showSolvedNotifications: false. Verify the WebSocket endpoint is reachable: open DevTools, load the app, and you should see an ws://localhost:3000 or socket.io connection in the Network tab. If you see it listed, you’re ready.
Finding the Live WebSocket Channel in Browser DevTools
Chrome DevTools gives you frame-level visibility without any proxy:
- Open DevTools (
F12) and go to the Network tab. - Filter by WS to show only WebSocket connections.
- Trigger the chat: click the chat/user support icon in the Juice Shop UI. A new socket connection appears in the list.
- Click the connection, then open the Messages pane. Frames flow in both directions—outbound frames (send() calls from the client, tinted green) and inbound frames from the server (white).
Send a chat message like “What is a quality product?” and watch the frames. Juice Shop’s chat protocol is JSON: frames carry fields such as action, query, id, and in some flows, a token used by the bot to track conversation state. Right-click a frame and choose Copy message to grab the exact payload. Save it—you’ll replay it in a moment.
This answers the first obvious question: capturing live WebSocket messages in Juice Shop requires nothing more than stock DevTools. No proxy, no extensions, no scripting.
Replaying Frames from the Console
DevTools shows you frames but doesn’t give you an edit-and-replay button for outbound traffic. The console does. Grab a handle to the open socket:
In many apps the socket object isn’t exposed on window, but you can usually find it via the socket.io client instance or by monkey-patching WebSocket.prototype.send before reloading and connecting, so every socket registers itself:
const _send = WebSocket.prototype.send; WebSocket.prototype.send = function(data){ window.__lastSocket = this; return _send.call(this, data); }
Reload the page, open the chat, then from the console:
__lastSocket.send('{ "action": "query", "query": "What is a quality product?", "id": 1 }')
Replay the exact captured frame verbatim. Observed result: the server accepts it. The bot responds again, identically. No nonce, no sequence number, no idempotency check, no signature—duplicate frames are indistinguishable from fresh ones. That’s a clean confirmation of the replay weakness: the server validates shape at best, never origin or freshness.
Modifying Frames: Changing Fields in a Live Message
Now the interesting part. Take the captured JSON and mutate fields before sending:
- Change the
idfield to an arbitrary integer ("id": 9999). The server generally accepts it—the ID isn’t bound to your session or validated against server-side state. - Inject unexpected fields (
"isAdmin": true,"role": "admin"). Most are ignored silently, which is itself informative: the handler doesn’t reject unknown properties, so any downstream consumer of the frame that does read those fields would honor them. - Alter the
querystring to probe the chatbot’s responses, including the known prompt-injection payloads Juice Shop hides in the chat (“ignore previous instructions…”). Some of these trigger the JSON Web Token/GDPR-adjacent chat challenges—the bot may leak a token or reveal hidden behavior when the right phrase lands. - Send malformed JSON or wrong types (
"id": "abc"). Depending on the Juice Shop version, you’ll get either a graceful error frame or a crashed socket—the latter being a small denial-of-service finding in itself.
The pattern is familiar to anyone who has tampered with HTTP parameters: the server trusts client-supplied structure until something throws. The difference is that here the tampering surface is a persistent, bidirectional channel, so you can iterate quickly—send, observe, mutate, send again—without re-issuing requests through a proxy.
Proxy-Level Tampering with Burp or ws-tcpgw Alternatives
Browser-console tampering covers most of this lab, but proxies give you systematic intercept-and-modify workflows:
- Burp Suite (Professional, or Community with limitations) intercepts WebSocket frames via the proxy listener. Enable the WS interceptor in Proxy > WebSockets history, toggle Intercept, and frames pause mid-flight for editing. Match/replace rules can automate mutations—swap the
queryfield on every outbound frame, for instance. - ws-tcpgw / OWASP WebSocket proxies: For scripted testing, tools like ws-tcpgw or writing a small Node.js MITM script (accept the client’s socket, open your own upstream socket, log and rewrite frames in both directions) give full programmatic control.
For wss:// endpoints you’d need TLS interception with a trusted CA cert installed—irrelevant here since localhost runs plain ws://, which is a reminder that even “secure” deployments are only as protected as the weakest hop.
What the Server Accepts vs. Rejects: Observed Results
| Mutation | Server Response | Challenge Impact |
|---|---|---|
| Replay captured frame verbatim | Accepted; bot responds again | None—no replay detection |
Changed id to arbitrary value |
Accepted | None—no session binding on the ID |
Added unknown fields (isAdmin, role) |
Accepted, fields ignored by this handler | None, but indicates missing schema enforcement |
Modified query with injection payload |
Accepted; bot behavior changes | Can trigger chatbot prompt-injection challenges |
| Malformed JSON / wrong types | Error frame or socket crash (version-dependent) | Robustness finding |
Honest caveats: exact behavior varies by Juice Shop version, and some handlers do validate the chat token. The point isn’t that every field is vulnerable—it’s that validation is inconsistent and largely client-trusting, which mirrors production WebSocket services.
Blue-Team Takeaways: Validating WebSocket Messages Server-Side
The fix list is short and unambiguous:
- Schema validation on every frame. Validate inbound JSON against a server-side schema (JSON Schema, zod, ajv)—reject unknown fields, wrong types, missing values. Never parse-and-hope.
- Session binding. Derive identity and authorization from the server-side session established at handshake—not from fields inside the frame. Client-supplied IDs are suggestions, not facts.
- Idempotency and anti-replay. Nonces, sequence numbers, or message IDs validated server-side to make replay useless.
- Rate limiting and message-size caps. Persistent sockets make it cheap to flood; enforce per-connection and per-user limits.
- Audit logging. Log frames with correlation IDs; you can’t investigate what you never recorded.
CISA’s Secure by Design guidance and OWASP’s API Top 10 both converge on the same principle: trust is a server-side property, never a client assertion.
Mapping the Lessons to OWASP and CTF Practice
This lab maps cleanly onto several frameworks:
- OWASP WSTG, WebSocket testing section (WSTG-CLNT-11 / INPV-11): origin validation, input validation, and DoS testing on WS endpoints.
- OWASP API Security Top 10: BOLA/BOPLA-style risks apply when frames carry object IDs; Broken Function-Level Authorization applies when actions aren’t bound to the session.
- OWASP MASVS: mobile clients using WebSockets fall under the same client-side trust and network-communication controls (MASVS-NETWORK).
- Juice Shop CTF flows: In hacking instructor and CTF mode, the chat challenges reward exactly this workflow—observe frames, understand the protocol, then push the bot with crafted input. The tampering skills here are the stepping stone to those challenges.
How does WS tampering differ from HTTP request tampering? Three ways: the channel is persistent (one auth moment, many messages), frames are often exempted from the validation middleware that guards HTTP routes, and bidirectionality means server-pushed messages are also an attack surface (injection into server-to-client frames can poison other users’ DOMs). Traditional HTTP tooling catches some of this; WS-aware tooling catches the rest.
Cleanup and Going Further
Tear down cleanly: stop the container (docker stop or the --rm flag handles it), or Ctrl+C the Node process. Clear browser DevTools patches by simply reloading—the prototype monkey-patch doesn’t survive navigation to a fresh page load without re-execution.
Next experiments once you’re comfortable:
- Binary frames: send non-text frames and watch how the handler’s parser copes.
- Reconnection abuse: flood reconnect cycles to test handshake rate limiting.
- Auth token refresh: capture frames sent after login and test whether stale or forged tokens still process actions.
For a deeper related dive, pair this lab with your XSS and SSTI practice—WebSocket-injected content often lands in a DOM sink, and the combination is where client-side compromise gets interesting.
Frequently Asked Questions
Is tampering with WebSocket messages legal?
Only on systems you own or are explicitly authorized to test. Juice Shop is an intentionally vulnerable application built for exactly this purpose, so a local instance is fair game. Tampering with WebSocket traffic on production services you don’t own is unauthorized access under computer-misuse laws virtually everywhere.
How do I see WebSocket traffic in Chrome DevTools?
Open the Network tab, filter by WS, click the socket connection, then switch to the Messages pane. Frames in both directions appear in real time; right-click any frame to copy its payload.
Does the server trust the client’s WebSocket frames by default?
Often yes, for convenience—which is exactly why replay and field-tampering work in this lab. Unless the server validates every inbound frame against its own schema and binds actions to the authenticated session, frame contents are attacker-controlled input like any other.
How can developers prevent WebSocket message tampering?
Validate every inbound frame against a server-side schema, bind actions to the authenticated session rather than client-supplied fields, add anti-replay controls, and rate-limit per connection. Never trust client-supplied IDs.
Does TLS (wss://) protect against tampering?
TLS protects the channel, not the application. It stops network observers from reading or modifying frames in transit—but it doesn’t stop the client itself from crafting arbitrary frames before encryption. The attacker owns the endpoint; application-layer validation is the only defense that matters here.
Related reading
- Build a Cross-Site WebSocket Hijacking Lab: CSWSH with wscat and a Vulnerable Chat App
- From ACARS to CPDLC: How Data Links Became Aviation's Attack Surface After Pen Test Partner's 757 Research



