Human
Machine
REPRO-2026-00156 HIGH
Verified
Yii2: local file inclusion via View::renderPhpFile extract() of caller-controlled params
yiisoft/yii2 (composer) May 23, 2026
What's the vulnerability?
yii\base\View::renderPhpFile($_file_, $_params_) calls
extract($_params_, EXTR_OVERWRITE) before the require $_file_ that
loads the template. Because EXTR_OVERWRITE is allowed to clobber existing
local variables, a _file_ key in the params array overwrites the local
$_file_ immediately before the require — redirecting the include to any
path on disk the PHP process can read.
This is local file inclusion: an attacker who controls (any subset of) the
params array passed to renderPhpFile can read arbitrary files, and execute
arbitrary PHP if the included file's contents are PHP.
Root Cause Analysis
# 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.
One Command
Verify with pruva-verify
Run the Pruva CLI to automatically fetch and execute the reproduction script.
pruva-verify REPRO-2026-00156 or
pruva-verify GHSA-5vpg-rj7q-qpw2 or
pruva-verify CVE-2026-39850 Install:
curl -fsSL https://pruva.dev/install.sh | sh Or Run Manually
1
Download the script
curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00156/artifacts/reproduction_steps.sh 2
Make executable
chmod +x reproduction_steps.sh 3
Run the script
./reproduction_steps.sh Run in a VM, container, or disposable environment. This exploits a real vulnerability.
How Pruva Reproduced This
Watch the AI agent's step-by-step process.
Loading session...
Artifacts
No artifacts available