Skip to content

Latest commit

 

History

History
35 lines (29 loc) · 9.85 KB

SECURITY_HALL_OF_FAME.md

File metadata and controls

35 lines (29 loc) · 9.85 KB

🏆 Security Hall of Fame

A huge thank you to the security researchers who have helped make ci4ms more secure by finding and reporting vulnerabilities.

Contributor Contribution Date
Lars van Mil Identified Critical RCE and Information Disclosure vulnerabilities. Jan 2026
0xAlchemist Identified Critical Stored DOM XSS vulnerabilities across Company Info, Social Media, and Mail Settings modules, and a Session Invalidation flaw, leading to Account Takeover, Privilege Escalation, and potential Platform Compromise. Feb 2026
peeefour Identified Stored DOM XSS vulnerabilities leading to Account Takeover. Feb 2026
Hunter. Identified Critical Stored XSS in Backend & Blog modules allowing Session Hijacking. Feb 2026
m1scher Assisted with vulnerability triaging and security testing. Feb 2026
alpernae Assisted with vulnerability triaging and security testing. Feb 2026
offset Identified Critical vulnerabilities including multiple Stored XSS (Blog & Pages content via broken html_purify validation), Authorization Bypass in Fileeditor destructive operations (delete/rename extension allowlist missing), Install Guard Bypass, and CRLF Injection. Apr – May 2026
fg0x0 Identified Critical Arbitrary File Write (Zip Slip RCE) vulnerabilities in Theme::upload and Backup::restore modules. Apr 2026
0xAlchemist , peeefour and DexterHK Identified Critical Full Account Takeover and Privilege Escalation via Stored DOM Blind XSS in Backup Management (v2). Apr 2026
dapickle Identified Critical Authenticated RCE in Theme installation, Arbitrary Database Table Drop in Theme module, and a Session Management Bypass. Apr 2026
iltosec Identified Broken Access Control in Media module, Unsafe Reflection in Dashboard Widgets, RCE via template-function parsing in Pages, and Stored XSS in Pages Cover Image URL leading to Account Takeover (the residual instance of the same Cover Image URL Stored XSS class in Blog Categories was subsequently hardened as well). Jun 2026
skeletonsec Coordinated disclosure of a delegated-privilege → superadmin → RCE chain (permission-management endpoints missing actor-scope checks, CWE-863), production installs shipping in development mode (debug toolbar, stack traces, login/comment captcha bypass, CWE-489), and unauthenticated self-registration on the backend (CWE-306). The report prompted an independent audit that hardened further access-control gaps in the same class (Backup restore, Methods permission mapping, user-group assignment). Aug 2026
isl4r Identified a lock-screen bypass (GHSA-2f22-3xhp-xp4j): a forged remember_token cookie — a cookie this application never legitimately issues — skipped the only server-side enforcement point for a locked administrative session's locked_at state (Modules\Auth\Filters\SessionTracker), and the identical check client-side kept the idle timer from ever arming in the first place. Either half let an attacker with browser access to a locked admin session keep using it without ever reaching the password check in LockController::unlockAction(). Sep 2026
0xAlchemist Identified a type-confusion flaw in auth_permissions_pages.typeOfPermissions (GHSA-5j8q): an unvalidated, non-array POST value reaching Methods::create()/update() threw an uncaught TypeError (HTTP 500), and the same missing type guard on the read side let Ci4MsAuthFilter::neededAction() either crash on a scalar JSON value or silently fall back to a permissive default action when the column held malformed JSON. Sep 2026
0xAlchemist and squ4r00t Identified a user-enumeration flaw in the backend login response (GHSA-ppvr-272j-ggvv): codeigniter4/shield's Session::check() returned a distinct message for a login attempt against a nonexistent account versus one against a real account with the wrong password, letting an unauthenticated attacker enumerate valid accounts through the login form alone. Sep 2026
0xAlchemist and squ4r00t Identified a user-enumeration flaw in the backend magic-link request endpoint (GHSA-cwhg-7xgv-343q), a sibling of the login finding above: MagicLinkController::loginAction() answered a nonexistent account with a 302 redirect carrying Auth.invalidEmail (embedding the submitted address), while an existing account got a 200 view, letting an unauthenticated attacker enumerate valid accounts through the public magic-link form alone. The project's audit of the report also found a second oracle (the mail-transport failure branch, reachable only for existing accounts) and a missing isRobot() guard in verify(). Oct 2026

