# REPRO-2026-00384: CodeIgniter4 View Parser conditional tags allow arbitrary PHP code execution in user-editable templates ## Summary Status: published Severity: high CVSS: Unknown CWE: CWE-94 (Improper Control of Generation of Code ('Code Injection')) Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00384 GHSA: GHSA-4Q58-JW8X-8CM7 ## Package Name: codeigniter4/framework (Composer ecosystem) Ecosystem: composer Affected: < 4.7.5 Fixed: 4.7.5 ## Root Cause # RCA Report — GHSA-4q58-jw8x-8cm7 ## Summary CodeIgniter4's View Parser evaluates `{if ...}` / `{elseif ...}` conditional tags by splicing the *unmodified* tag expression into a PHP `if (...)`: statement and running the resulting template through `eval()`. When an application renders user-editable template source (e.g. an online template editor) with `Parser::render()`/`renderString()`, any user who can edit the template can inject arbitrary PHP expressions into the conditional tag and achieve server-side arbitrary PHP code execution (CWE-94). Version 4.7.5 adds an opt-in restriction (`Config\View::$restrictParserConditionals` or the per-render `restrictConditionals` option) that tokenizes the condition and rejects anything beyond variables, literals, and comparison/logical operators. ## Impact - Package/component: `codeigniter4/framework`, `system/View/Parser.php` (`Parser::parseConditionals()`) - Affected versions: `< 4.7.5` (reproduced on v4.7.4, commit `2bd0f01d2813f9ec06db42643ce39d9f5428bf6d`); **4.7.5 remains exploitable unless the application explicitly enables `restrictParserConditionals` / the `restrictConditionals` render option** (confirmed at runtime). - Risk: High (CVSS 8.8). Remote, low-privilege attacker with template-edit capability gains full PHP code execution in the web server process: arbitrary command execution, file read/write, data theft, pivoting. ## Impact Parity - Disclosed/claimed maximum impact: arbitrary PHP code execution (code injection, CWE-94) via View Parser conditional tags — `code_execution`. - Reproduced impact from this run: arbitrary PHP code execution through the real HTTP endpoint of a running CodeIgniter 4.7.4 application. The payload `{if (file_put_contents(getenv('PRUVA_MARKER_DIR').'/vuln1.txt','...')!==false) && print(shell_exec('id'))}COND_BRANCH_OK{endif}` wrote attacker-named marker files to disk *and* returned the output of `shell_exec('id')` (`uid=1000(vscode) gid=1000(vscode) ...`) in the HTTP response body. - Parity: **full**. No gap between claimed and demonstrated impact. ## Root Cause `Parser::parseConditionals()` (v4.7.4, `system/View/Parser.php:455`) extracts each `{if CONDITION}` tag with a regex and replaces it with ``, embedding the attacker-controlled `CONDITION` verbatim into PHP source. The whole template is then executed with `eval('?>' . $template . 'tempData)`. The only sanitization applied beforehand is a `str_replace` of literal ``, which does nothing to stop code injected *through* the conditional expression itself. Because the condition is arbitrary PHP, expressions such as `(file_put_contents(...)!==false) && print(shell_exec('id'))` execute with the privileges of the PHP process. Fix (v4.7.5): `Parser::parseTemplate()` computes `$this->restrictConditionals = ... || (bool) ($options['restrictConditionals'] ?? $this->config->restrictParserConditionals)` and `parseConditionals()` throws `ViewException::forRestrictedConditional()` when `isRestrictedCondition()` (a `PhpToken::tokenize` allow-list of variables, literals, arithmetic/comparison/logical operators, and grouping parentheses) rejects the expression. The restriction is **opt-in** (`public bool $restrictParserConditionals = false;` default), so upgrading alone does not close the hole — verified at runtime in this run. Advisory: https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-4q58-jw8x-8cm7 Fix diff: `git diff v4.7.4 v4.7.5 -- system/View/Parser.php app/Config/View.php` ## Reproduction Steps 1. `bundle/repro/reproduction_steps.sh` (self-contained; run twice consecutively, both runs exit 0). 2. The script: - installs PHP CLI + composer if missing; - checks out CodeIgniter4 `v4.7.4` (vulnerable) into `/repo` and `v4.7.5` (fixed) into `bundle/artifacts/ci475`, verifying the patch hunk is absent/present; - `composer install --no-dev` in both apps; - injects a `TemplateRender` controller exposing `POST /render`, which passes the request's `template` body straight into `service('parser')->setData([...])->renderString($template, $options)` (with `restrictConditionals=true` when `restrict=1`); - serves both apps over HTTP with the PHP built-in web server (the exact command `php spark serve` execs), health-checks `GET /`; - sends the conditional-tag payload twice per scenario and asserts: - v4.7.4: marker file created with the unique token **and** `uid=` from `shell_exec('id')` present in the HTTP response (RCE confirmed); - v4.7.5 + `restrict=1`: no marker, no `uid=`, response is a 500 `ViewException: The Parser conditional is not allowed in restricted mode`; - v4.7.5 + `restrict=0` (default): marker created, `uid=` present — advisory note "upgrading alone is insufficient" confirmed. 3. Expected evidence: `[+] vuln attempt N: arbitrary PHP executed`, `[+] fixed-restricted attempt N: payload neutralized`, `[+] fixed-unrestricted attempt N: still exploitable`, final `RESULT: CONFIRMED`, exit code 0. ## Evidence - Full run log: `bundle/logs/reproduction_steps.log` - Server logs: `bundle/logs/vuln_server.log`, `bundle/logs/fixed_server.log` (PHP built-in server request logs for both apps) - HTTP request/response captures: `bundle/artifacts/http/` (e.g. `vuln1_request.txt` = payload, `vuln1_response.txt` contains `uid=1000(vscode) gid=1000(vscode) groups=1000(vscode)` + `COND_BRANCH_OK`; `fixedr1_response.txt` contains the `ViewException` restricted-mode message) - Marker files written by the eval'd payload: `bundle/repro/markers/vuln1.txt`, `vuln2.txt`, `fixedu1.txt`, `fixedu2.txt` (each contains the per-attempt unique token `GHSA-4q58-jw8x-8cm7 arbitrary PHP executed token=...`) - Runtime manifest with target identity and SHA-256 of every proof artifact: `bundle/repro/runtime_manifest.json` - Environment: Ubuntu 26.04, PHP 8.5.4 (cli, NTS), Composer 2.9.5, CodeIgniter v4.7.4 (`2bd0f01d...`) and v4.7.5 (`36256090...`), served via `php -S localhost:8090/8091` with `CI_ENVIRONMENT=production`. ## Recommendations / Next Steps - Upgrade to `codeigniter4/framework >= 4.7.5` **and** set `Config\View::$restrictParserConditionals = true` (or pass `['restrictConditionals' => true]` to every `render()`/`renderString()` call that processes less-trusted template source). Upgrading without enabling the restriction leaves the application exploitable. - Longer-term, the framework should consider making the restricted mode the default for `renderString()` of non-file templates. - Test recommendation: regression test asserting that a conditional tag containing a function call raises `ViewException::forRestrictedConditional` when restriction is enabled (this exact behavior was observed at runtime). ## Additional Notes - Idempotency: the script is fully re-runnable. It reuses the prepared project cache checkout (`/pruva/project-cache/repo`) and composer `vendor/` when present, kills stale `php -S` listeners on ports 8090/8091 before starting fresh servers, cleans the marker directory, and regenerates all HTTP captures, markers, and `runtime_manifest.json` with fresh per-attempt tokens on every run. Two consecutive clean runs passed (exit 0). - The reproduction uses the framework repository's own application skeleton (`app/`, `public/`, `system/rewrite.php`) served in `production` mode — no mocks, no sanitizer, no reimplementation of the vulnerable code. - Edge case noted: `spark serve` silently increments the port when the requested one is busy; the script therefore runs the underlying `php -S` command directly and pre-kills stale listeners so the healthcheck and exploit always target the intended instance. ## Reproduction Details Reproduced: 2026-10-09T18:02:47.398Z Duration: 4661 seconds Tool calls: 158 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00384 pruva-verify GHSA-4Q58-JW8X-8CM7 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00384&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00384/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - GitHub Advisory: https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-4q58-jw8x-8cm7 - Source: https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-4q58-jw8x-8cm7 ## Artifacts - bundle/repro/rca_report.md (analysis, 7849 bytes) - bundle/repro/reproduction_steps.sh (reproduction_script, 12195 bytes) - bundle/artifacts/http/fixedr1_response.txt (other, 302 bytes) - bundle/artifacts/http/fixedr2_response.txt (other, 302 bytes) - bundle/artifacts/http/fixedu1_response.txt (other, 68 bytes) - bundle/artifacts/http/fixedu2_response.txt (other, 68 bytes) - bundle/artifacts/http/vuln1_request.txt (other, 198 bytes) - bundle/artifacts/http/vuln1_response.txt (other, 68 bytes) - bundle/artifacts/http/vuln2_response.txt (other, 68 bytes) - bundle/logs/fixed_server.log (log, 563 bytes) - bundle/logs/reproduction_steps.log (log, 1851 bytes) - bundle/logs/vuln_server.log (log, 373 bytes) - bundle/repro/markers/fixedu1.txt (other, 76 bytes) - bundle/repro/markers/fixedu2.txt (other, 76 bytes) - bundle/repro/markers/vuln1.txt (other, 74 bytes) - bundle/repro/markers/vuln2.txt (other, 74 bytes) - bundle/repro/runtime_manifest.json (other, 2860 bytes) - bundle/repro/validation_verdict.json (other, 1301 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00384 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00384/artifacts/bundle/repro/reproduction_steps.sh - Web: https://www.pruva.dev/reproductions/REPRO-2026-00384 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://www.pruva.dev