VDB
KO
MEDIUM 5.3

GHSA-mhc4-g3wh-cw7m

Vitess: Missing authorization on vttablet /debug/vrlog exposes live VReplication SQL data

Details

## Vulnerability Details

**File**: `go/vt/vttablet/tabletmanager/vreplication/vrlog.go`

### Summary `vttablet`'s `/debug/vrlog` HTTP endpoint streams live VReplication event data — including the literal SQL DML statements being replicated by MoveTables, Reshard, Materialize, and "vitess"-strategy Online DDL workflows — with no authorization check at all. Every comparable "debugging" HTTP endpoint in vttablet/vtgate (querylogz, queryz, txlogz, livequeryz, schemaz, debugenv, hotrows, tablet_plans, query_stats, query_rules) calls `acl.CheckAccessHTTP(r, acl.DEBUGGING)` before serving data, so that the cluster operator's configured `--security-policy` (e.g. `deny-all`, `read-only`, or a custom plugin) is actually honored. `vrlog.go` is the one exception: it has zero references to the `acl` package.

### Root Cause `addHttpEndpoint()` registers `/debug/vrlog` via `servenv.HTTPHandleFunc`, and `vrlogStatsHandler()` immediately starts streaming subscribed `VrLogStats` events to the response writer without first calling `acl.CheckAccessHTTP(r, acl.DEBUGGING)`, unlike every sibling handler in the same family (see e.g. `go/vt/vttablet/tabletserver/querylogz.go`'s `querylogzHandler`, which calls the check first).

The data streamed is sensitive: `go/vt/vttablet/tabletmanager/vreplication/vplayer.go` calls `NewVrLogStats(...).Send(sql)` / `.Send(event.Statement)` for every row change and statement event flowing through a VReplication stream (vplayer.go:339, 700, 771, 785) — i.e. the literal SQL (including bound data values) being copied/replicated by MoveTables, Reshard, Materialize, and Online DDL.

### Attack Scenario 1. A cluster operator configures `--security-policy=deny-all` (or `read-only`, or a custom policy) specifically to lock down debugging/admin HTTP endpoints on vttablet, relying on this being uniformly enforced. 2. An attacker who can reach the vttablet debug HTTP port (common in Kubernetes/Prometheus-scraping deployments where this port is exposed beyond localhost) — but who does NOT have the `DEBUGGING`/admin role the policy requires — sends `GET /debug/vrlog`. 3. Every other debug endpoint correctly returns `403 Forbidden`. `/debug/vrlog` returns `200 OK` and streams live VReplication event data, including raw SQL DML statements containing application data values, for as long as the attacker keeps the connection open (bounded by `timeout`/`limit` query params, repeatable).

### Impact Confidentiality impact: disclosure of live replicated application data (potentially including PII or other sensitive column values) to an unauthorized actor, bypassing an access control the operator explicitly configured. No write/modify capability; this is a read-only information-disclosure / access-control-bypass issue, scoped to the vttablet debug HTTP listener.

### Vulnerable Code ```go // go/vt/vttablet/tabletmanager/vreplication/vrlog.go func addHttpEndpoint() { servenv.HTTPHandleFunc("/debug/vrlog", func(w http.ResponseWriter, r *http.Request) { ch := vrLogStatsLogger.Subscribe("vrlogstats") defer vrLogStatsLogger.Unsubscribe(ch) vrlogStatsHandler(ch, w, r) }) }

func vrlogStatsHandler(ch chan *VrLogStats, w http.ResponseWriter, r *http.Request) { timeout, limit := parseTimeoutLimitParams(r) // no acl.CheckAccessHTTP(r, acl.DEBUGGING) call anywhere in this file ... ```

### Recommended Fix ```go import "vitess.io/vitess/go/acl"

func vrlogStatsHandler(ch chan *VrLogStats, w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } timeout, limit := parseTimeoutLimitParams(r) ... ``` This mirrors the exact pattern already used by `querylogz.go`, `queryz.go`, `txlogz.go`, `livequeryz.go`, `schemaz.go`, `debugenv.go`, and `tx_serializer.go` (hotrows) in the same codebase.

### Verification Built v24.0.1 from source and wrote a standalone Go test that: (1) activates the real `deny-all` security policy via `acl.RegisterFlags`, (2) registers `/debug/vrlog` via the real, unmodified `vreplication.NewVrLogStats(...)` call path (the same one `vplayer.go` uses for live replication traffic), (3) serves the real `servenv` HTTP mux on a loopback listener, and (4) issues a real `GET /debug/vrlog` while emitting a simulated replicated statement.

Result: ``` acl.CheckAccessHTTP(DEBUGGING) under deny-all => err=not allowed: deny-all security-policy enforced GET /debug/vrlog under deny-all => status=200 body="ROWCHANGE Event\tINSERT INTO secret_table (ssn) VALUES ('leaked-via-vrlog')\t2026-06-22T11:01:55\t147101\n" ``` The identical ACL check that protects every sibling endpoint correctly rejected the request, while `/debug/vrlog` returned 200 and streamed the simulated sensitive content — confirming the bypass against real, unmodified v24.0.1 code.

Also confirmed via the GitHub Contents API that `vrlog.go` has zero references to the `acl` package on `main`, `release-23.0`, and `release-22.0` as well, so all currently supported release lines appear affected.

Are you affected?

Enter the version of the package you're using.

Affected packages

Go / vitess.io/vitess
Introduced in: 0.24.0-rc1

No fixed version published yet for vitess.io/vitess (go modules). Pin to a known-safe version or switch to an alternative.

Go / vitess.io/vitess
Introduced in: 0

No fixed version published yet for vitess.io/vitess (go modules). Pin to a known-safe version or switch to an alternative.

References