WebRTC Security: TURN Servers, IP Leakage, and Peer Connection Vulnerabilities

WebRTC powers browser-based video, voice, and peer-to-peer data. The security model is complex: TURN server credentials can be stolen to relay traffic, ICE candidates expose internal network topology, and signaling channels require careful authentication. A developer guide to the real attack surface.

WebRTC (Web Real-Time Communication) is the technology behind browser-native video calls, voice chat, and peer-to-peer data channels. It’s in your conferencing tools, your customer support widgets, your collaborative applications. The security model is more complex than most web application security, because WebRTC introduces several distinct attack surfaces that standard web application testing doesn’t cover.

Understanding WebRTC security requires understanding the connection establishment flow first. Then the attack surfaces become clear.

How WebRTC Connections Work

A WebRTC connection between two peers requires three components working together:

Signaling — WebRTC does not define a signaling protocol. Your application is responsible for exchanging Session Description Protocol (SDP) offers and answers between peers. This usually happens over WebSocket or a REST API. SDP describes the media capabilities, codecs, and network information for the session.

ICE (Interactive Connectivity Establishment) — ICE handles NAT traversal. Each peer gathers a set of ICE candidates — possible network paths through which the peer can be reached — and exchanges them via signaling. The ICE agent then performs connectivity checks to find the best working path.

STUN and TURN servers — STUN servers help peers discover their public IP addresses. TURN (Traversal Using Relays around NAT) servers relay media traffic when direct peer-to-peer connection fails (which is common in enterprise networks with symmetric NAT). TURN is essential for reliable connectivity but introduces a relay server into the media path.

Attack Surface 1: TURN Credential Theft

TURN servers require authentication to prevent open relay abuse. Credentials are typically short-lived HMAC-based tokens generated by your application server. The client JavaScript receives these credentials and uses them to authenticate with the TURN server.

The attack: an attacker who obtains valid TURN credentials — from a compromised user account, XSS, or a vulnerable credential generation endpoint — can use your TURN server as a general-purpose traffic relay for their own purposes. This is expensive (bandwidth and compute costs), and potentially implicates your infrastructure in other attacks.

How credentials are exposed:

// Common (vulnerable) pattern: credentials embedded in page HTML or JS bundle
const iceServers = [{
  urls: 'turn:turn.example.com:3478',
  username: 'staticuser',        // ← static credentials in client code
  credential: 'staticpassword'   // ← never rotate, trivially extractable
}];

The correct approach: short-lived HMAC credentials

TURN servers that support the TURN REST API (RFC 5766) accept time-limited credentials. Generate them server-side per-session:

// Server-side credential generation (Node.js)
const crypto = require('crypto');

function generateTurnCredentials(username, secret, ttl = 3600) {
  const timestamp = Math.floor(Date.now() / 1000) + ttl;
  const temporaryUser = `${timestamp}:${username}`;
  const credential = crypto
    .createHmac('sha1', secret)
    .update(temporaryUser)
    .digest('base64');
  
  return {
    username: temporaryUser,
    credential: credential,
    ttl: ttl
  };
}

// Return credentials only to authenticated users via API
app.get('/api/turn-credentials', requireAuth, (req, res) => {
  const creds = generateTurnCredentials(req.user.id, process.env.TURN_SECRET);
  res.json({
    iceServers: [{
      urls: process.env.TURN_SERVER_URL,
      username: creds.username,
      credential: creds.credential
    }]
  });
});

Key points: the TURN secret should never leave your server, credentials should have a short TTL (1 hour is reasonable), and the credential generation endpoint must require authentication — unauthenticated access to TURN credentials is an open relay.

Attack Surface 2: ICE Candidate IP Address Leakage

When a browser gathers ICE candidates, it includes network interface information — including private IP addresses (RFC 1918 ranges) and in older configurations, even VPN or virtual interface IPs. This leaks internal network topology to peer participants.

In a meeting application, every participant can observe the ICE candidates of every other participant, revealing private IP ranges in use, whether participants are on VPNs, and the approximate network topology of the other side.

Browser mDNS obfuscation: Modern browsers (Chrome, Firefox, Edge) now obfuscate local IP addresses in ICE candidates using mDNS names (*.local), replacing the actual IP with a randomly generated mDNS hostname. This prevents direct IP leakage from the browser to the peer.

However, this only applies to the browser’s own implementation. If your application is a native or Electron app using WebRTC, or if you’re processing ICE candidates server-side, you may still expose real IPs.

Server-side ICE filtering: Before passing ICE candidates from one peer to another through your signaling server, you can filter or obfuscate them:

// Signaling server: filter relay and srflx candidates only (hide host candidates)
function filterIceCandidates(candidates) {
  return candidates.filter(candidate => {
    // Only pass relay candidates - strips host IP information
    return candidate.candidate.includes('typ relay');
  });
}

