Skip to content

Commit 109878b

Browse files
authored
Fix: CVE-2026-39850, Isolate internal variables in View::renderPhpFile() and ErrorHandler::renderFile() to prevent parameter collisions from overriding included file paths
1 parent f635d55 commit 109878b

5 files changed

Lines changed: 54 additions & 4 deletions

File tree

framework/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Yii Framework 2 Change Log
44
2.0.55 under development
55
------------------------
66

7+
- Bug: CVE-2026-39850, Isolate internal variables in `View::renderPhpFile()` and `ErrorHandler::renderFile()` to prevent parameter collisions from overriding included file paths (samdark)
78
- Bug #20705: Replace `$this` with `self` in generics in Psalm annotations (mspirkov)
89
- Bug #20715: Adjust `JSON` helper error message assertions for `PHP 8.6` compatibility in `JsonTest` class (terabytesoftw)
910
- Enh #20714: Allow overriding the `yii\grid\GridView`'s default `filterSelector`, allow using `Closure`s for `filterSelector` (chriscpty)

framework/base/View.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,14 @@ public function afterRender($viewFile, $params, &$output)
341341
public function renderPhpFile($_file_, $_params_ = [])
342342
{
343343
$_obInitialLevel_ = ob_get_level();
344+
$_renderer_ = function () {
345+
extract(func_get_arg(1), EXTR_OVERWRITE);
346+
require func_get_arg(0);
347+
};
344348
ob_start();
345349
ob_implicit_flush(false);
346-
extract($_params_, EXTR_OVERWRITE);
347350
try {
348-
require $_file_;
351+
call_user_func_array($_renderer_, [$_file_, $_params_]);
349352
return ob_get_clean();
350353
} catch (\Exception $e) {
351354
while (ob_get_level() > $_obInitialLevel_) {

framework/web/ErrorHandler.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,13 @@ public function renderFile($_file_, $_params_)
259259
{
260260
$_params_['handler'] = $this;
261261
if ($this->exception instanceof ErrorException || !Yii::$app->has('view')) {
262+
$_renderer_ = function () {
263+
extract(func_get_arg(1), EXTR_OVERWRITE);
264+
require Yii::getAlias(func_get_arg(0));
265+
};
262266
ob_start();
263267
ob_implicit_flush(false);
264-
extract($_params_, EXTR_OVERWRITE);
265-
require Yii::getAlias($_file_);
268+
call_user_func_array($_renderer_, [$_file_, $_params_]);
266269

267270
return ob_get_clean();
268271
}

tests/framework/base/ViewTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,19 @@ public function testRelativePathInView(): void
108108
$this->assertSame($subViewContent, $view->render('@testviews/base'));
109109
}
110110

111+
public function testRenderFileDoesNotAllowInternalFileOverride(): void
112+
{
113+
$view = new View();
114+
115+
$viewFile = $this->testViewPath . DIRECTORY_SEPARATOR . 'safe.php';
116+
file_put_contents($viewFile, '<?php echo "safe view";');
117+
118+
$secretFile = $this->testViewPath . DIRECTORY_SEPARATOR . 'secret.txt';
119+
file_put_contents($secretFile, 'secret data');
120+
121+
$this->assertSame('safe view', $view->renderFile($viewFile, ['_file_' => $secretFile]));
122+
}
123+
111124
public function testAfterRender(): void
112125
{
113126
$view = new View();

tests/framework/web/ErrorHandlerTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use Exception;
1212
use yii\BaseYii;
13+
use yii\base\ErrorException;
1314
use yii\web\Application;
1415
use Yii;
1516
use yii\web\NotFoundHttpException;
@@ -178,6 +179,28 @@ public function testHtmlEncodeWithUnicodeSequence(): void
178179

179180
$this->assertSame($expected, $handler->htmlEncode($text));
180181
}
182+
183+
public function testRenderFileDoesNotAllowInternalFileOverride(): void
184+
{
185+
$viewFile = tempnam(sys_get_temp_dir(), 'yii2-error-view-');
186+
$secretFile = tempnam(sys_get_temp_dir(), 'yii2-error-secret-');
187+
188+
file_put_contents($viewFile, '<?php echo "safe error view";');
189+
file_put_contents($secretFile, 'secret data');
190+
191+
try {
192+
/** @var ErrorHandler $handler */
193+
$handler = Yii::$app->getErrorHandler();
194+
195+
$this->assertSame(
196+
'safe error view',
197+
$handler->renderFileForException($viewFile, ['_file_' => $secretFile], new ErrorException('test'))
198+
);
199+
} finally {
200+
@unlink($viewFile);
201+
@unlink($secretFile);
202+
}
203+
}
181204
}
182205

183206
class ErrorHandler extends \yii\web\ErrorHandler
@@ -189,4 +212,11 @@ protected function shouldRenderSimpleHtml()
189212
{
190213
return false;
191214
}
215+
216+
public function renderFileForException($file, array $params, \Throwable $exception): string
217+
{
218+
$this->exception = $exception;
219+
220+
return $this->renderFile($file, $params);
221+
}
192222
}

0 commit comments

Comments
 (0)