Vitess vttablet /twopczにおけるXSS脆弱性

送信者: xylove21
宛先: security@vitess.io
件名: [セキュリティレポート] Vitess vttablet /twopczにおけるXSS脆弱性
日付: 2026年6月24日 14:30:00 +0800

## 概要

`go/vt/vttablet/tabletserver/twopcz.go`の`twopczHandler`(185行目)において、`Action`と`dtid`というフォームの値が、`fmt.Fprintln(w, msg)`を通じてHTMLレスポンスボディに直接反映されています。**HTMLエスケープ処理は行われていません**。レスポンスは`text/html; charset=utf-8`として送信され、Vitessのデフォルトの`acl`ポリシーは**許可全て**であるため、vttabletのHTTPポート(デフォルトは`15999`、gRPCおよびMySQLのプロトコルポートとマルチプレキシングされています)にアクセスできるユーザーであれば誰でもこのエンドポイントにアクセスできます。

巧妙に作成された`Action`または`dtid`の値を使用することで、**リフレクト型XSS**が発生し、オペレーターのブラウザ上で実行されます。これにより、vttabletのデバッグエンドポイントへの同一オリジンアクセスが可能になります。これには、`/queryz`(実行中のクエリプラン)、`/querylogz`(バインド値を含む最近のクエリ)、`/txlogz`(トランザクション)、`/debug/env`(環境変数 — MySQL/トポロジーの認証情報が含まれる可能性がある)、`/debug/vars`(expvarの統計情報)、および有効になっている場合は`/debug/pprof/*`が含まれます。