Note: filtering to relay-only candidates forces all traffic through your TURN server, which increases load but eliminates IP leakage. Whether this trade-off is appropriate depends on your application’s privacy requirements.

Attack Surface 3: Signaling Channel Security

The signaling channel — typically a WebSocket connection — is where SDP offers and answers, ICE candidates, and session control messages are exchanged. The signaling channel is application-defined, and its security entirely depends on your implementation.

Authentication: Every signaling message must be authenticated to the session it belongs to. Without authentication, an attacker who can inject messages into the signaling channel can:

  • Replace ICE candidates with attacker-controlled relay addresses (man-in-the-middle)
  • Terminate sessions by sending hang-up signals
  • Inject additional participants into calls
// Signaling message validation
function validateSignalingMessage(message, sessionId, userId) {
  // Verify the message belongs to this session
  if (message.sessionId !== sessionId) {
    throw new Error('Session ID mismatch');
  }
  // Verify the sender is a legitimate participant
  if (!isSessionParticipant(sessionId, userId)) {
    throw new Error('Unauthorized signaling participant');
  }
  // Validate message structure and content
  if (!isValidSdpOrCandidate(message)) {
    throw new Error('Invalid signaling message format');
  }
}

CSRF protection on signaling WebSocket: WebSocket connections are not subject to CORS preflight checks in the same way HTTP requests are. If your WebSocket signaling endpoint doesn’t validate the Origin header, a malicious page can establish a WebSocket connection to your signaling server while the user is authenticated.

// WebSocket server: validate Origin header
wss.on('connection', (ws, req) => {
  const origin = req.headers.origin;
  if (!allowedOrigins.includes(origin)) {
    ws.close(4001, 'Invalid origin');
    return;
  }
  // Continue with authenticated session setup
});

Attack Surface 4: DTLS-SRTP and Media Encryption

WebRTC mandates DTLS-SRTP for encrypting media streams — audio, video, and data channels are all encrypted with keys negotiated during DTLS handshake. This is good: passive traffic capture of the media stream is not sufficient to decrypt content.

The common pitfall: failing to verify DTLS fingerprints. During SDP negotiation, each peer includes a fingerprint of its DTLS certificate. The expectation is that these fingerprints are validated to prevent a DTLS man-in-the-middle attack where a relay inserts itself between peers and decrypts media.

In practice, many WebRTC implementations and signaling servers do not validate that the DTLS fingerprint received in the SDP matches what was actually used in the connection. If your signaling channel can be manipulated (see above), an attacker can substitute their own DTLS fingerprint, establishing a relay through which they can intercept media.

Verify that your WebRTC implementation validates DTLS fingerprints and that your signaling server passes them unmodified. For applications with strong end-to-end confidentiality requirements, consider explicit certificate pinning at the application layer.

Data Channel Security

WebRTC data channels can transfer arbitrary binary or text data directly between peers over SCTP/DTLS. They are often used for file transfer, gaming, and custom application data.

Buffer exhaustion: Data channels have configurable buffer sizes. A malicious peer can flood a data channel with data faster than the receiver processes it, potentially causing memory exhaustion. Set bufferedAmountLowThreshold and handle backpressure:

const dataChannel = peerConnection.createDataChannel('data', {
  ordered: true,
  maxRetransmits: 3
});

// Implement flow control
dataChannel.bufferedAmountLowThreshold = 65536; // 64KB
dataChannel.onbufferedamountlow = () => {
  // Resume sending when buffer drains
  resumeSending();
};

// Pause sending when buffer fills
if (dataChannel.bufferedAmount > 1024 * 1024) { // 1MB
  pauseSending();
}

Content validation: Data arriving over WebRTC data channels comes from a peer, not from your server. If the received data is processed by your application — parsed, executed, or stored — it must be validated and sanitised as untrusted input. A compromised peer or a man-in-the-middle can send arbitrary data channel content.

Testing WebRTC Applications

Standard web application testing tools don’t cover WebRTC attack surface. Supplement your testing with:

  • TURN credential validation: Attempt to use credentials after TTL expiry to confirm they are genuinely invalidated. Attempt to use credentials without authentication to confirm the generation endpoint requires auth.
  • ICE candidate analysis: Capture and review ICE candidates passed through your signaling server to identify IP information being exposed to peers.
  • Signaling injection: Test whether your signaling server accepts messages without valid session authentication. Try sending SDP offers from an unauthenticated WebSocket connection.
  • Origin header validation: Test WebSocket connections from disallowed origins to confirm they are rejected.
  • DTLS fingerprint verification: Verify that fingerprint validation is implemented and that substituting a manipulated fingerprint causes connection rejection.

WebRTC’s security model is solid when correctly implemented. The attack surface is real but well-defined — the goal is ensuring your application’s implementation doesn’t undermine the protocol’s built-in protections.