---
id: "CVE-2026-104252"
page: "https://infrastack.ai/live/CVE-2026-104252"
kind: "vulnerability"
aliases: ["CVE-2026-104252", "GHSA-v75v-6fpf-gvw8", "GO-2026-6798"]
title: "Vitess vttablet /twopcz reflected XSS"
severity: "critical"
cvss: 9.8
epss: 0.01174
exploited: false
status: "open"
category: "package"
products: [{"name": "vitess.io/vitess", "fixed": ["0.23.5", "0.24.2"], "affected": ">= 0.24.0, < 0.24.2", "ecosystem": "go"}, {"name": "linuxfoundation vitess", "fixed": ["23.0.5"], "affected": "< 23.0.5", "ecosystem": "vendor"}, {"name": "linuxfoundation vitess", "fixed": ["24.0.2"], "affected": ">= 24.0.0, < 24.0.2", "ecosystem": "vendor"}, {"name": "vitess.io/vitess", "fixed": ["0.23.5"], "affected": "< 0.23.5", "ecosystem": "go"}]
ecosystems: ["go", "vendor"]
tags: ["cwe-116", "cwe-79", "cwe-80", "go", "kubernetes", "mysql", "package", "vitess", "vttablet"]
not_affected_if: ["pre-0.24-default-config", "vttablet-not-exposed"]
published_at: "2026-10-13T20:17:12+00:00"
first_seen_at: "2026-11-06T00:09:05+00:00"
updated_at: "2026-11-06T00:19:08+00:00"
sources: [{"source": "govuln", "url": "https://pkg.go.dev/vuln/GO-2026-6798", "first_seen_at": "2026-11-06T00:09:05+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-104252", "first_seen_at": "2026-11-06T00:19:01+00:00"}, {"source": "ghsa", "url": "https://github.com/advisories/GHSA-v75v-6fpf-gvw8", "first_seen_at": "2026-11-06T00:19:08+00:00"}]
exploit_sources: [{"type": "epss", "score": 0.01174, "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-104252"}]
---

# Vitess vttablet /twopcz reflected XSS

Link for people: https://infrastack.ai/live/CVE-2026-104252 (this .md is for agents)

The vttablet /twopcz web page in Vitess reflects user-supplied input back into its HTML without proper escaping. An attacker who gets a user to open a crafted link can run scripts in that user's browser session on the Vitess admin page.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- go:vitess.io/vitess affected >= 0.24.0, < 0.24.2 fixed 0.23.5, 0.24.2
- vendor:linuxfoundation vitess affected < 23.0.5 fixed 23.0.5
- vendor:linuxfoundation vitess affected >= 24.0.0, < 24.0.2 fixed 24.0.2
- go:vitess.io/vitess affected < 0.23.5 fixed 0.23.5

## How to check

- Check the Go module version of `vitess.io/vitess` in `go.mod` or with `go list -m vitess.io/vitess`
- Confirm whether vttablet is reachable with its HTTP status page enabled, and whether `/twopcz` is served

## Fix

- `vitess.io/vitess` `0.23.5` for the 0.23 line
- `vitess.io/vitess` `0.24.2` for the 0.24 line

## Timeline

- 2026-11-06T00:09:05+00:00 govuln https://pkg.go.dev/vuln/GO-2026-6798
- 2026-11-06T00:19:01+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-104252
- 2026-11-06T00:19:08+00:00 ghsa https://github.com/advisories/GHSA-v75v-6fpf-gvw8

## Documents

- [advisory] Vendor advisory: github.com https://github.com/vitessio/vitess/security/advisories/GHSA-v75v-6fpf-gvw8
- [advisory] Maintainer's advisory: api.github.com https://api.github.com/repos/vitessio/vitess/security-advisories/GHSA-v75v-6fpf-gvw8
- [fix] Patch: github.com https://github.com/vitessio/vitess/commit/c1ed96302e658190168c971afdac730cf70e43f7
- [fix] Patch: github.com https://github.com/vitessio/vitess/pull/20255
- [fix] Patch: github.com https://github.com/vitessio/vitess/pull/20273
- [fix] Patch: github.com https://github.com/vitessio/vitess/commit/3140c1846cd120db34f33b2083139b4ab638aed2
- [fix] Release notes: github.com https://github.com/vitessio/vitess/releases/tag/v23.0.5
- [fix] Release notes: github.com https://github.com/vitessio/vitess/releases/tag/v24.0.2
- [fix] Patch: github.com https://github.com/vitessio/vitess/pull/20274
- [fix] Patch: github.com https://github.com/vitessio/vitess/commit/976e1e0d78f9d29d02e5e31b4aa7e180648f08af

