# REPRO-2026-00156: Yii2: local file inclusion via View::renderPhpFile extract() of caller-controlled params ## Summary Status: published Severity: high Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00156 GHSA: GHSA-5vpg-rj7q-qpw2 CVE: CVE-2026-39850 ## Package Name: yiisoft/yii2 Ecosystem: composer Affected: <= 2.0.54 Fixed: 2.0.55 ## Root Cause # Root Cause Analysis: CVE-2026-39850 ## Summary CVE-2026-39850 is a Local File Inclusion (LFI) vulnerability in Yii2's `View::renderPhpFile()` method. The method uses `extract($_params_, EXTR_OVERWRITE)` before `require $_file_`, which allows a malicious `_file_` key in the params array to overwrite the local `$_file_` variable. This redirects the `require` statement to an attacker-controlled file path, enabling arbitrary file inclusion. ## Impact - **Package**: `yiisoft/yii2` - **Affected versions**: `<= 2.0.54` - **Fixed version**: `2.0.55` - **CWE**: CWE-98 / CWE-94 (LFI / Code Injection) - **Risk level**: High (CVSS 3.1 base 7.4) - **Consequences**: Attackers who control any part of the params array passed to `renderPhpFile` can read arbitrary files and potentially execute arbitrary PHP code if the included file contains PHP. ## Root Cause In `yii\base\View::renderPhpFile()`, the vulnerable code sequence was: ```php extract($_params_, EXTR_OVERWRITE); require $_file_; ``` Because `EXTR_OVERWRITE` is used, variables in `$_params_` overwrite existing local variables with the same name. If `$_params_` contains a key named `_file_`, the local `$_file_` variable (which holds the intended template path) is overwritten with the attacker-supplied value immediately before the `require` executes. The fix (commit `109878b491dbffa541032bc99fb5e26d12cd0375`) isolates the `extract` and `require` inside a closure: ```php $_renderer_ = function () { extract(func_get_arg(1), EXTR_OVERWRITE); require func_get_arg(0); }; call_user_func_array($_renderer_, [$_file_, $_params_]); ``` Because `extract` runs inside the closure's local scope, it can only overwrite variables within that scope, not the `$_file_` variable in the parent `renderPhpFile` scope. Thus the intended template path is preserved. A similar fix was applied to `yii\web\ErrorHandler::renderFile()`. ## Reproduction Steps The reproduction script is `repro/reproduction_steps.sh`. What the script does: 1. Clones the `yiisoft/yii2` repository. 2. Creates a benign template (`safe.php`) and a secret file (`secret.txt`). 3. Checks out the vulnerable tag `2.0.54`. 4. Runs a PHP CLI script that instantiates `yii\base\View` and calls `renderPhpFile('safe.php', ['_file_' => 'secret.txt'])`. 5. Captures the output. 6. Checks out the fixed tag `2.0.55` and repeats the test. Expected evidence: - On **2.0.54** (vulnerable), the output contains `SECRET_DATA_LINE` from the secret file, proving LFI. - On **2.0.55** (fixed), the output contains `SAFE OUTPUT` from the intended template, proving the fix works. ## Evidence ### Vulnerable build (2.0.54) - **Log**: `logs/vulnerable_2.0.54.log` - **Excerpt**: ``` === RESULT === SECRET_DATA_LINE === END === VULNERABLE: _file_ param was able to override local variable and include secret file ``` ### Fixed build (2.0.55) - **Log**: `logs/fixed_2.0.55.log` - **Excerpt**: ``` === RESULT === SAFE OUTPUT === END === FIXED: _file_ param did not override local variable, safe.php was rendered ``` ### Environment - PHP 8.4.19 (cli) - Composer 2.8.12 - Git 2.x - Yii2 cloned from https://github.com/yiisoft/yii2 ## Recommendations / Next Steps - **Immediate**: Upgrade `yiisoft/yii2` to `>= 2.0.55`. - **Defense in depth**: Sanitize or validate user-controlled data before passing it as view params. Avoid passing raw user input directly to `renderPhpFile` or `renderFile`. - **Testing**: Add regression tests that pass `_file_` and other internal variable names in view params to ensure future changes do not reintroduce scope leakage. ## Additional Notes - **Idempotency**: The reproduction script was run twice consecutively and produced identical results both times. - **Edge cases**: The same vulnerability pattern existed in `yii\web\ErrorHandler::renderFile()`, which was fixed in the same commit. The reproduction focuses on `View::renderPhpFile()` as the primary attack surface. - **No web server required**: The issue is reproducible entirely via PHP CLI, making automated testing straightforward. ## Reproduction Details Reproduced: 2026-05-23T06:29:49.478Z Duration: 911 seconds Tool calls: 120 Turns: 98 Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00156 pruva-verify GHSA-5vpg-rj7q-qpw2 pruva-verify CVE-2026-39850 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00156&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00156/artifacts/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/advisories/GHSA-5vpg-rj7q-qpw2 - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-39850 - Source: https://github.com/yiisoft/yii2 ## Artifacts - repro/rca_report.md (analysis, 4080 bytes) - repro/reproduction_steps.sh (reproduction_script, 2356 bytes) - vuln_variant/rca_report.md (analysis, 5728 bytes) - vuln_variant/reproduction_steps.sh (reproduction_script, 5874 bytes) - bundle/context.json (other, 2871 bytes) - bundle/metadata.json (other, 645 bytes) - bundle/ticket.md (ticket, 3203 bytes) - repro/validation_verdict.json (other, 155 bytes) - vuln_variant/root_cause_equivalence.json (other, 776 bytes) - vuln_variant/patch_analysis.md (documentation, 3574 bytes) - vuln_variant/variant_manifest.json (other, 2440 bytes) - vuln_variant/validation_verdict.json (other, 1855 bytes) - vuln_variant/source_identity.json (other, 580 bytes) - logs/composer_2.0.55.log (log, 75 bytes) - logs/vulnerable_2.0.54.log (log, 129 bytes) - logs/fixed_2.0.55.log (log, 114 bytes) - logs/vuln_variant/variant2_vulnerable.log (log, 127 bytes) - logs/vuln_variant/variant1_vulnerable.log (log, 116 bytes) - logs/vuln_variant/variant2_fixed.log (log, 115 bytes) - logs/vuln_variant/variant_verdict.json (other, 311 bytes) - logs/vuln_variant/variant3_vulnerable.log (log, 123 bytes) - logs/vuln_variant/variant1_fixed.log (log, 104 bytes) - logs/vuln_variant/variant3_fixed.log (log, 107 bytes) - logs/vuln_variant/fixed_version.txt (other, 207 bytes) - logs/validation_verdict.json (other, 155 bytes) - logs/composer_2.0.54.log (log, 75 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00156 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00156/artifacts/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00156 ## 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://pruva.dev