VYPR
High severityNVD Advisory· Published Sep 4, 2019· Updated Aug 4, 2024

CVE-2019-13209

CVE-2019-13209

Description

Rancher 2 through 2.2.4 is vulnerable to Cross-Site Websocket Hijacking, allowing an attacker to execute Kubernetes API commands with the victim's identity.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Rancher 2 through 2.2.4 is vulnerable to Cross-Site Websocket Hijacking, allowing an attacker to execute Kubernetes API commands with the victim's identity.

Overview

CVE-2019-13209 is a Cross-Site Websocket Hijacking vulnerability in Rancher, a Kubernetes management platform, affecting versions 2.0 to 2.2.4. The root cause is the lack of a cross-origin check for WebSocket connections, as confirmed by the security patch that adds an origin check in the websocket handler [1][3].

Exploitation

For exploitation, the victim must be authenticated to a Rancher server and visit a malicious third-party website while logged in. Under these conditions, the attacker can trigger a WebSocket connection to the Rancher server from the attacker-controlled site, without proper origin validation. By doing so, the attacker can hijack a management channel and issue arbitrary Kubernetes API requests using the victim's credentials and permissions [1][2].

Impact

Successful exploitation allows the attacker to execute commands on the targeted Kubernetes cluster with the full privileges of the victim user. This can lead to unauthorized access to cluster resources, configuration changes, data exfiltration, or further compromise of managed workloads [2].

Mitigation

The vulnerability is addressed in Rancher release v2.2.5, which introduces an origin check for WebSocket requests [1][3]. Users should upgrade to v2.2.5 or later. No workarounds are mentioned in the public sources; upgrading is the recommended mitigation.

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/rancher/rancherGo
>= 2.0.0, < 2.0.162.0.16
github.com/rancher/rancherGo
>= 2.1.0, < 2.1.112.1.11
github.com/rancher/rancherGo
>= 2.2.0, < 2.2.52.2.5

Affected products

2

Patches

1
0ddffe484adc

Add websocket origin check