## From the sources

From: xylove21
To: security@vitess.io
Subject: [Security Report] Vitess vttablet /twopcz reflected XSS
Date: Tue, 24 Jun 2026 14:30:00 +0800

## Summary

The `twopczHandler` in
`go/vt/vttablet/tabletserver/twopcz.go` (line 185) reflects the `Action`
and `dtid` form values directly into the HTML response body via
`fmt.Fprintln(w, msg)` — **without HTML escaping**. The response is served
as `text/html; charset=utf-8`, and Vitess's default `acl` policy is
**allow-all**, so the endpoint is reachable by anyone who can hit the
vttablet's HTTP port (default `15999`, multiplexed with gRPC and MySQL
wire-protocol ports).

A crafted `Action` or `dtid` value yields a **reflected XSS** that
executes in the operator's browser with same-origin access to the
vttablet's debug endpoints. This includes `/queryz` (live query plans),
`/querylogz` (recent queries with bound values), `/txlogz` (transactions),
`/debug/env` (environment variables — may include MySQL/topology
credentials), `/debug/vars` (expvar stats), and `/debug/pprof/*` if
enabled.

The bug was introduced before v20.0.0 (long-standing) and was silently
fixed in commit `3140c1846c` (merged 2026-06-09, PR #20255,
"tabletserver: escape reflected form values in twopcz handler"). The
fix replaces `fmt.Fprintln(w, msg)` with
`fmt.Fprintln(w, html.EscapeString(msg))`. **No released version of
Vitess contains the fix** — the fix lives only on `main` (current tip
`33a3c0fafe69`, 2026-06-23). There is **no CVE, GHSA, or OSV entry**
for this specific bug (verified 2026-06-24 against GitHub Advisory
Database, OSV.dev, and NVD).

## Impact

