# nginx-ui 脆弱性検証 学習プラン **対象CVE**: CVE-2026-42221(認証バイパス)/ CVE-2026-42238(未認証リストア) **対象バージョン**: nginx-ui v2.3.7(脆弱)→ v2.3.8(修正済み)で比較 **目的**: Webアプリケーションの認証欠如系脆弱性の仕組みを理解し、実機検証を通じてセキュリティの知識を深める --- ## 使用ツール一覧 | ツール | 用途 | |--------|------| | Docker Desktop | コンテナ実行環境 | | Docker Compose | コンテナ構成管理 | | Python 3 + pycryptodome | 攻撃スクリプト(`scripts/` ディレクトリで venv 管理) | | ブラウザ(Chrome/Firefox) | nginx-ui WebGUI 操作・開発者ツール | --- ## フェーズ 1: nginx-ui の理解 ### アーキテクチャ ``` ブラウザ(Vue.js フロントエンド) ↓ HTTP API (/api/*) nginx-ui バックエンド(Go)port 9000 ↓ ファイル直接読み書き / シェルコマンド実行 Nginx プロセス(port 80/443)/ 設定ファイル / SQLite DB / app.ini ``` - nginx-ui は Nginx とは**独立した別プロセス**で port 9000 で動作する - GUI 操作はすべて裏で `/api/*` を叩いている - Nginx 設定ファイルとシェルコマンド実行を直接制御する - `app.ini` の `TestConfigCmd` が RCE の核心(デフォルト: `nginx -t`) --- ## フェーズ 2: ラボ環境の準備 ### ファイル構成 ``` nginx_lab/ ├── docker-compose.yml ├── Dockerfile ├── nginx.conf # sites-enabled / stream ブロック修正済み ├── init-app.ini # app.ini 初期テンプレート └── entrypoint.sh # 初回起動時に app.ini を初期化するスクリプト ``` > **なぜ Dockerfile を使うか**: > 素の `uozi/nginx-ui:v2.3.7` は以下の問題があり警告が出る。 > - `sites-enabled` / `stream-enabled` / `streams-enabled` ディレクトリが存在しない > - `app.ini` に Nginx パスが未設定 > > これらを Dockerfile と entrypoint.sh で自動解決しているため `docker compose up` 一発でセットアップ画面まで到達できる。 ### 起動 ```powershell cd nginx_lab docker compose build docker compose up -d Invoke-RestMethod -Uri "http://localhost:8080/api/install" -Method Get # lock: False / timeout: False → 未セットアップ(正常) ``` ### 環境リセット(検証をやり直す場合) ```powershell docker compose down -v docker compose up -d ``` > **ポイント**: > - nginx-ui 本体は port 9000 で動作。port 80 にマップしても応答しないため `8080:9000` にマッピングする > - ボリューム `-v nginx-ui-data:/etc/nginx-ui` は必須。省略すると起動ループになる > - 起動から **10 分以内** にセットアップしないとタイムアウトする --- ## フェーズ 3: nginx-ui の正常な使い方を学ぶ ### 実施内容 - ブラウザで http://localhost:8080 にアクセスしセットアップ - バックアップを取得(zip 形式・トークン署名済み・通常ツールでは展開不可) - コンテナ内で `docker exec nginx-ui-vuln cat /etc/nginx-ui/app.ini` で直接確認 - 開発者ツール(F12)でAPI呼び出しを観察 ### app.ini で重要な設定 ```ini [nginx] TestConfigCmd = ← 空 = nginx -t が実行される ← ここを書き換えると任意コマンドが root で実行される(CVE-2026-42238 の核心) ``` --- ## フェーズ 4: 脆弱性の理論理解 ### 両 CVE に共通する条件(実機で確認) ``` 攻撃が成立する条件: lock=false かつ 起動から10分以内 lock=false → /api/install が開く(CVE-2026-42221) → /api/restore が開く(CVE-2026-42238) lock=true になった瞬間(ユーザー登録完了)→ 両方即座に閉じる 10分経過 → 両方閉じる ``` > **重要**: 管理者登録後に再起動しても `lock` は DB に永続化されているため再起動後も `lock=true` のまま。攻撃は「まだ誰も登録していないサーバー」にしか刺さらない。 ### CVE-2026-42221(認証バイパス)の攻撃フロー ``` 1. POST /api/crypto/public_key(fingerprint・timestamp が必要)→ RSA 公開鍵取得 2. username/password/email の JSON を丸ごと RSA 暗号化 3. POST /api/install に encrypted_params として送信 → 管理者として登録される ``` ### CVE-2026-42238(未認証リストア)の攻撃フロー ``` 1. lock=false の間に POST /api/restore で悪意ある app.ini を送り込む 2. CVE-2026-42221 で管理者アカウントを取得(lock=true になる) 3. 管理者として nginx-ui を再起動 → TestConfigCmd が root で実行 → RCE ``` ### 2つの CVE の関係 両方とも根本原因は同じ:「lock=false の10分間、セットアップ用エンドポイントに認証がない」 CVE-2026-42221 で管理者を取得した時点で認証済みの `/api/restore` が使えるため、CVE-2026-42238 の未認証窓を使う必要がなくなる。同一の根本原因から2つの攻撃面が派生しているため、別々の CVE として報告されている。 --- ## フェーズ 5: CVE-2026-42221 の実証 ### 攻撃スクリプト実行 ```powershell $scripts = "scripts" & "$scripts\venv\Scripts\python" "$scripts\exploit_42221.py" ``` ### 実行結果 ``` [Step1] lock=False / timeout=False [Step2] RSA 公開鍵取得: 成功 [Step3] ペイロードを RSA 暗号化: 成功 [Step4] /api/install に送信... ステータス: 200 / レスポンス: {'message': 'ok'} [★] 攻撃成功!管理者アカウントを乗っ取りました ``` ブラウザで `attacker` / `attacker_password` でログイン成功を確認。 ### 実機で判明した実装の詳細 - `/api/crypto/public_key` は **POST** メソッド(GET は 404) - `fingerprint`(任意文字列)と `timestamp`(Unix秒)が必須パラメータ - パスワードだけでなく **username/password/email の JSON 全体** を RSA 暗号化して `encrypted_params` として送る --- ## フェーズ 6: CVE-2026-42238 の検証 ### 実機で確認した動作 | 状態 | /api/restore の応答 | |---|---| | lock=false・10分以内 | 500(ファイルなしエラー)→ **開いている** | | lock=true(ユーザー登録済み)| 403 Authorization failed → **閉じている** | | 再起動直後・lock=true | 403 → **閉じている**(タイミングの問題ではない) | ### 結論 - CVE-2026-42238 は `lock=false` の窓でのみ有効 - `lock=true` になった瞬間(ユーザー登録後)即座に閉じる - CVE-2026-42221 の後に CVE-2026-42238 の未認証窓を使う必要はない --- ## フェーズ 8: 修正版との比較(パッチ分析) ### v2.3.8 の起動 ```powershell docker run -d --name nginx-ui-patched -p 127.0.0.1:8081:9000 -v nginx-ui-patched-data:/etc/nginx-ui uozi/nginx-ui:v2.3.8 ``` ### 攻撃結果の比較 | 攻撃 | v2.3.7 | v2.3.8 | |---|---|---| | `/api/install`(シークレットなし) | 200 OK(成功) | 404 Not Found | | `/api/setup/install`(シークレットなし) | — | 500 "Install secret is required" | | `/api/setup/install`(シークレットあり) | — | 400(次の処理へ進む) | | `/api/restore`(未認証) | 開いている | 403 Authorization failed | ### パッチの本質 **エンドポイントのパス変更** ``` v2.3.7: POST /api/install ← 誰でもアクセス可 v2.3.8: POST /api/setup/install ← SetupAuthRequired() ミドルウェアで保護 ``` **インストールシークレット方式の導入** ``` v2.3.8 起動時: /etc/nginx-ui/.install_secret を自動生成(ログには値を出力しない) セットアップには X-Install-Secret ヘッダーでこの値を送る必要がある ファイルを読めるのは SSH でサーバーに入れる正規の管理者だけ ``` **修正の本質** ``` v2.3.7: ネットワークに繋がれば誰でもセットアップできる v2.3.8: サーバーに SSH できる人(正規の管理者)しかセットアップできない ``` **ルーターレベルの変更(routers.go)** ```go // v2.3.8: SetupAuthRequired() 1つを /setup グループに追加するだけで両 CVE が同時に修正 setup := root.Group("/setup", middleware.SetupAuthRequired()) { system.InitSetupRouter(setup) // /api/setup/install backup.InitSetupRouter(setup) // /api/setup/restore } ``` ### 今回の学習で得た重要な気づき 1. **CVE の数 ≠ 問題の深刻さ**: 同じ根本原因から複数の CVE が出ることがある 2. **技術的な脆弱性と運用の問題は別**: セットアップ途中のサーバーを公開すること自体が重大なリスク 3. **修正の設計が重要**: v2.3.8 は「認証の有無」ではなく「誰が認証できるか」を根本から変えた 4. **実機検証で初めて分かること**: CVE の説明と実際の動作が異なる部分がある --- ## 後片付け ```powershell docker compose -f nginx_lab/docker-compose.yml down -v docker stop nginx-ui-patched; docker rm nginx-ui-patched docker volume rm nginx-ui-patched-data docker rmi nginx_lab-nginx-ui uozi/nginx-ui:v2.3.7 uozi/nginx-ui:v2.3.8 ``` --- ## 注意事項 - 本検証はすべて **自分が管理する隔離されたローカル環境** で実施すること - コンテナのポートは `127.0.0.1` にのみバインドし、LAN 上の他端末からアクセスできないようにすること - 許可のないシステムへ同様の手法を使用することは **不正アクセス禁止法に違反する**