https://github.com/rancher/rancherDax McDonaldJun 17, 2019via ghsa
4 files changed · +118 3
  • pkg/clusterrouter/router.go+6 2 modified
    @@ -11,7 +11,6 @@ import (
     )
     
     type Router struct {
    -	clusterLookup ClusterLookup
     	serverFactory *factory
     }
     
    @@ -25,7 +24,12 @@ func New(localConfig *rest.Config, lookup ClusterLookup, dialer dialer.Factory,
     func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
     	c, handler, err := r.serverFactory.get(req)
     	if err != nil {
    -		response(rw, httperror.ServerError, err.Error())
    +		e, ok := err.(*httperror.APIError)
    +		if ok {
    +			response(rw, e.Code, e.Message)
    +		} else {
    +			response(rw, httperror.ServerError, err.Error())
    +		}
     		return
     	}
     
    
  • pkg/websocket/handler.go+89 0 added
    @@ -0,0 +1,89 @@
    +package websocket
    +
    +import (
    +	"encoding/json"
    +	"net/http"
    +	"net/url"
    +	"strings"
    +
    +	"github.com/rancher/norman/httperror"
    +)
    +
    +func NewWebsocketHandler(handler http.Handler) http.Handler {
    +	return &websocketHandler{
    +		handler,
    +	}
    +}
    +
    +type websocketHandler struct {
    +	next http.Handler
    +}
    +
    +func (h websocketHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    +	if isWebsocket(req) {
    +		if !checkSameOrigin(req) {
    +			response(rw, httperror.PermissionDenied, "origin not allowed")
    +			return
    +		}
    +	}
    +	h.next.ServeHTTP(rw, req)
    +}
    +
    +// Inspired by https://github.com/gorilla/websocket/blob/80c2d40e9b91f2ef7a9c1a403aeec64d1b89a9a6/server.go#L87
    +// checkSameOrigin returns true if the origin is not set or is equal to the request host.
    +func checkSameOrigin(r *http.Request) bool {
    +	origin := r.Header["Origin"]
    +	if len(origin) == 0 {
    +		return true
    +	}
    +	u, err := url.Parse(origin[0])
    +	if err != nil {
    +		return false
    +	}
    +
    +	if u.Port() == "" {
    +		return u.Host == r.Host
    +	}
    +	return u.Host == r.Host && u.Port() == portOnly(r.Host)
    +}
    +
    +// isWebsocket returns true if the request is a websocket
    +func isWebsocket(r *http.Request) bool {
    +	if !headerListContainsValue(r.Header, "Connection", "upgrade") {
    +		return false
    +	}
    +	return true
    +}
    +
    +// headerListContainsValue returns true if the token header with the given name contains token.
    +func headerListContainsValue(header http.Header, name string, value string) bool {
    +	for _, v := range header[name] {
    +		for _, s := range strings.Split(v, ",") {
    +			if strings.EqualFold(value, strings.TrimSpace(s)) {
    +				return true
    +			}
    +		}
    +	}
    +	return false
    +}
    +
    +func response(rw http.ResponseWriter, code httperror.ErrorCode, message string) {
    +	rw.WriteHeader(code.Status)
    +	rw.Header().Set("content-type", "application/json")
    +	json.NewEncoder(rw).Encode(httperror.NewAPIError(code, message))
    +}
    +
    +// portOnly returns the port part of localhost:port, without the leading colon
    +func portOnly(hostport string) string {
    +	colon := strings.IndexByte(hostport, ':')
    +	if colon == -1 {
    +		return ""
    +	}
    +	if i := strings.Index(hostport, "]:"); i != -1 {
    +		return hostport[i+len("]:"):]
    +	}
    +	if strings.Contains(hostport, "]") {
    +		return ""
    +	}
    +	return hostport[colon+len(":"):]
    +}
    
  • server/server.go+3 1 modified
    @@ -21,6 +21,7 @@ import (
     	"github.com/rancher/rancher/pkg/pipeline/hooks"
     	"github.com/rancher/rancher/pkg/rkenodeconfigserver"
     	"github.com/rancher/rancher/pkg/telemetry"
    +	"github.com/rancher/rancher/pkg/websocket"
     	"github.com/rancher/rancher/server/capabilities"
     	"github.com/rancher/rancher/server/responsewriter"
     	"github.com/rancher/rancher/server/ui"
    @@ -58,8 +59,9 @@ func Start(ctx context.Context, httpPort, httpsPort int, localClusterEnabled boo
     	if err != nil {
     		return err
     	}
    +	websocketHandler := websocket.NewWebsocketHandler(authedHandler)
     
    -	auditHandler := audit.NewAuditLogFilter(ctx, auditLogWriter, authedHandler)
    +	auditHandler := audit.NewAuditLogFilter(ctx, auditLogWriter, websocketHandler)
     
     	webhookHandler := hooks.New(scaledContext)
     
    
  • tests/integration/suite/test_tokens.py+20 0 modified
    @@ -1,3 +1,8 @@
    +import pytest
    +import rancher
    +from .conftest import BASE_URL
    +
    +
     def test_certificates(admin_mc):
         client = admin_mc.client
     
    @@ -10,3 +15,18 @@ def test_certificates(admin_mc):
                 currentCount += 1
     
         assert currentCount == 1
    +
    +
    +def test_websocket(admin_mc):
    +    client = rancher.Client(url=BASE_URL, token=admin_mc.client.token,
    +                            verify=False)
    +    # make a request that looks like a websocket
    +    client._session.headers["Connection"] = "upgrade"
    +    client._session.headers["Upgrade"] = "websocket"
    +    client._session.headers["Origin"] = "badStuff"
    +    # do something with client now that we have a "websocket"
    +
    +    with pytest.raises(rancher.ApiError) as e:
    +        client.list_cluster()
    +
    +    assert e.value.error.Code.Status == 403
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.