
# CVE-2026-46645 再現用Dockerラボ SQLAdminの`ajax_lookup`エンドポイントにおける認可バイパスであるCVE-2026-46645を再現するためのDockerベースのラボです。脆弱性のあるターゲットとパッチ適用済みターゲット、PoCスクリプト、およびセキュリティ研究・教育のための手動curl再現手順が含まれています。
このリポジトリには、SQLAdmin の ajax_lookup エンドポイントに影響する認可バイパス脆弱性 CVE-2026-46645 を再現するためのローカル Docker ラボが含まれています。
SQLAdmin は、Starlette および FastAPI アプリケーションの SQLAlchemy モデル向けの管理インターフェースです。脆弱な動作は、アプリケーションが is_accessible(request) で ModelView を制限しているにもかかわらず、SQLAdmin の ajax_lookup ルートがルックアップ結果を返す前に同じアクセス制御判断を適用しない場合に発生します。
このラボは、2 つの SQLAdmin バージョンを比較します:
| Service | SQLAdmin version | Purpose | URL |
|---|---|---|---|
vuln | 0.25.0 | 脆弱なターゲット | http://127.0.0.1:8001 |
patched | 0.25.1 | パッチ適用済みの比較ターゲット | http://127.0.0.1:8002 |
実証される脆弱性チェーンは次のとおりです:```text Authenticated low-privileged user → restricted SQLAdmin ModelView → ModelView.is_accessible(request) returns False → user directly requests the ajax_lookup endpoint → SQLAdmin 0.25.0 returns relationship lookup data → SQLAdmin 0.25.1 blocks the same request with HTTP 403
このラボは、認可バイパスを理解しやすくするために、意図的に単純な `Report` / `SecretProject` データモデルを使用しています。これらのモデル名は脆弱性の根本原因ではありません。これらは、制御された再現条件を作成するためにのみ使用されます。
このラボは、制御されたローカル研究、ソースレベルでの理解、およびポートフォリオデモンストレーションのみを目的として設計されています。
## 検証済みの事実
| 主張 | 証拠 | このラボでの検証方法 |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| SQLAdmin の `ajax_lookup` エンドポイントが影響を受けるコンポーネントです。 | 公開アドバイザリは、影響を受けるエンドポイント形式を `GET /{identity}/ajax/lookup?name=<field>&term=<query>` として説明しています。 | PoC を実行し、`/admin/report/ajax/lookup?name=project&term=Secret` へのリクエストを観察します。 |
| SQLAdmin `0.25.0` が脆弱な比較対象として使用されます。 | このラボは `vuln` コンテナに `sqladmin==0.25.0` をインストールします。 | `docker compose exec -T vuln python -m pip show sqladmin` を実行します。 |
| SQLAdmin `0.25.1` がパッチ適用済みの比較対象として使用されます。 | 公開アドバイザリとリリースノートは、`0.25.1` を修正版として特定しています。 | `docker compose exec -T patched python -m pip show sqladmin` を実行します。 |
| 根本原因は SQLAdmin のアップストリームの `Admin.ajax_lookup()` ルートにあります。 | パッチは、`ajax_lookup()` に欠落していた認証と `is_accessible(request)` の強制を追加します。 | この README のコマンドを使用して、両方のコンテナ内の `Admin.ajax_lookup()` を検査します。 |
| このラボは制限付きの `ModelView` を作成します。 | `ReportAdmin.is_accessible(request)` は意図的に `False` を返します。 | `app/main.py` を検査します。 |
| PoC は認証済みセッションを使用します。 | PoC は最初に `/admin/login` にログインし、セッションクッキーを保持してから `ajax_lookup` をリクエストします。 | `python3 poc/poc.py --base-url http://127.0.0.1:8001` を実行します。 |
| 脆弱なシグナルはデータ露出です。 | SQLAdmin `0.25.0` は、制限付きビューから HTTP 200 と JSON ルックアップ結果を返します。 | 脆弱なターゲットは `Secret Project Alpha` と `Secret Project Beta` を返すはずです。 |
| パッチ適用後のシグナルはアクセス拒否です。 | SQLAdmin `0.25.1` は、同じ認証済みリクエストに対して HTTP 403 を返します。 | パッチ適用後のターゲットは `403 Forbidden` を返すはずです。 |
## 前提条件と未知の事項
このラボは、脆弱なベースラインとして `sqladmin==0.25.0` を、パッチ適用済みベースラインとして `sqladmin==0.25.1` を使用します。
このラボは、以下の条件における認可バイパスに焦点を当てています。```text
A user is authenticated,
the target ModelView is not accessible,
but ajax_lookup is requested directly.
このラボは、SQLAdminの考えられるすべてのデプロイパターンを再現しようとはしていません。意図的に、制限付きの管理ビューを1つ持つ小さなStarletteアプリケーションを作成して、脆弱なバージョンと修正済みバージョンの動作の違いを簡単に検証できるようにしています。
Report および SecretProject モデルはラボ専用のオブジェクトです。これらはSQLAdmin自体の一部ではありません。
PoCは、権限昇格、データ改ざん、セッション窃取、外部コールバック、永続化、またはラボ外のシステムへの攻撃を試みるものではありません。
根本原因は、このラボのアプリケーションコードではなく、SQLAdminの上流の Admin.ajax_lookup() ルートにあります。
SQLAdminでは、開発者は以下をオーバーライドすることで管理ビューへのアクセスを制限できます。```python ModelView.is_accessible(request)
他の管理ルートは、リクエストの処理を続行する前に、このアクセス制御判断を実施することが期待されています。たとえば、list、create、details、delete、edit、export などのルートは、現在のリクエストがターゲットの `ModelView` にアクセスできるかどうかを確認します。
脆弱な `ajax_lookup` ルートは、同じアクセス制御判断を実施していませんでした。
`ajax_lookup` エンドポイントは、SQLAdmin の `form_ajax_refs` 機能によって、関連する値を動的に読み込むために使用されます。そのエンドポイント形式は次のとおりです:```text
/admin/<identity>/ajax/lookup?name=<field>&term=<query>
脆弱なバージョンでは、ajax_lookup() は対象の ModelView を解決し、クエリ文字列からルックアップフィールド名と検索語を読み取り、AJAXローダーを呼び出してJSON結果を返します。欠落しているセキュリティステップは、現在のリクエストがその ModelView にアクセスすることを許可されているかどうかを最初に検証しないことです。
セキュリティへの影響として、認証されたユーザーが通常のUIルートを通じて制限された管理ビューにアクセスすることをブロックされていても、そのビューのAJAXルックアップエンドポイントを直接リクエストして、リレーションシップのルックアップデータを受け取ることができる可能性があります。
SQLAdminの 0.25.1 は、ajax_lookup() 内でアクセス制御を強制することでこれを修正します。パッチ適用済みのルートは model_view.is_accessible(request) をチェックし、対象のビューにアクセスできない場合はHTTP 403を返します。
このラボでは、脆弱な状態を再現するためだけに、ReportAdmin.is_accessible(request) が False を返すように定義しています。ラボのコードは根本原因ではありません。これは、SQLAdminのアップストリームの ajax_lookup() ルートがアクセス制御の決定を尊重するかどうかを検証する、管理されたテストハーネスです。
期待される動作の違い:```text sqladmin 0.25.0 -> HTTP 200 with JSON lookup results sqladmin 0.25.1 -> HTTP 403 Forbidden
## ソースパッチの概要
意味のあるアップストリームパッチは、`Admin.ajax_lookup()` への認証と認可の強制の追加です。
パッチ適用後の動作は次と同等です:```python
@login_required
async def ajax_lookup(self, request):
identity = request.path_params["identity"]
model_view = self._find_model_view(identity)
if not model_view.is_accessible(request):
raise HTTPException(status_code=403)
name = request.query_params.get("name")
term = request.query_params.get("term")
...
主要な認可チェックは次のとおりです:```python if not model_view.is_accessible(request): raise HTTPException(status_code=403)
The lab demonstrates that this check is absent from the vulnerable version and present in the patched version.
## Lab Architecture
The lab runs two isolated Starlette applications through Docker Compose.```text
.
├── app/
│ ├── __init__.py
│ └── main.py
├── docker-compose.yml
├── patched/
│ └── Dockerfile
├── poc/
│ └── poc.py
├── README.md
├── requirements/
│ ├── patched.txt
│ └── vuln.txt
└── vuln/
└── Dockerfile
2つのサービスは同じアプリケーションコードを実行しますが、インストールするSQLAdminのバージョンが異なります:
| Service | パッケージバージョン | ポートマッピング |
|---|---|---|
vuln | sqladmin==0.25.0 | 127.0.0.1:8001 -> 8000 |
patched | sqladmin==0.25.1 | 127.0.0.1:8002 -> 8000 |
アプリケーションは2つのSQLAlchemyモデルを作成します:```text SecretProject Report
`Report` は `SecretProject` との関係を持ちます:```text
Report.project -> SecretProject
ReportAdmin は AJAX リレーションシップルックアップを定義します:```python
form_ajax_refs = {
"project": {
"fields": ("name",),
"order_by": "name",
"limit": 10,
}
}
制限付き管理者ビューは次のとおりです:```python
class ReportAdmin(ModelView, model=Report):
def is_accessible(self, request):
return False
これは、SQLAdmin の ajax_lookup() ルートが is_accessible() を強制するかどうかをテストするために必要な条件を意図的に作り出します。
PoC で使用される脆弱なエンドポイントは次のとおりです:```text /admin/report/ajax/lookup?name=project&term=Secret
デフォルトのラボ資格情報:```text
username: analyst
password: lab-password
requests パッケージcurl必要に応じて、ホストにPoCの依存関係をインストールします:```bash python3 -m pip install requests
## クイックスタート