Kimai's API invoice endpoint missing customer-level access control (IDOR)
Date: 2026-03
Affected: composer/kimai/kimai
Attack vector: auth-bypass
Category: vulnerability-disclosure
Severity: Medium
CVE: CVE-2026-28685
CWE: CWE-285
CWE-862
CVSS: 6.5 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N)

## Summary `GET /api/invoices/{id}` only checks the role-based `view_invoice` permission but does not verify the requesting user has `access` to the invoice's customer. Any user with `ROLE_TEAMLEAD` (which grants `view_invoice`) can read all invoices in the system, including those belonging to customers assigned to other teams. ## Affected Code `src/API/InvoiceController.php` line 92-101: ```php #[IsGranted('view_invoice')] // Role check only, no customer access check #[Route(methods: ['GET'], path: '/{id}', name: 'get_invoice', requirements: ['id' => '\d+'])] public function getAction(Invoice $invoice): Response { $view = new View($invoice, 200); $view->getContext()->setGroups(self::GROUPS_ENTITY); return $this->viewHandler->handle($view); // Returns ANY invoice by ID } ``` The web controller (`src/Controller/InvoiceController.php` line 304-307) correctly checks customer access: ```php #[IsGranted('view_invoice')] #[IsGranted(new Expression("is_granted('access', subject.getCustomer())"), 'invoice')] public function downloadAction(Invoice $invoice, ...): Response { ... } ``` The `access` attribute in `CustomerVoter` (line 71-87) verifies team membership, but this check is entirely missing from the API endpoint. ## PoC Tested against Kimai v2.50.0 (Docker: `kimai/kimai2:apache`). Setup: - TeamA with CustomerA ("SecretCorp"), TeamB with CustomerB ("BobCorp") - Bob is a teamlead in TeamB only - An invoice exists for SecretCorp (TeamA) ```bash #


Mitigations:

Architectural layers (MAESTRO):