| Field | Value |
|-------|-------|
| **Type** | Reflected Cross-Site Scripting (XSS) |
| **CVSS 3.1** | `AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N` = **7.7 HIGH** |
| **Attack Vector** | Network (vttablet HTTP port 15999, multiplexed with gRPC/MySQL) |
| **Attack Complexity** | Low (craft URL, send to operator) |
| **Privileges Required** | None (default `acl` policy is allow-all) |
| **User Interaction** | Required (operator must visit crafted URL) |
| **Scope** | Changed (XSS chain reaction to other vttablet debug endpoints) |
| **Confidentiality** | High (query text, env vars, expvar stats) |
| **Integrity** | None (XSS itself doesn't change state) |
| **Availability** | None (XSS itself doesn't disrupt service) |

### Affected versions (verified)

All released versions are vulnerable. The fix lives only on `main`:

| Tag | Released | `twopcz.go` line 185 | Status |
|---|---|---|---|
| v20.0.0 | 2024-06-27 | `fmt.Fprintln(w, msg)` | **BUGGY** |
| v20.0.1 - v20.0.6 | 2024 | `fmt.Fprintln(w, msg)` | **BUGGY** |
| v22.0.4 | 2026-02-27 | `fmt.Fprintln(w, msg)` | **BUGGY** |
| v23.0.3, v23.0.4 | 2026-02 / 2026-05 | `fmt.Fprintln(w, msg)` | **BUGGY** |
| v24.0.0, v24.0.1 | 2026-04 / 2026-05 | `fmt.Fprintln(w, msg)` | **BUGGY** |
| **main** (`33a3c0fafe69`) | 2026-06-23 | `fmt.Fprintln(w, html.EscapeString(msg))` | **FIXED** |

### Sensitive data exposed via the same-origin XSS chain

A successful XSS lands in the vttablet's origin. From there, the
attacker can fetch (via `fetch()` from injected JavaScript) the
following same-origin endpoints, all of which are unauthenticated by
default:

- `/twopcz` — in-flight 2PC transactions (query text, parameters, dtid)
- `/queryz` — live query plans
- `/querylogz` — recent queries with bound values
- `/txlogz` — transactions
- `/debug/env` — environment variables (may include MySQL creds, topology
  credentials, encryption keys)
- `/debug/vars` — expvar stats
- `/debug/pprof/*` — CPU/memory profiles (if enabled)

## Reproduction (independent, runnable PoC)

The PoC is a self-contained Go HTTP server that replicates the buggy
`twopczHandler` from Vitess v20.0.0 verbatim (using the same line-185
code). It runs on `127.0.0.1:15080` and returns the same response shape
as the real Vitess endpoint.

```bash
# 1) Build the PoC (Go 1.23+)
cd /path/to/finding/CVE-pending-twopcz-xss
go build -o /tmp/vitess-twopcz-poc ./poc.go

# 2) Start the buggy server
ADDR=127.0.0.1:15080 /tmp/vitess-twopcz-poc &

# 3) Reproduce — Test 1: bare GET (no params)
curl -sS -i "http://127.0.0.1:15080/twopcz"
# → Content-Type: text/html; charset=utf-8, status 200

# 4) Test 2: XSS via Action param (URL-encode the script tag)
curl -sS -i "http://127.0.0.1:15080/twopcz?Action=%3Cscript%3Ealert(1)%3C%2Fscript%3E&dtid=zz"
# → Response body contains literal "<script>alert(1)</script>(zz): completed."
# → Browser executes the script as same-origin code

# 5) Test 3: XSS via dtid param (attribute-context payload)
curl -sS -i "http://127.0.0.1:15080/twopcz?Action=Demo&dtid=%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E"
# → Response body contains literal "<img src=x onerror=alert(1)>"
# → Browser fires the onerror handler as same-origin code

# 6) Run the 3x automated runner (writes evidence/run-{1,2,3}.log)
bash ./poc/run.sh
```

PoC result excerpt (from `evidence/run-1.log`):

```
--- Test 2: XSS via Action=<script>alert(1)</script> ---
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...
<h2>WARNING: Actions on this page can jeopardize data integrity.</h2>
<script>alert(1)</script>(zz): completed.

PASS: payload reflected UNESCAPED (XSS exploitable in browser)
```

The PoC was run 3 times on the buggy server (all 3/3 PASS) and the same
code is byte-identical to the v20.0.0 commit `8aeb274f6c8e` line 185.

## Root cause (technical detail)

`go/vt/vttablet/tabletserver/twopcz.go` (v20.0.0 through v24.0.1):

```go
// Line 185
fmt.Fprintln(w, msg)
```

`msg` is constructed earlier in the handler as:

```go
msg := fmt.Sprintf("%s(%s): completed.", action, dtid)
```

with `action` and `dtid` coming from the form values
`r.FormValue("Action")` and `r.FormValue("dtid")`. The form values are
URL-decoded (so a `+` becomes a space, `%3C` becomes `<`) but never
HTML-escaped. The handler explicitly sets `Content-Type: text/html;
charset=utf-8` (via the `<!DOCTYPE html>` it writes first), so the
browser executes any injected `<script>` or attribute-context payload.

The fix in main (`fmt.Fprintln(w, html.EscapeString(msg))`) closes the
hole by HTML-escaping both `action` and `dtid` before reflection.

## Suggested fix (already in main)

```go
// Recommended minimal patch (already in main, needs backport to all release branches):
import "html"

func twopczHandler(w http.ResponseWriter, r *http.Request) {
    // ... existing logic ...
    msg := fmt.Sprintf("%s(%s): completed.", action, dtid)
    fmt.Fprintln(w, html.EscapeString(msg))  // ← was: fmt.Fprintln(w, msg)
}
```

Backport the fix to v20.0.x, v22.0.x, v23.0.x, and v24.0.x as patch
releases. The fix is one line and a one-line `import "html"` addition,
so the backport risk is minimal.

## Workaround / mitigation (no patch available)

If you cannot upgrade to a fixed release immediately:

1. **Restrict the vttablet HTTP port** (default 15999) to localhost
   only. vttablet can run with `--tablet-allow-stats=false` and
   `--tablet-grpc-port` configured separately from the HTTP port, or
   you can firewall the HTTP port at the network layer. The debug
   endpoints are not needed by production traffic.

2. **Set a non-default ACL policy**. Vitess supports an `acl` policy
   that defaults to allow-all. Setting it to a JSON file that denies
   unauthenticated access to debug endpoints (see
   `go/acl/acl.go::savePolicy`) would block the XSS, but the default
   policy is currently `""` (allow-all) and many users don't override
   it.

3. **Use a reverse proxy** (nginx, envoy) in front of the vttablet
   HTTP port with a strict allowlist of clients.

## Cross-references

  - The bug is **not** related to:
    - CVE-2024-53257 (`/debug/querylogz` HTML injection, fixed in
      v20.0.4 — different endpoint)
    - CVE-2026-27969 (backup MANIFEST path traversal)
    - CVE-2026-27965 / GHSA-8g8j-r87h-p36x (backup external decompressor
      RCE)
  - PR #20255 is the fix commit; PR #19486 (defense-in-depth) is a
    separate hardening change

## Contact


---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