Maintainer-Identified Findings

Vulnerabilities the project maintainer found through their own internal review, rather than an external report, are logged here separately so the table above stays accurate to who actually disclosed each one.

Finding Contribution Date
Bertuğ Fahri ÖZER (project maintainer, internal audit — not an external report) Identified that Methods::moduleUpload()/moduleCreate()/moduleDelete() were gated only by a single, delegable create/delete permission role, even though uploading a module ZIP is uploading PHP code that app/Config/Routes.php requires unconditionally on the next request — a delegated permission holder could reach remote code execution, or use the create/delete endpoints to write or drop arbitrary module files and tables. Closed with a hardcoded inGroup('superadmin') gate on all three controller methods. Sep 2026
Bertuğ Fahri ÖZER (project maintainer, internal audit — not an external report) Identified that Methods::update() wrote an auth_permissions_pages row's pagename, className, methodName and typeOfPermissions with no actor-scope check at all — exactly the four columns Ci4MsAuthFilter reads on every request to decide authorization (className+methodName locate the row, permission_string($page->pagename, ...) and typeOfPermissions decide the verdict), and the existing classNameMethodNameCollides() guard only stopped a duplicate (className, methodName) pair, not a pagename/typeOfPermissions change on an existing row. A delegated permission-page editor could therefore remap any route's authorization to a permission they already held, effective immediately since update() already calls rbac_cache_flush(). Closed with a hardcoded inGroup('superadmin') gate on create(), update() and the POST+AJAX branch of index(), plus PROTECTED_MODULES enforcement extended to update() and index()'s POST branch (both the module_id and page_id paths), applied even to superadmin; index()'s GET branch was left open so read-permitted operators keep the list screen. Sep 2026
Bertuğ Fahri ÖZER (project maintainer, internal audit — not an external report) Identified that the backend login captcha could be skipped entirely by omitting the captcha field: LoginController::loginAction() compared the submitted value to the expected one with a loose ==, the expected phrase exists only in one-request flashdata written by the login form's GET, and Shield's getLoginRules() carries no captcha rule — so a client that never fetched the form sent null against an expected null, and null == null passed. Both layers were open at once; a session and CSRF token obtainable from any other page was enough. Closed with a captcha => required validation rule plus a fail-closed comparison (hash_equals(), empty expected value rejected). Project CVSS assessment: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N = 5.3, modelling the lost anti-automation control (credential stuffing, CWE-804 / CWE-307) rather than an authentication bypass — login still required the correct password; read literally as no direct C/I/A impact the same flaw scores 0.0. The auth-rates throttle stayed in force. Details in CHANGELOG.md. Oct 2026
Bertuğ Fahri ÖZER (project maintainer, internal audit — not an external report) Identified that Modules\Auth\Config\AuthConfig exempted two state-changing lock-screen POSTs from CSRF via $csrfExcept = ['backend/lock', 'backend/lock/*']: POST backend/lock (LockController::unlockAction(), which logs out and destroys the session on the third failed attempt) and POST backend/lock/set (setLockAction(), which locks the session). SameSite=Lax was the only remaining defence. A defense-in-depth gap rather than a standalone bypass, since SameSite=Lax already blocked a plain cross-site POST. Closed by emptying $csrfExcept; CI4's own Filters::initialize() confirms both routes now carry the csrf before-filter. Project CVSS assessment: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L = 3.1, with AC:H for the SameSite=Lax barrier (an attack needs a same-site foothold or a browser that does not enforce Lax), UI:R for the victim opening attacker content, and A:L for forced session termination or locking with no data read or modified. Details in CHANGELOG.md. Oct 2026

If you find a security vulnerability, please report it via the Security Policy. Non-security bug reporters are credited separately in BUG_REPORTERS.md.