Security

Updated:
Author: Erkin Khidirov

By nature, a TURN server accepts packets from the internet and forwards them to other addresses. Therefore, an improperly secured TURN server can become a target for two threats: unauthorized bandwidth consumption by third-party applications and SSRF attacks against internal infrastructure. In Slyber Turn Server, both issues are resolved at the architectural level.

1. Temporary Cryptographic Credentials

Slyber Turn Server does not store a user database on disk and does not require database queries on every connection. It uses the industry-standard WebRTC ephemeral token scheme:

  • Username (username): consists of the token expiration time (Unix timestamp in seconds) and the user identifier, separated by a colon: <expiration_timestamp>:<user_id> (for example, 1761580000:user_777).
  • Password (password): calculated using the formula Base64(HMAC-SHA1(TURN_SECRET, username)).

When a client connects to the TURN server:

  1. The server validates the username format and verifies that the current time has not exceeded expiration_timestamp.
  2. It independently computes the HMAC-SHA1 of the username using its own TURN_SECRET and verifies the packet signature.
  3. As soon as the TTL expires, the credentials automatically become invalid.

2. Defense-in-Depth for the HTTP API (/api/turn-credentials)

You can flexibly combine 4 layers of protection for the credential issuance endpoint:

Layer A: Secret Key (X-Turn-Secret / Bearer)

A request can include the X-Turn-Secret: <your_TURN_SECRET> or Authorization: Bearer <your_TURN_SECRET> header.

  • Key verification is performed in constant time via subtle.ConstantTimeCompare, completely eliminating timing attacks.
  • If REQUIRE_SECRET=true is enabled, requests without this header are immediately rejected with a 401 Unauthorized status code.
  • Providing a valid secret key grants privileges: it bypasses Rate Limiter quotas, CORS/IP filters, and allows requesting a custom ?ttl= that exceeds the default CREDENTIAL_TTL_SEC.

Layer B: IP and Subnet Allowlist

If ALLOWED_IPS is configured (for example, 198.51.100.25, 10.20.0.0/16), requests without a secret key are permitted only from the specified IPv4/IPv6 addresses or CIDR subnets (all others receive 403 Forbidden).

Layer C: Smart CORS Validator

If a request comes directly from a browser (without a secret key and without a strict ALLOWED_IPS restriction), the server validates the Origin header:

  • Exact domain and scheme matching is supported (for example, https://exmaple.com).
  • Automatic subdomain support: if https://exmaple.com is specified in ALLOWED_ORIGINS, the validator will automatically allow requests from any subdomain using the same scheme, such as https://rtc.example.com or https://app.example.com.

Layer D: Flood Protection

For all requests without a secret key, a built-in per-IP rate limiter based on the Token Bucket algorithm is enforced:

  • Refill rate: 2.0 requests per second per IP.
  • Maximum burst: up to 10 consecutive requests. Exceeding this limit returns 429 Too Many Requests.
  • DDoS memory protection: the table stores a maximum of 50,000 unique IP addresses and automatically purges entries older than 2 minutes every 2 minutes.

Safe Operation Behind a Reverse Proxy (Nginx / Caddy)

The server is protected against X-Forwarded-For and X-Real-IP header spoofing: it trusts these headers only when the physical TCP connection originates from a local interface (127.0.0.1 / ::1, i.e., from your local Nginx instance) or when the request is authenticated with a valid X-Turn-Secret.

3. Built-in SSRF Attack Protection

In the TURN protocol, the client specifies which IP address (Peer IP) the server should forward a packet to. An attacker might attempt to specify the internal address of your database (10.0.0.42), a local server port (127.0.0.1), or a cloud metadata service like AWS/GCP (169.254.169.254) instead of a peer's address.

Slyber Turn Server includes a built-in PeerFilter that inspects every permission request (PermissionHandler) and separates blocked addresses into two categories:

Category 1: Critical SSRF Threats

Attempts to reach these addresses are strictly blocked, increment the turn_ssrf_blocked_total attack counter, are logged to the security audit log, and add the client's IP to the offender list (top_offenders) with a 5x penalty weight:

  • Loopback addresses: 127.0.0.0/8, ::1.
  • System "This Network" range: 0.0.0.0/8 (a common vector for bypassing 127.0.0.1 filters).
  • Link-Local and Cloud Metadata (AWS / GCP / DigitalOcean / Azure): 169.254.0.0/16 (including 169.254.169.254), IPv6 AWS IMDS fd00:ec2::254/128, fe80::/10.
  • Broadcast, multicast, and unspecified addresses: 255.255.255.255, 0.0.0.0, Multicast.
  • Local network interfaces of the TURN host itself: on startup, the server scans all of its network interfaces (net.InterfaceAddrs()) and prohibits connections to its own internal IPs.

Category 2: Browser Local ICE Candidates

When establishing a WebRTC connection, browsers often mistakenly attempt to probe a peer's local home addresses (such as 192.168.1.1) through the TURN server. This is not a malicious attack, but standard WebRTC ICE behavior. To avoid cluttering the security log with false alarms, PeerFilter drops these packets silently and increments a separate lan_skipped counter instead:

  • RFC 1918 private networks and IPv6 ULA (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7).
  • Carrier-grade NAT / Tailscale networks (CGNAT 100.64.0.0/10).
  • Documentation, benchmarking, and reserved subnets (192.0.0.0/24, 192.0.2.0/24, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 240.0.0.0/4, 2001:db8::/32, 2001::/32, 2002::/16, 64:ff9b::/96).

TURN Hairpinning Support

At the same time, the filter explicitly allows forwarding packets to the server's own public address (PUBLIC_IP). This is required for TURN Hairpinning scenarios - when both call participants are behind strict NATs and both are connected to the same TURN server, forwarding media streams between two relay ports on that server.