CVE-2026-41587 Overview
CVE-2026-41587 is an unrestricted file upload vulnerability [CWE-434] in CI4MS, a CodeIgniter 4-based CMS skeleton with RBAC authorization and theme support. The flaw resides in the theme upload feature exposed to authenticated backend users holding the theme-upload permission. CI4MS extracts uploaded ZIP archives directly into the web-accessible public/ directory without filtering file extensions or content. Attackers can include PHP files inside a crafted ZIP and execute them over HTTP, achieving remote code execution on the underlying server. The issue affects versions from 0.26.0.0 up to but not including 0.31.7.0 and is patched in 0.31.7.0.
Critical Impact
Any authenticated backend user with the theme-upload permission can achieve remote code execution on the CI4MS host by uploading a ZIP archive containing PHP files.
Affected Products
- CI4MS versions >= 0.26.0.0
- CI4MS versions < 0.31.7.0
- CodeIgniter 4-based deployments using the CI4MS Theme module
Discovery Timeline
- 2026-05-07 - CVE-2026-41587 published to NVD
- 2026-05-07 - Last updated in NVD database
Technical Details for CVE-2026-41587
Vulnerability Analysis
CI4MS exposes a backend theme installer that accepts ZIP archives and extracts them into the application's public/ directory. Before the patch, the extraction loop in modules/Theme/Controllers/Theme.php performed only a minimal path traversal check using a regular expression that matched literal .. substrings. It did not validate file extensions or scan archive contents for executable code.
Because public/ is served directly by the web server, any PHP file written there becomes reachable over HTTP. An authenticated attacker holding the theme-upload permission can package arbitrary .php payloads inside a theme ZIP and request the resulting URL to execute code in the application context. Successful exploitation yields full read, write, and code execution capability on the application's confidentiality, integrity, and availability.
Root Cause
The root cause is the absence of an allow-list for file extensions written into a web-accessible directory. The pre-patch loop iterates each ZIP entry and writes it without inspecting its extension, MIME type, or magic bytes. PHP source files are treated identically to static assets such as CSS or images.
Attack Vector
Exploitation requires network access to the backend and an authenticated session with the theme-upload privilege. The attacker crafts a ZIP archive named like a legitimate theme package, embeds a PHP webshell, and submits it through the theme upload form. After extraction, the attacker requests the deployed file under the public/ URL path and executes arbitrary code.
$tempPath = WRITEPATH . 'tmp/' . str_replace('_theme.zip', '', $file->getName()) . '/';
$zip = new \ZipArchive();
if ($zip->open($file->getTempName()) === true) {
+ // ── Security: Pre-extraction validation ──────────────────────
+ // Allowed static file extensions for the public/ directory.
+ // PHP files MUST NOT be written under public/ (RCE prevention).
+ $allowedPublicExtensions = [
+ 'css', 'js', 'map',
+ 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'bmp', 'avif',
+ 'woff', 'woff2', 'ttf', 'eot', 'otf',
+ 'xml', 'json', 'txt', 'md',
+ 'mp4', 'webm', 'ogg', 'mp3', 'wav',
+ 'pdf',
+ ];
+
+ $forbiddenEntries = [];
+
for ($i = 0; $i < $zip->numFiles; $i++) {
$entryName = $zip->getNameIndex($i);
- if (preg_match('/\.\./', $entryName)) {
+
+ // 1. Path traversal check (strengthened)
+ if (preg_match('/\.\.[\\/\\\\]/', $entryName) || preg_match('/^[\\/\\\\]/', $entryName)) {
$zip->close();
- return redirect()->route('backendThemes')->withInput()->with('errors', [lang('Theme.zipOpenFailed')]);
+ return redirect()->route('backendThemes')->withInput()
+ ->with('errors', [lang('Theme.pathTraversalDetected', [$entryName])]);
+ }
+
+ // 2. Forbidden file extension check for public/ directory
Source: GitHub Commit b969465
The patch introduces an allow-list of static asset extensions, strengthens the path traversal regex to catch directory separators, and rejects archives that contain forbidden entries before any file is written.
Detection Methods for CVE-2026-41587
Indicators of Compromise
- New .php, .phtml, or .phar files appearing under the application's public/ directory tree, especially within theme subfolders.
- ZIP files staged in WRITEPATH/tmp/ containing PHP entries, indicating recent theme uploads.
- HTTP POST requests to backend theme upload endpoints followed by GET requests to newly created public/themes/<name>/*.php paths.
- Web server access log entries showing PHP execution under theme directories from external IP addresses.
Detection Strategies
- Hash and inventory all files under public/ and alert on the creation of executable PHP content within theme directories.
- Inspect web server logs for sequential theme upload requests and subsequent direct requests to .php files inside theme paths.
- Audit CI4MS application logs for theme-upload permission usage by accounts that do not normally manage themes.
- Run static scanners against extracted theme directories to flag PHP eval, system, exec, or base64_decode patterns common to webshells.
Monitoring Recommendations
- Enable file integrity monitoring on public/ and WRITEPATH/tmp/ to capture writes and extractions in real time.
- Forward web and application logs to a centralized analytics platform and alert on theme upload events outside change windows.
- Track authentication events for backend accounts with the theme-upload permission and review for credential abuse.
How to Mitigate CVE-2026-41587
Immediate Actions Required
- Upgrade CI4MS to version 0.31.7.0 or later, which enforces an extension allow-list during ZIP extraction.
- Audit the public/ directory for unexpected PHP files and remove any that were not part of an approved theme.
- Revoke the theme-upload permission from accounts that do not require it and rotate credentials for any account that recently uploaded themes.
- Review web server and application logs for suspicious theme uploads or PHP execution since 0.26.0.0 was deployed.
Patch Information
The vendor fix is in commit b969465e71eacd9eb57014ad1fce1fc34fa7bca0 and is shipped in CI4MS 0.31.7.0. See the GitHub Security Advisory GHSA-fw49-9xq4-gmx6 and the upstream commit for the patch contents, including the allow-list of static extensions and the strengthened path traversal regex.
Workarounds
- Restrict the theme-upload permission to a minimal set of trusted administrators until upgrading is possible.
- Configure the web server to deny PHP execution within theme directories under public/, for example by disabling .php handling for theme subpaths.
- Place the backend interface behind an IP allow-list or VPN to reduce exposure of the upload endpoint.
# Apache: disable PHP execution under public/themes/
<Directory "/var/www/ci4ms/public/themes">
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>
php_flag engine off
</Directory>
# Nginx: return 403 for PHP files under theme directories
location ~ ^/themes/.*\.(php|phtml|phar)$ {
return 403;
}
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.


