Tampering Live WebSocket Messages in Juice Shop: A Hands-On Message-Integrity Lab

Tampering Live WebSocket Messages in Juice Shop: A Hands-On Message-Integrity Lab

  • Post author:
  • Post published:
  • Post category:Security
📋 Key Takeaways
  • Yes—OWASP Juice Shop's live chat feature runs over a WebSocket that accepts client-supplied JSON frames with minimal server-side validation.
  • HTTP penetration testing has a well-established playbook: intercept the request, modify the parameter, observe the response.
  • Chrome DevTools gives you frame-level visibility without any proxy
  • DevTools shows you frames but doesn't give you an edit-and-replay button for outbound traffic.
10 min read · 1,890 words
Educational & Ethical Use Only — This article is provided for educational and ethical cybersecurity research purposes only. The techniques described should only be used on systems you own or have explicit permission to test. Always follow responsible disclosure and the laws applicable to you. Mitigations are included so engineers can harden real systems.
Security· 10 min read

🔎 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-shop
  • docker 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.git
  • cd 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:

  1. Open DevTools (F12) and go to the Network tab.
  2. Filter by WS to show only WebSocket connections.
  3. Trigger the chat: click the chat/user support icon in the Juice Shop UI. A new socket connection appears in the list.
  4. 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 id field 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 query string 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 query field 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

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.

Hmmnm
Published by Hmmnm

Hands-on cybersecurity tutorials, CVE breakdowns, and guided learning paths — written and lab-tested by the Hmmnm team.

Keep going — the structured way
This post is one step. The learning paths chain the next ones for you, with progress tracking and no account needed.
Follow a learning path →