この脆弱性はv20.0.0以前から存在していました(長期間にわたる問題)。コミット`3140c1846c`(2026年6月9日にマージ、PR #20255、「tabletserver: twopczハンドラで反映されるフォーム値をエスケープする」)によって、問題は修正されました。修正は、`fmt.Fprintln(w, msg)`を`fmt.Fprintln(w, html.EscapeString(msg))`に置き換えるものです。**リリースされているVitessのバージョンには、この修正が含まれていません**。修正は`main`ブランチ(現在の最新バージョンは`33a3c0fafe69`、2026年6月23日)でのみ存在します。この特定の脆弱性については、**CVE、GHSA、またはOSVのエントリはありません**(2026年6月24日にGitHub Advisory Database、OSV.dev、およびNVDで確認済み)。

## 影響

| 項目 | 値 |
|---|---|
| **種類** | リフレクト型クロスサイトスクリプティング (XSS) |
| **CVSS 3.1** | `AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N` = **7.7 (高)** |
| **攻撃経路** | ネットワーク (vttabletのHTTPポート15999、gRPC/MySQLとマルチプレキシング) |
| **攻撃の複雑さ** | 低 (巧妙なURLを作成し、オペレーターに送信) |
| **必要な権限** | なし (デフォルトの`acl`ポリシーは全て許可) |
| **ユーザーインタラクション** | 必要 (オペレーターが作成されたURLを訪問する必要がある) |
| **影響範囲** | 変更あり (XSSが他のvttabletのデバッグエンドポイントに連鎖的に影響を及ぼす可能性がある) |
| **機密性** | 高 (クエリテキスト、環境変数、expvarの統計情報) |
| **完全性** | なし (XSS自体は状態を変更しない) |
| **可用性** | なし (XSS自体はサービスを中断しない) |

### 影響を受けるバージョン (検証済み)

すべてのリリース版に脆弱性があります。修正は`main`ブランチでのみ利用可能です。

| タグ | リリース日 | `twopcz.go`の185行目 | 状態 |
|---|---|---|---|
| v20.0.0 | 2024-06-27 | `fmt.Fprintln(w, msg)` | **脆弱性あり** |
| v20.0.1 - v20.0.6 | 2024 | `fmt.Fprintln(w, msg)` | **脆弱性あり** |
| v22.0.4 | 2026-02-27 | `fmt.Fprintln(w, msg)` | **脆弱性あり** |
| v23.0.3, v23.0.4 | 2026-02 / 2026-05 | `fmt.Fprintln(w, msg)` | **脆弱性あり** |
| v24.0.0, v24.0.1 | 2026-04 / 2026-05 | `fmt.Fprintln(w, msg)` | **脆弱性あり** |
| **main** (`33a3c0fafe69`) | 2026-06-23 | `fmt.Fprintln(w, html.EscapeString(msg))` | **修正済み** |

### 同じオリジンからのXSS連鎖によって暴露される機密データ

XSSが成功すると、vttabletのオリジンに侵入します。そこから、攻撃者は
注入されたJavaScriptから`fetch()`を使用して、以下の同じオリジンのエンドポイントにアクセスできます。
これらのエンドポイントは、デフォルトでは認証されていません。

- `/twopcz` — 実行中の2相コミットトランザクション (クエリテキスト、パラメータ、dtid)
- `/queryz` — 実行中のクエリプラン
- `/querylogz` — 最近のクエリとそのバインド値
- `/txlogz` — トランザクション
- `/debug/env` — 環境変数 (MySQLの認証情報、トポロジークレデンシャル、暗号化キーが含まれる可能性がある)
- `/debug/vars` — expvarの統計情報
- `/debug/pprof/*` — CPU/メモリプロファイル (有効になっている場合)

## 再現 (独立して実行可能なPoC)

このPoCは、Vitess v20.0.0の脆弱な`twopczHandler`を完全に再現する、スタンドアロンのGo HTTPサーバーです(同じ185行目のコードを使用)。 これは`127.0.0.1:15080`で実行され、実際のVitessのエンドポイントと同じレスポンスを返します。

```bash
# 1) PoCのビルド (Go 1.23以降)
cd /path/to/finding/CVE-pending-twopcz-xss
go build -o /tmp/vitess-twopcz-poc ./poc.go

# 2) 脆弱なサーバーの起動
ADDR=127.0.0.1:15080 /tmp/vitess-twopcz-poc &

# 3) 再現 — テスト1: 単純なGETリクエスト (パラメータなし)
curl -sS -i "http://127.0.0.1:15080/twopcz"
# → Content-Type: text/html; charset=utf-8, status 200

# 4) テスト2: Actionパラメータ経由のXSS (scriptタグをURLエンコード)
curl -sS -i "http://127.0.0.1:15080/twopcz?Action=%3Cscript%3Ealert(1)%3C%2Fscript%3E&dtid=zz"
# → レスポンスボディには "literal <script>alert(1)</script>(zz): completed." が含まれる
# → ブラウザは、このスクリプトを同一オリジンのコードとして実行する

# 5) テスト3: dtidパラメータ経由のXSS (属性コンテキストペイロード)
curl -sS -i "http://127.0.0.1:15080/twopcz?Action=Demo&dtid=%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E"
# → レスポンスボディには "literal <img src=x onerror=alert(1)>" が含まれる
# → ブラウザは、onerrorハンドラを同一オリジンのコードとして実行する

# 6) 自動テストスクリプトの実行 (evidence/run-{1,2,3}.logを作成)
bash ./poc/run.sh
```

PoCの結果の抜粋 ( `evidence/run-1.log` から):

```
--- テスト2: Action=<script>alert(1)</script> 経由のXSS ---
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: ペイロードが未エスケープで反映され、ブラウザでXSS攻撃が可能
```

このPoCは、脆弱なサーバー上で3回実行され、すべて成功しました (3/3 PASS)。 また、このコードは、v20.0.0のコミット `8aeb274f6c8e` の185行目と完全に一致しています。

## 根本原因 (技術的な詳細)

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

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

`msg`は、ハンドラの以前の部分で以下のように構築されます。

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

ここで、`action` と `dtid` は、フォームの値 `r.FormValue("Action")` と `r.FormValue("dtid")` から取得されます。フォームの値は URL デコードされています(例えば、`+` はスペースに、`%3C` は `<` に変換されますが)、HTML エスケープは行われません。ハンドラは明示的に `Content-Type: text/html; charset=utf-8` を設定します(`<!DOCTYPE html>` で最初に記述されるため)、そのため、挿入された `<script>` タグや属性コンテキストのペイロードがブラウザによって実行されます。

`main` 関数における修正 (`fmt.Fprintln(w, html.EscapeString(msg))`) は、リフレクションを行う前に、`action` と `dtid` の両方を HTML エスケープすることで、この脆弱性を解消します。

## 推奨される修正(すでに main 関数に実装済み)

```go
// 推奨される最小限の修正(すでに main 関数に実装されており、すべてのリリースブランチに適用する必要があります):
import "html"

func twopczHandler(w http.ResponseWriter, r *http.Request) {
// ... 既存のロジック ...
msg := fmt.Sprintf("%s(%s): completed.", action, dtid)
fmt.Fprintln(w, html.EscapeString(msg)) // ← 変更前: fmt.Fprintln(w, msg)
}
```

この修正を v20.0.x、v22.0.x、v23.0.x、および v24.0.x のバージョンに、パッチリリースとして適用します。この修正は 1 行の変更と、1 行の `import "html"` の追加のみであり、そのため、適用に伴うリスクは最小限です。

## ワークアラウンド / 軽減策(パッチは利用不可)

固定されたバージョンにすぐにアップグレードできない場合は、以下の方法を試してください。

1. **vttablet の HTTP ポート(デフォルトは 15999)をローカルホストのみ** に制限します。vttablet は、`--tablet-allow-stats=false` と `--tablet-grpc-port` を設定することで、HTTP ポートとは別に gRPC ポートを設定できます。または、ネットワーク層で HTTP ポートをファイアウォールで保護することもできます。デバッグエンドポイントは、本番環境のトラフィックでは必要ありません。

2. **デフォルト以外の ACL ポリシーを設定する**。Vitess は、デフォルトで「すべて許可」の `acl` ポリシーをサポートしています。JSON ファイルを使用して、認証されていないアクセスをデバッグエンドポイントで拒否するように設定することで、XSS 攻撃をブロックできます(`go/acl/acl.go::savePolicy` を参照)。ただし、デフォルトのポリシーは現在 `""`(すべて許可)であり、多くのユーザーがこれをオーバーライドしていません。

3. **リバースプロキシ(nginx、envoyなど)をvttabletのHTTPポートの前に配置し、許可リストでクライアントを厳格に制限する。**

## 関連情報

- このバグは、以下のものとは関連がありません。
- CVE-2024-53257(`/debug/querylogz`におけるHTMLインジェクション。v20.0.4で修正済み — 異なるエンドポイント)
- CVE-2026-27969(バックアップのMANIFESTパスのトラバーサル)
- CVE-2026-27965 / GHSA-8g8j-r87h-p36x(バックアップの外部デコンプレッサーにおけるリモートコード実行)
- PR #20255が修正を含むコミットです。PR #19486(多層防御)は、別のセキュリティ強化の変更です。

## 連絡先

- GitHub: @xylove21

影響を受けるパッケージ:
- go vitess.io/vitess: 影響を受けるバージョンは0.23.5未満、修正されたバージョンは0.23.5
- go vitess.io/vitess: 影響を受けるバージョンは0.24.0以上、0.24.2未満、修正されたバージョンは0.24.2