GitHub Security Advisory: GHSA-vfhx-5459-qhqh
Package: ci4-cms-erp/ci4ms (COMPOSER)
Severity: HIGH (CVSS 8.1)
CWE: CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection')
Vulnerable versions: <= 0.31.3.0
Patched in: 0.31.4.0

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

Details:
## 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&db[CREDENTIAL_REDACTED]=MySQLi&dbpre=ci4ms_&dbport=3306&name=Admin&surname=User&username=admin&[CREDENTIAL_REDACTED]=admin@example.com&sit