From c19484fc9ad5cb9a62f8cca49bce42bef20f7595 Mon Sep 17 00:00:00 2001 From: otsuka Date: Wed, 22 Jul 2026 18:22:20 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E5=BD=A2=E5=BC=8F=E3=82=92zip=E3=81=97=E3=81=8B=E5=8F=97?= =?UTF-8?q?=E3=81=91=E5=85=A5=E3=82=8C=E3=81=AA=E3=81=84=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/baser-core/src/Service/PluginsService.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/baser-core/src/Service/PluginsService.php b/plugins/baser-core/src/Service/PluginsService.php index 5b3787cfb2..b42a291ec6 100644 --- a/plugins/baser-core/src/Service/PluginsService.php +++ b/plugins/baser-core/src/Service/PluginsService.php @@ -701,7 +701,9 @@ public function add(array $postData) $name = $postData['file']->getClientFileName(); $postData['file']->moveTo(TMP . $name); $zip = new BcZip(); - if (!$zip->extract(TMP . $name, TMP)) { + $finfo = new \finfo(FILEINFO_MIME_TYPE); + $mimeType = $finfo->file(TMP . $name); + if (!in_array($mimeType, ['application/zip', 'application/x-zip-compressed'], true) || !$zip->extract(TMP . $name, TMP)) { throw new BcException(__d('baser_core', 'アップロードしたZIPファイルの展開に失敗しました。')); } $srcDirName = $zip->topArchiveName; From 9254464155ee0010b04964ca8f065b6ce4770b80 Mon Sep 17 00:00:00 2001 From: otsuka Date: Mon, 27 Jul 2026 18:47:42 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[=20PluginsService.php=20]=20=E3=83=BBMIME?= =?UTF-8?q?=E4=B8=8D=E4=B8=80=E8=87=B4=E3=81=AE=E3=82=B1=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=81=A8=E3=80=81ZIP=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E5=B1=95=E9=96=8B=E3=81=AB=E5=A4=B1=E6=95=97=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=82=B1=E3=83=BC=E3=82=B9=E3=82=92=E5=88=86=E3=81=91?= =?UTF-8?q?=E3=82=8B=20=E3=83=BB=E4=BE=8B=E5=A4=96=E5=89=8D=E3=81=AF?= =?UTF-8?q?=EF=BC=91=E6=99=82=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4=EF=BC=88=EF=BC=89=20[=20PluginsServiceTest.p?= =?UTF-8?q?hp=20]=20=E3=83=BBMIME=E4=B8=8D=E4=B8=80=E8=87=B4=E3=81=AE?= =?UTF-8?q?=E9=9A=9B=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baser-core/src/Service/PluginsService.php | 11 +++++- .../TestCase/Service/PluginsServiceTest.php | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/plugins/baser-core/src/Service/PluginsService.php b/plugins/baser-core/src/Service/PluginsService.php index b42a291ec6..5988255cff 100644 --- a/plugins/baser-core/src/Service/PluginsService.php +++ b/plugins/baser-core/src/Service/PluginsService.php @@ -703,7 +703,16 @@ public function add(array $postData) $zip = new BcZip(); $finfo = new \finfo(FILEINFO_MIME_TYPE); $mimeType = $finfo->file(TMP . $name); - if (!in_array($mimeType, ['application/zip', 'application/x-zip-compressed'], true) || !$zip->extract(TMP . $name, TMP)) { + if (!in_array($mimeType, ['application/zip', 'application/x-zip-compressed'], true)) { + if (file_exists(TMP . $name)) { + unlink(TMP . $name); + } + throw new BcException(__d('baser_core', 'ZIPファイルをアップロードしてください。')); + } + if (!$zip->extract(TMP . $name, TMP)) { + if (file_exists(TMP . $name)) { + unlink(TMP . $name); + } throw new BcException(__d('baser_core', 'アップロードしたZIPファイルの展開に失敗しました。')); } $srcDirName = $zip->topArchiveName; diff --git a/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php b/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php index d21c1117c3..1f5b2b2b01 100644 --- a/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php +++ b/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php @@ -469,6 +469,41 @@ public function test_add() // $this->Plugins->add(["file" => $files]); } + /** + * 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(); + } + } + /** * test getAvailableCoreVersionInfo * @return void From ef247517419a5f1e172816272f11bbe2bcfd19c1 Mon Sep 17 00:00:00 2001 From: otsuka Date: Fri, 31 Jul 2026 12:36:16 +0900 Subject: [PATCH 3/4] =?UTF-8?q?test=5FaddRejectsNonZipFile()=20=E3=82=92?= =?UTF-8?q?=E3=82=AF=E3=83=A9=E3=82=B9=E3=81=AE=E6=9C=AB=E5=B0=BE=E3=81=AB?= =?UTF-8?q?=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TestCase/Service/PluginsServiceTest.php | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php b/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php index 1f5b2b2b01..79a35e390e 100644 --- a/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php +++ b/plugins/baser-core/tests/TestCase/Service/PluginsServiceTest.php @@ -469,41 +469,6 @@ public function test_add() // $this->Plugins->add(["file" => $files]); } - /** - * 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(); - } - } - /** * test getAvailableCoreVersionInfo * @return void @@ -870,4 +835,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(); + } + } } From cb1d54e985b26d1a11deea7a291a4dc197b00f60 Mon Sep 17 00:00:00 2001 From: ryuring Date: Wed, 26 Aug 2026 12:08:37 +0900 Subject: [PATCH 4/4] =?UTF-8?q?ZIP=E5=88=A4=E5=AE=9A=E3=81=AE=E3=83=AC?= =?UTF-8?q?=E3=83=93=E3=83=A5=E3=83=BC=E6=8C=87=E6=91=98=E3=82=92=E5=8F=8D?= =?UTF-8?q?=E6=98=A0=EF=BC=88ext-fileinfo=E5=AF=BE=E5=BF=9C=E3=83=BB?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E8=BF=BD=E5=8A=A0=E3=83=BB=E9=87=8D=E8=A4=87?= =?UTF-8?q?=E6=95=B4=E7=90=86=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ext-fileinfo が無効な環境で finfo クラスが存在せず致命的エラーになるため、 class_exists() でガードし、判定できない場合は展開時のエラーに委ねるようにした。 レンタルサーバー等で無効化されている可能性があるため、require ではなく suggest に記載して任意依存として扱う。 既存の BcBlog RssHelper も function_exists('mime_content_type') で 同様にガードしている。 - 「ZIPファイルをアップロードしてください。」を英語の翻訳ファイルに追加。 同じメソッド内の既存メッセージは登録済みのため揃えた。 - application/x-zip-compressed はブラウザが申告する MIME であり finfo が 返す値ではない旨をコメントで補足。誤解を避けるため残しつつ意図を明示した。 - 失敗時のテンポラリファイル削除が2箇所に重複していたため、try/catch で 1箇所にまとめた。 Co-Authored-By: Claude Opus 5 (1M context) --- composer.json | 1 + plugins/baser-core/composer.json | 3 ++ .../resources/locales/en/baser_core.po | 6 +++- .../baser-core/src/Service/PluginsService.php | 28 ++++++++++++------- 4 files changed, 27 insertions(+), 11 deletions(-) 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 940d81daf5..936729913e 100644 --- a/plugins/baser-core/src/Service/PluginsService.php +++ b/plugins/baser-core/src/Service/PluginsService.php @@ -721,20 +721,28 @@ public function add(array $postData) // パストラバーサル対策: クライアント提供のファイル名から basename() でディレクトリ要素を除去する $name = basename($postData['file']->getClientFileName()); $postData['file']->moveTo(TMP . $name); - $zip = new BcZip(); - $finfo = new \finfo(FILEINFO_MIME_TYPE); - $mimeType = $finfo->file(TMP . $name); - if (!in_array($mimeType, ['application/zip', 'application/x-zip-compressed'], true)) { - if (file_exists(TMP . $name)) { - unlink(TMP . $name); + 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ファイルをアップロードしてください。')); + } } - throw new BcException(__d('baser_core', 'ZIPファイルをアップロードしてください。')); - } - if (!$zip->extract(TMP . $name, TMP)) { + $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 new BcException(__d('baser_core', 'アップロードしたZIPファイルの展開に失敗しました。')); + throw $e; } $srcDirName = $zip->topArchiveName; $dstName = $srcName = Inflector::camelize($srcDirName);