GitHub Security Advisory: GHSA-9rxp-f27p-wv3h
Package: ci4-cms-erp/ci4ms (COMPOSER)
Severity: MODERATE (CVSS 6.7)
CWE: CWE-285: Improper Authorization
Vulnerable versions: <= 0.31.3.0
Patched in: 0.31.4.0

Summary: CI4MS has a Hidden Items Authorization Bypass in Fileeditor Allows Reading Secrets and Writing Protected Files

Details:
## Summary

The Fileeditor controller defines a `hiddenItems` array containing security-sensitive paths (`.env`, `composer.json`, `vendor/`, `.git/`) but only enforces this protection in the `listFiles()` method. The `readFile()`, `saveFile()`, `deleteFileOrFolder()`, `renameFile()`, `createFile()`, and `createFolder()` endpoints perform no hidden items validation, allowing direct API access to files that are intended to be protected. A backend user with only `fileeditor.read` permission can exfiltrate application secrets from `.env`, and a user with `fileeditor.update` permission can overwrite `composer.json` to achieve remote code execution.

## Details

The `hiddenItems` array is defined at `modules/Fileeditor/Controllers/Fileeditor.php:10-26`:

```php
protected $hiddenItems = [
    '.git', '.github', '.idea', '.vscode',
    'node_modules', 'vendor', 'writable',
    '.env', 'env', 'composer.json', 'composer.lock',
    'tests', 'spark', 'phpunit.xml.dist', 'preload.php'
];
```

This array is checked **only** in `listFiles()` at lines 45-48 and 64:

```php
// Line 45-48 - path component check
foreach ($pathParts as $part) {
    if (in_array($part, $this->hiddenItems)) {
        return $this->failForbidden();
    }
}
// Line 64 - directory listing filter
if (in_array($name, $this->hiddenItems)) continue;
```

However, `readFile()` (line 76) performs **neither** a `hiddenItems` check **nor** an `allowedFileTypes()` check:

```php
public function readFile()
{
    // ... validation ...
    $path = $this->request->getVar('path');
    $fullPath = realpath(ROOTPATH . $path);
    if (!$fullPath || !is_file($fullPath) || strpos($fullPath, realpath(ROOTPATH)) !== 0) {
        return $this->response->setJSON(['error' => '...'])->setStatusCode(400);
    }
    return $this->response->setJSON(['content' => file_get_contents($fullPath)]);
}
```

This means any file within ROOTPATH — regardless of extension (`.php`, `.env`, etc.) — can be read by any user with the `fileeditor.read` permission.

Similarly, `saveFile()` (line 92) checks `allowedFileTypes()` but not `hiddenItems`. Since `json` is in `$allowedExtensions`, `composer.json` (which is explicitly in `hiddenItems`) can be overwritten:

```php
protected $allowedExtensions = ['css', 'js', 'html', 'txt', 'json', 'sql', 'md'];
```

`deleteFileOrFolder()` (line 194) checks neither `hiddenItems` nor `allowedFileTypes()`.

**Compounding factor:** CSRF protection is disabled for all fileeditor routes in `modules/Fileeditor/Config/FileeditorConfig.php:7-10`:

```php
public $csrfExcept = [
    'backend/fileeditor',
    'backend/fileeditor/*',
];
```

This means the write and delete operations are additionally vulnerable to cross-site request forgery if an authenticated user visits a malicious page.

## PoC

Requires an authenticated backend session with `fileeditor.read` permission granted.

**Step 1: Read .env file to extract secrets**
```bash
curl -s -b 'ci_session=<valid_session_cookie>' \
  'https://target.com