{"id":"CVE-2026-28685","aliases":["EUVD-2026-9994","GHSA-v33r-r6h2-8wr7"],"status":"published","title":"Kimai: API invoice endpoint missing customer-level access control (IDOR)","description":"Kimai is a web-based multi-user time-tracking application. Prior to version 2.51.0, \"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. This issue has been patched in version 2.51.0.","severity":{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.0045,"percentile":0.39351,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-285","CWE-862"],"published":"2026-03-04T20:43:17Z","modified":"2026-10-28T02:30:47Z","fixes":[{"ecosystem":"Packagist","package":"kimai/kimai","fixed_in":["2.51.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"kimai/kimai","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"2.50.0"},{"fixed":"2.51.0"}]}]},{"source":"osv","ecosystem":"Packagist","package":"kimai/kimai","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.51.0"}]}],"versions":["0.1","0.2","0.3","0.4","0.5","0.6","0.6.1","0.7","0.8","0.8.1","0.9","1.0","1.0.1","1.1","1.10","1.10.1","1.10.2","1.11","1.11.1","1.12","1.13","1.14","1.14.1","1.14.2","1.14.3","1.15","1.15.1","1.15.2","1.15.3","1.15.4","1.15.5","1.15.6","1.16","1.16.1","1.16.10","1.16.2","1.16.3","1.16.4","1.16.5","1.16.6","1.16.7","1.16.8","1.16.9","1.17","1.17.1","1.18","1.18.1","1.18.2","1.19","1.19.1","1.19.2","1.19.3","1.19.4","1.19.5","1.19.6","1.19.7","1.2","1.20","1.20.1","1.20.2","1.20.3","1.20.4","1.21.0","1.22.0","1.22.1","1.23.0","1.23.1","1.24.0","1.25.0","1.26.0","1.27.0","1.28.0","1.28.1","1.29.0","1.29.1","1.3","1.30.0","1.30.1","1.30.10","1.30.11","1.30.2","1.30.3","1.30.4","1.30.5","1.30.6","1.30.7","1.30.8","1.30.9","1.4","1.4.1","1.4.2","1.5","1.6","1.6.1","1.6.2","1.7","1.8","1.9","2.0.0","2.0.0-alpha","2.0.0-beta","2.0.0-beta-2","2.0.0-beta-3","2.0.0-rc-1","2.0.1","2.0.10","2.0.11","2.0.12","2.0.13","2.0.14","2.0.15","2.0.16","2.0.17","2.0.18","2.0.19","2.0.2","2.0.20","2.0.21","2.0.22","2.0.23","2.0.24","2.0.25","2.0.26","2.0.27","2.0.28","2.0.29","2.0.3","2.0.30","2.0.31","2.0.32","2.0.33","2.0.34","2.0.35","2.0.4","2.0.5","2.0.6","2.0.7","2.0.8","2.0.9","2.1.0","2.10.0","2.11.0","2.12.0","2.13.0","2.14.0","2.15.0","2.16.0","2.16.1","2.17.0","2.18.0","2.19.0","2.19.1","2.2.0","2.2.1","2.20.0","2.20.1","2.21.0","2.22.0","2.23.0","2.24.0","2.25.0","2.26.0","2.27.0","2.28.0","2.29.0","2.3.0","2.30.0","2.31.0","2.32.0","2.33.0","2.34.0","2.35.0","2.35.1","2.36.0","2.36.1","2.37.0","2.38.0","2.39.0","2.4.0","2.4.1","2.40.0","2.41.0","2.42.0","2.43.0","2.44.0","2.45.0","2.46.0","2.47.0","2.48.0","2.49.0","2.5.0","2.50.0","2.6.0","2.7.0","2.8.0","2.9.0"]}],"products":[{"vendor":"kimai","product":"kimai"}],"references":[{"url":"https://github.com/kimai/kimai/security/advisories/GHSA-v33r-r6h2-8wr7","tags":["x_refsource_CONFIRM","Exploit","Mitigation","Vendor Advisory","advisory","web"]},{"url":"https://github.com/kimai/kimai/commit/a0601c8cb28fed1cca19051a8272425069ab758f","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/kimai/kimai/releases/tag/2.51.0","tags":["x_refsource_MISC","Product","Release Notes","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-28685","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-v33r-r6h2-8wr7"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/28xxx/CVE-2026-28685.json","tags":["advisory"]},{"url":"https://github.com/kimai/kimai","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-28685","title":"Kimai: API invoice endpoint missing customer-level access control (IDOR)","description":"Kimai is a web-based multi-user time-tracking application. Prior to version 2.51.0, \"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. This issue has been patched in version 2.51.0.","status":"published","published":"2026-03-06T04:49:08.312Z","modified":"2026-03-09T19:46:54.339Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-285"],"updated_at":"2026-11-02T14:20:01.969981Z"}],"euvd":[{"id":"EUVD-2026-9994","description":"Kimai is a web-based multi-user time-tracking application. Prior to version 2.51.0, \"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. This issue has been patched in version 2.51.0.","status":"published","published":"2026-03-06T04:49:08Z","modified":"2026-03-09T19:46:54Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"updated_at":"2026-11-02T19:48:06.581696Z"}],"ghsa":[{"id":"GHSA-v33r-r6h2-8wr7","title":"Kimai's API invoice endpoint missing customer-level access control (IDOR)","description":"## Summary\n\n`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.\n\n## Affected Code\n\n`src/API/InvoiceController.php` line 92-101:\n\n```php\n#[IsGranted('view_invoice')]           // Role check only, no customer access check\n#[Route(methods: ['GET'], path: '/{id}', name: 'get_invoice', requirements: ['id' =\u003e '\\d+'])]\npublic function getAction(Invoice $invoice): Response\n{\n    $view = new View($invoice, 200);\n    $view-\u003egetContext()-\u003esetGroups(self::GROUPS_ENTITY);\n    return $this-\u003eviewHandler-\u003ehandle($view);  // Returns ANY invoice by ID\n}\n```\n\nThe web controller (`src/Controller/InvoiceController.php` line 304-307) correctly checks customer access:\n\n```php\n#[IsGranted('view_invoice')]\n#[IsGranted(new Expression(\"is_granted('access', subject.getCustomer())\"), 'invoice')]\npublic function downloadAction(Invoice $invoice, ...): Response { ... }\n```\n\nThe `access` attribute in `CustomerVoter` (line 71-87) verifies team membership, but this check is entirely missing from the API endpoint.\n\n## PoC\n\nTested against Kimai v2.50.0 (Docker: `kimai/kimai2:apache`).\n\nSetup:\n- TeamA with CustomerA (\"SecretCorp\"), TeamB with CustomerB (\"BobCorp\")\n- Bob is a teamlead in TeamB only\n- An invoice exists for SecretCorp (TeamA)\n\n```bash\n# Bob (TeamB) reads SecretCorp (TeamA) invoice\ncurl -H \"Authorization: Bearer BOB_TOKEN\" http://localhost:8888/api/invoices/1\n```\n\nResponse (200 OK):\n```json\n{\n  \"invoiceNumber\": \"INV-2026-001\",\n  \"total\": 15000.0,\n  \"currency\": \"USD\",\n  \"customer\": {\"name\": \"SecretCorp\", ...}\n}\n```\n\nBob can also enumerate all invoices via `GET /api/invoices` — the list endpoint uses `setCurrentUser()` in the query but the single-item endpoint bypasses this entirely via Symfony ParamConverter.\n\n## Impact\n\nAny teamlead can read all invoices across the system regardless of team assignment. Invoice data typically contains sensitive financial information (amounts, customer details, payment terms). In multi-team deployments this breaks the intended data isolation between teams.\n\n## Suggested Fix\n\nAdd the customer access check to the API endpoint, matching the web controller:\n\n```diff\n #[IsGranted('view_invoice')]\n+#[IsGranted(new Expression(\"is_granted('access', subject.getCustomer())\"), 'invoice')]\n #[Route(methods: ['GET'], path: '/{id}', name: 'get_invoice')]\n public function getAction(Invoice $invoice): Response\n```","status":"published","published":"2026-03-04T20:43:17Z","modified":"2026-05-11T05:37:54Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-285","CWE-862"],"updated_at":"2026-11-02T14:40:05.816188Z"}],"nvd":[{"id":"CVE-2026-28685","description":"Kimai is a web-based multi-user time-tracking application. Prior to version 2.51.0, \"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. This issue has been patched in version 2.51.0.","status":"published","published":"2026-03-06T05:16:38.77Z","modified":"2026-06-17T10:28:54.35Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-285"],"updated_at":"2026-11-02T14:12:08.279852Z"}],"osv":[{"id":"CVE-2026-28685","title":"Kimai: API invoice endpoint missing customer-level access control (IDOR)","description":"Kimai is a web-based multi-user time-tracking application. Prior to version 2.51.0, \"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. This issue has been patched in version 2.51.0.","status":"published","published":"2026-03-06T04:49:08.312Z","modified":"2026-10-28T02:30:47.801738742Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-285"],"updated_at":"2026-11-02T15:45:00.530889Z"},{"id":"GHSA-v33r-r6h2-8wr7","title":"Kimai's API invoice endpoint missing customer-level access control (IDOR)","description":"## Summary\n\n`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.\n\n## Affected Code\n\n`src/API/InvoiceController.php` line 92-101:\n\n```php\n#[IsGranted('view_invoice')]           // Role check only, no customer access check\n#[Route(methods: ['GET'], path: '/{id}', name: 'get_invoice', requirements: ['id' =\u003e '\\d+'])]\npublic function getAction(Invoice $invoice): Response\n{\n    $view = new View($invoice, 200);\n    $view-\u003egetContext()-\u003esetGroups(self::GROUPS_ENTITY);\n    return $this-\u003eviewHandler-\u003ehandle($view);  // Returns ANY invoice by ID\n}\n```\n\nThe web controller (`src/Controller/InvoiceController.php` line 304-307) correctly checks customer access:\n\n```php\n#[IsGranted('view_invoice')]\n#[IsGranted(new Expression(\"is_granted('access', subject.getCustomer())\"), 'invoice')]\npublic function downloadAction(Invoice $invoice, ...): Response { ... }\n```\n\nThe `access` attribute in `CustomerVoter` (line 71-87) verifies team membership, but this check is entirely missing from the API endpoint.\n\n## PoC\n\nTested against Kimai v2.50.0 (Docker: `kimai/kimai2:apache`).\n\nSetup:\n- TeamA with CustomerA (\"SecretCorp\"), TeamB with CustomerB (\"BobCorp\")\n- Bob is a teamlead in TeamB only\n- An invoice exists for SecretCorp (TeamA)\n\n```bash\n# Bob (TeamB) reads SecretCorp (TeamA) invoice\ncurl -H \"Authorization: Bearer BOB_TOKEN\" http://localhost:8888/api/invoices/1\n```\n\nResponse (200 OK):\n```json\n{\n  \"invoiceNumber\": \"INV-2026-001\",\n  \"total\": 15000.0,\n  \"currency\": \"USD\",\n  \"customer\": {\"name\": \"SecretCorp\", ...}\n}\n```\n\nBob can also enumerate all invoices via `GET /api/invoices` — the list endpoint uses `setCurrentUser()` in the query but the single-item endpoint bypasses this entirely via Symfony ParamConverter.\n\n## Impact\n\nAny teamlead can read all invoices across the system regardless of team assignment. Invoice data typically contains sensitive financial information (amounts, customer details, payment terms). In multi-team deployments this breaks the intended data isolation between teams.\n\n## Suggested Fix\n\nAdd the customer access check to the API endpoint, matching the web controller:\n\n```diff\n #[IsGranted('view_invoice')]\n+#[IsGranted(new Expression(\"is_granted('access', subject.getCustomer())\"), 'invoice')]\n #[Route(methods: ['GET'], path: '/{id}', name: 'get_invoice')]\n public function getAction(Invoice $invoice): Response\n```","status":"published","published":"2026-03-04T20:43:17Z","modified":"2026-03-06T15:31:28.398898Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-285","CWE-862"],"updated_at":"2026-11-02T15:46:56.639206Z"}]}}
