GitHub Security Advisory: GHSA-rm98-82fr-mcfx
Package: thorsten/phpmyfaq (COMPOSER)
Severity: MODERATE (CVSS 4.3)
CWE: CWE-862: Missing Authorization
Vulnerable versions: <= 4.1.1
Patched in: 4.1.2

Summary: phpMyFAQ's Missing CONFIGURATION_EDIT Permission Check on 12 Admin API Configuration Tab Endpoints Allows Information Disclosure by Any Authenticated User

Details:
## Summary

12 endpoints in `ConfigurationTabController.php` use `userIsAuthenticated()` (login-only check) instead of `userHasPermission(PermissionType::CONFIGURATION_EDIT)`. This allows any authenticated user — including ones with zero admin permissions — to enumerate system configuration metadata including the permission model, active template, cache backend, mail provider, and translation provider.

## Details

The `ConfigurationTabController` contains 15 public endpoints. Three of them (`list`, `save`, `uploadTheme`) correctly enforce `CONFIGURATION_EDIT` permission:

```php
// phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/ConfigurationTabController.php:63
public function list(Request $request): Response
{
    $this->userHasPermission(PermissionType::CONFIGURATION_EDIT); // ✅ Correct
    // ...
}
```

The remaining 12 only check that the user is logged in:

```php
// phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/ConfigurationTabController.php:353
public function translations(): Response
{
    $this->userIsAuthenticated(); // ❌ Missing permission check
    // ...
}
```

The difference between these two methods is significant:

```php
// AbstractController.php:258 — login-only
protected function userIsAuthenticated(): void
{
    if (!$this->currentUser->isLoggedIn()) {
        throw new UnauthorizedHttpException(challenge: 'User is not authenticated.');
    }
}

// AbstractController.php:317 — login + permission check
protected function userHasPermission(PermissionType $permissionType): void
{
    if (!$this->currentUser->isLoggedIn()) {
        throw new UnauthorizedHttpException(challenge: 'User is not authenticated.');
    }
    $currentUser = $this->currentUser;
    if (!$currentUser?->perm->hasPermission($currentUser->getUserId(), $permissionType->value)) {
        throw new ForbiddenException(/* ... */);
    }
}
```

There is no middleware or router-level authorization — the Kernel (`Kernel.php`) dispatches directly to controllers with only Language, Router, and Exception listeners. All authorization is at the controller method level.

The 12 affected endpoints (all GET, all under `/admin/api/`):

| # | Method | Route | Info Exposed |
|---|--------|-------|-------------|
| 1 | `translations()` | `/configuration/translations` | Available languages + current language |
| 2 | `templates()` | `/configuration/templates` | Available themes + active theme |
| 3 | `faqsSortingKey()` | `/configuration/faqs-sorting-key/{current}` | FAQ sorting key options |
| 4 | `faqsSortingOrder()` | `/configuration/faqs-sorting-order/{current}` | FAQ sorting order |
| 5 | `faqsSortingPopular()` | `/configuration/faqs-sorting-popular/{current}` | Popular FAQ sorting |
| 6 | `permLevel()` | `/configuration/perm-level/{current}` | Permission model (basic/medium) |
| 7 | `releaseEnvironment()` | `/configuration/release-environment/{current}` | Dev/production environment |
| 8 | `searchRelevance()` | `/configuration/search-relevance/{current}` | Search r