diff --git a/composer.json b/composer.json index 74462babf7..66e0a56997 100644 --- a/composer.json +++ b/composer.json @@ -57,6 +57,7 @@ "baserproject/bc-widget-area": "5.3.0" }, "suggest": { + "ext-fileinfo": "Enables validating that an uploaded plugin/theme archive is really a ZIP file before extracting it.", "markstory/asset_compress": "An asset compression plugin which provides file concatenation and a flexible filter system for preprocessing and minification.", "dereuromark/cakephp-ide-helper": "After baking your code, this keeps your annotations in sync with the code evolving from there on for maximum IDE and PHPStan/Psalm compatibility.", "phpstan/phpstan": "PHPStan focuses on finding errors in your code without actually running it. It catches whole classes of bugs even before you write tests for the code.", diff --git a/plugins/baser-core/composer.json b/plugins/baser-core/composer.json index aaeaacbf14..a4f9a2f7bf 100644 --- a/plugins/baser-core/composer.json +++ b/plugins/baser-core/composer.json @@ -50,6 +50,9 @@ "vierge-noire/cakephp-test-suite-light": "~3.0.0", "ext-xdebug": "*" }, + "suggest": { + "ext-fileinfo": "Enables validating that an uploaded plugin/theme archive is really a ZIP file before extracting it." + }, "autoload": { "psr-4": { "BaserCore\\": "src", diff --git a/plugins/baser-core/resources/locales/en/baser_core.po b/plugins/baser-core/resources/locales/en/baser_core.po index eff899e5be..dedaca56bf 100644 --- a/plugins/baser-core/resources/locales/en/baser_core.po +++ b/plugins/baser-core/resources/locales/en/baser_core.po @@ -3396,7 +3396,11 @@ msgstr "Error occurred when processing. Please contact the Plugin’s developer. msgid "サーバに設定されているサイズ制限を超えています。" msgstr "The size is exceeding the size restriction of the server." -#: plugins/baser-core/src/Service/PluginsService.php:705 +#: plugins/baser-core/src/Service/PluginsService.php:733 +msgid "ZIPファイルをアップロードしてください。" +msgstr "Please upload a ZIP file." + +#: plugins/baser-core/src/Service/PluginsService.php:738 #: plugins/baser-core/src/Service/ThemesService.php:156 #: plugins/baser-core/src/Service/UtilitiesService.php:428 msgid "アップロードしたZIPファイルの展開に失敗しました。" diff --git a/plugins/baser-core/src/Service/PluginsService.php b/plugins/baser-core/src/Service/PluginsService.php index 9694962c61..936729913e 100644 --- a/plugins/baser-core/src/Service/PluginsService.php +++ b/plugins/baser-core/src/Service/PluginsService.php @@ -721,9 +721,28 @@ public function add(array $postData) // パストラバーサル対策: クライアント提供のファイル名から basename() でディレクトリ要素を除去する $name = basename($postData['file']->getClientFileName()); $postData['file']->moveTo(TMP . $name); - $zip = new BcZip(); - if (!$zip->extract(TMP . $name, TMP)) { - throw new BcException(__d('baser_core', 'アップロードしたZIPファイルの展開に失敗しました。')); + try { + // アップロードされたファイルが ZIP であるかを内容から判定する + // ext-fileinfo が無効な環境では判定をスキップし、展開時のエラーに委ねる + if (class_exists('finfo')) { + $finfo = new \finfo(FILEINFO_MIME_TYPE); + $mimeType = $finfo->file(TMP . $name); + // finfo はファイルの内容から判定するため通常は application/zip を返す + // application/x-zip-compressed は一部環境の libmagic に対する保険 + if (!in_array($mimeType, ['application/zip', 'application/x-zip-compressed'], true)) { + throw new BcException(__d('baser_core', 'ZIPファイルをアップロードしてください。')); + } + } + $zip = new BcZip(); + if (!$zip->extract(TMP . $name, TMP)) { + throw new BcException(__d('baser_core', 'アップロードしたZIPファイルの展開に失敗しました。')); + } + } catch (BcException $e) { + // 展開できなかったテンポラリファイルを残さない + if (file_exists(TMP . $name)) { + unlink(TMP . $name); + } + throw $e; } $srcDirName = $zip->topArchiveName; $dstName = $srcName = Inflector::camelize($srcDirName); diff --git a/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php b/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php index da7c7f9910..d2ac4c33ab 100644 --- a/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php +++ b/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php @@ -877,4 +877,39 @@ public function test_getCoreUpdate_vulnerability() $this->assertFalse(file_exists($rceFile), 'getCoreUpdate でOSコマンドインジェクションが発生しました'); } + + /** + * test add ZIP以外のファイルをアップロードした場合 + * @return void + */ + public function test_addRejectsNonZipFile() + { + $zipSrcPath = TMP . 'zip' . DS; + $folder = new BcFolder($zipSrcPath); + $folder->create(); + //架空のプラグイン名を指定して、ZIP以外のファイルを作成 + $plugin = 'NotZipPlugin'; + $testFile = $zipSrcPath . $plugin . '.zip'; + file_put_contents($testFile, 'This is not a zip file.'); + $size = filesize($testFile); + + $this->setUploadFileToRequest('file', $testFile); + $files = new UploadedFile( + $testFile, + $size, + UPLOAD_ERR_OK, + $plugin . '.zip', + 'text/plain' + ); + + $this->expectException("BaserCore\Error\BcException"); + $this->expectExceptionMessage("ZIPファイルをアップロードしてください。"); + try { + $this->Plugins->add(["file" => $files]); + } finally { + // アップロードされた一時ファイルが削除されていること + $this->assertFileDoesNotExist(TMP . $plugin . '.zip'); + $folder->delete(); + } + } }