CVE-2021-41243: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (CWE-78)
value: There is a Potential Zip Slip Vulnerability and OS Command Injection Vulnerability on the management system of baserCMS. Users with permissions to upload files may upload crafted zip files which may execute arbitrary commands on the host operating system. This is a vulnerability that needs to be addressed when the management system is used by an unspecified number of users. If you are eligible, please update to the new version as soon as possible.
CWE description: The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
CVSS v3 base score: 8.8
Published: 2021-11-26T18:15Z
Repository: https://github.com/baserproject/basercms
Language: PHP
Files changed: app/webroot/theme/admin-third/Installations/admin/step2.php, app/webroot/theme/admin-third/Tools/admin/log.php, lib/Baser/Config/setting.php, lib/Baser/Controller/Component/BcManagerComponent.php, lib/Baser/Controller/PagesController.php, lib/Baser/Controller/ThemesController.php, lib/Baser/Controller/ToolsController.php, lib/Baser/Lib/BcZip.php
Commit date: 2021-11-25 15:17:52 +0900
Commit message: Merge pull request from GHSA-7rpc-9m88-cf9w

* 固定ページプレビュー実行時に入力内容検証を追加

* SimpleZipの利用を停止しZipArchiveの利用に切り替え

* 不要コード除去

* BcZip利用時、ZipArchiveをopenしていない場合エラーログが出力される問題を改善
Diff: diff --git a/app/webroot/theme/admin-third/Installations/admin/step2.php b/app/webroot/theme/admin-third/Installations/admin/step2.php
index ec2b6c4129..545ccae1ac 100755
--- a/app/webroot/theme/admin-third/Installations/admin/step2.php
+++ b/app/webroot/theme/admin-third/Installations/admin/step2.php
@@ -386,11 +386,29 @@
 					<li><?php echo __d('baser', '/baser/config/safemode/db/ 内の全て　→　/app/db/ （SQLite を利用する場合）') ?></li>
 					<li><?php echo __d('baser', '/baser/config/theme/ 内の全て　→　/app/webroot/theme/') ?></li>
 				</ul>
 			<?php endif ?>
 		</div>
+		
+		<div class="panel-box bca-panel-box corner10">
+			<h3 class="bca-panel-box__title"><?php echo __d('baser', '拡張モジュール') ?></h3>
+			<ul class="section">
+				<li class='<?php if ($zipOk) echo 'check'; else echo 'failed'; ?>'>
+					<?php echo __d('baser', 'Zip') ?><br/>
+					<div class="check-result">
+						<?php if ($zipOk): ?>
+							<?php echo __d('baser', '利用可') ?>
+						<?php else: ?>
+							<?php echo __d('baser', '利用不可') ?><br/>
+							<small><?php echo __d('baser', 'テーマなどのzipダウンロードが制限されます。') ?></small>
+						<?php endif ?>
+					</div>
+				</li>
+			</ul>
+		</div>
 	</div>
+		
 
 	<form action="<?php echo $this->request->base ?>/installations/step2" method="post" id="checkenv">
 		<?php echo $this->BcForm->hidden('clicked') ?>
 		<div class="submit bca-actions">
 			<?php echo $this->BcForm->button(__d('baser', '再チェック'), ['class' => 'btn-orange button bca-btn bca-actions__item', 'id' => 'btncheckagain']) ?>
diff --git a/app/webroot/theme/admin-third/Tools/admin/log.php b/app/webroot/theme/admin-third/Tools/admin/log.php
index 4ea9769f6e..689de5a12d 100755
--- a/app/webroot/theme/admin-third/Tools/admin/log.php
+++ b/app/webroot/theme/admin-third/Tools/admin/log.php
@@ -14,15 +14,17 @@
  * [ADMIN] データメンテナンス
  */
 ?>
 
 
+<?php if ($zipEnable): ?>
 <div class="section bca-main__section">
 	<h2 class="bca-main__heading" data-bca-heading-size="lg"><?php echo __d('baser', 'ログ(エラーログ)の取得') ?></h2>
 	<p class="bca-main__text"><?php echo __d('baser', 'ログ(エラーログ)をPCにダウンロードします。') ?></p>
 	<p class="bca-main__text"><?php $this->BcBaser->link(__d('baser', 'ダウンロード'), ['download'], ['class' => 'button-small bca-btn', 'data-bca-btn-type' => 'download']) ?> </p>
 </div>
+<?php endif; ?>
 
 <div class="section bca-main__section">
 	<h2 class="bca-main__heading" data-bca-heading-size="lg"><?php echo __d('baser', 'エラーログの削除') ?></h2>
 
 	<p class="bca-main__text"><?php echo __d('baser', 'エラーログを削除します。サーバの容量を圧迫する場合時などに利用ください。') ?><br>
diff --git a/lib/Baser/Config/setting.php b/lib/Baser/Config/setting.php
index edb4632931..778e60b9fe 100755
--- a/lib/Baser/Config/setting.php
+++ b/lib/Baser/Config/setting.php
@@ -476,10 +476,20 @@ $config['BcApp'] = [
 	// 	- false：ブラウザ
 	// 	- true：サイト設定
 	'systemMessageLangFromSiteSetting' => true
 ];
 
+/**
+ * Zipモジュールなし
+ */
+if (!extension_loaded('zip')) {
+	unset($config['BcApp']['adminNavigation']['Systems']['Theme']['menus']['ThemesDownload']);
+	unset($config['BcApp']['adminNavigation']['Systems']['Theme']['menus']['ThemesDownloadDefaultDataPattern']);
+	unset($config['BcApp']['adminNavigation']['Systems']['Tools']['menus']['ToolsMaintenance']);
+}
+
+
 /**
  * システム要件
  */
 $config['BcRequire'] = [
 	'phpVersion' => "5.4.0",
diff --git a/lib/Baser/Controller/Component/BcManagerComponent.php b/lib/Baser/Controller/Component/BcManagerComponent.php
index c7e8451e03..96f65763d1 100644
--- a/lib/Baser/Controller/Component/BcManagerComponent.php
+++ b/lib/Baser/Controller/Component/BcManagerComponent.php
@@ -1385,17 +1385,19 @@ class BcManagerComponent extends Component
 			'dbDirWritable' => is_writable(APP . 'db'),
 			'phpActualVersion' => preg_replace('/[a-z-]/', '', phpversion()),
 			'phpGd' => extension_loaded('gd'),
 			'phpPdo' => extension_loaded('pdo'),
 			'phpXml' => extension_loaded('xml'),
+			'phpZip' => extension_loaded('zip'),
 			'apacheRewrite' => $rewriteInstalled,
 		];
 		$check = [
 			'encodingOk' => (preg_match('/UTF-8/i', $status['encoding'])? true : false),
 			'gdOk' => $status['phpGd'],
 			'pdoOk' => $status['phpPdo'],
 			'xmlOk' => $status['phpXml'],
+			'zipOk' => $status['phpZip'],
 			'phpVersionOk' => version_compare(preg_replace('/[a-z-]/', '', $status['phpVersion']), Configure::read('BcRequire.phpVersion'), '>='),
 			'phpMemoryOk' => ((($status['phpMemory'] >= Configure::read('BcRequire.phpMemory')) || $status['phpMemory'] == -1) === true)
 		];
 
 		if (!$status['configDirWritable']) {
diff --git a/lib/Baser/Controller/PagesController.php b/lib/Baser/Controller/PagesController.php
index d99b25f71c..f0944c7469 100644
--- a/lib/Baser/Controller/PagesController.php
+++ b/lib/Baser/Controller/PagesController.php
@@ -408,12 +408,20 @@ class PagesController extends AppController
 		$previewCreated = false;
 		if ($this->request->data) {
 			// POSTパラメータのコードに含まれるscriptタグをそのままHTMLに出力するとブラウザによりXSSと判定される
 			// 一度データをセッションに退避する
 			if ($this->BcContents->preview === 'default') {
+				// 入力validation
+				$check = ['content_tmp' => $this->request->data['Page']['contents_tmp']];
+				if (!$this->Page->containsScript($check)) {
+					$this->BcMessage->setError(__d('baser', '本稿欄でスクリプトの入力は許可されていません。'));
+					$this->notFound();
+				}
+				
 				$sessionKey = __CLASS__ . '_preview_default_' . $this->request->data['Content']['entity_id'];
 				$this->request->data = $this->Content->saveTmpFiles($this->request->data, mt_rand(0, 99999999));
+				
 				$this->Session->write($sessionKey, $this->request->data);
 				$query = [];
 				if ($this->request->query) {
 					foreach($this->request->query as $key => $value) {
 						$query[] = $key . '=' . $value;
@@ -429,10 +437,17 @@ class PagesController extends AppController
 				$this->redirect($redirectUrl);
 				return;
 			}
 
 			if ($this->BcContents->preview === 'draft') {
+				// 入力validation
+				$check = ['content_tmp' => $this->request->data['Page']['contents_tmp']];
+				if (!$this->Page->containsScript($check)) {
+					$this->BcMessage->setError(__d('baser', '本稿欄でスクリプトの入力は許可されていません。'));
+					$this->notFound();
+				}
+				
 				$this->request->data = $this->Content->saveTmpFiles($this->request->data, mt_rand(0, 99999999));
 				$this->request->params['Content']['eyecatch'] = $this->request->data['Content']['eyecatch'];
 
 				$uuid = $this->_createPreviewTemplate($this->request->data);
 				$this->set('previewTemplate', TMP . 'pages_preview_' . $uuid . $this->ext);
diff --git a/lib/Baser/Controller/ThemesController.php b/lib/Baser/Controller/ThemesController.php
index 2adad4461f..f8b3022e1f 100644
--- a/lib/Baser/Controller/ThemesController.php
+++ b/lib/Baser/Controller/ThemesController.php
@@ -8,11 +8,11 @@
  * @package         Baser.Controller
  * @since           baserCMS v 0.1.0
  * @license         https://basercms.net/license/index.html
  */
 
-App::uses('Simplezip', 'Vendor');
+App::uses('BcZip', 'Lib');
 
 /**
  * Class ThemesController
  *
  * @property Page $Page
@@ -93,11 +93,10 @@ class ThemesController extends AppController
 			return;
 		}
 
 		$name = $this->request->data['Theme']['file']['name'];
 		move_uploaded_file($this->request->data['Theme']['file']['tmp_name'], TMP . $name);
-		App::uses('BcZip', 'Lib');
 		$BcZip = new BcZip();
 		if (!$BcZip->extract(TMP . $name, BASER_THEMES)) {
 			$msg = __d('baser', 'アップロードしたZIPファイルの展開に失敗しました。');
 			$msg .= "\n" . $BcZip->error;
 			$this->BcMessage->setError($msg);
@@ -571,15 +570,21 @@ class ThemesController extends AppController
 	/**
 	 * 初期データセットをダウンロードする
 	 */
 	public function admin_download_default_data_pattern()
 	{
+		if (!extension_loaded('zip')) {
+			$this->notFound();
+		}
+		
+		$this->autoRender = false;
 		set_time_limit(0);
 		ini_set('memory_limit', -1);
 
 		/* コアのCSVを生成 */
 		$tmpDir = TMP . 'csv' . DS;
+		$distPath = TMP . 'default.zip';
 		$Folder = new Folder();
 		$Folder->create($tmpDir);
 		emptyFolder($tmpDir);
 		clearAllCache();
 		$excludes = ['plugins', 'dblogs', 'users', 'favorites'];
@@ -602,17 +607,24 @@ class ThemesController extends AppController
 			}
 			$records[] = '"' . implode('","', $record) . '"';
 		}
 		ftruncate($fp, 0);
 		fwrite($fp, implode("\n", $records));
+		
 		/* ZIPに固めてダウンロード */
-		$fileName = 'default';
-		$Simplezip = new Simplezip();
-		$Simplezip->addFolder($tmpDir);
-		$Simplezip->download($fileName);
+		$bcZip = new BcZip();
+		$bcZip->create($tmpDir, $distPath);
+		
+		header("Cache-Control: no-store");
+		header("Content-Type: application/zip");
+		header("Content-Disposition: attachment; filename=" . basename($distPath) . ";");
+		header("Content-Length: " . filesize($distPath));
+		while (ob_get_level()) { ob_end_clean(); }
+		echo readfile($distPath);
+		
 		emptyFolder($tmpDir);
-		exit();
+		unlink($distPath);
 	}
 
 	/**
 	 * CSVファイルを書きだす
 	 *
@@ -669,21 +681,35 @@ class ThemesController extends AppController
 	/**
 	 * ダウンロード
 	 */
 	public function admin_download()
 	{
+		if (!extension_loaded('zip')) {
+			$this->notFound();
+		}
+		
 		$this->autoRender = false;
 		$tmpDir = TMP . 'theme' . DS;
+		$orgPath = BASER_THEMES . $this->siteConfigs['theme'] . DS;
+		$sourcePath = $tmpDir . $this->siteConfigs['theme'];
+		$distPath = $sourcePath . '.zip';
+		
 		$Folder = new Folder();
 		$Folder->create($tmpDir);
-		$path = BASER_THEMES . $this->siteConfigs['theme'] . DS;
 		$Folder->copy([
-			'from' => $path,
-			'to' => $tmpDir . $this->siteConfigs['theme'],
+			'from' => $orgPath,
+			'to' => $sourcePath,
 			'chmod' => 0777
 		]);
-		$Simplezip = new Simplezip();
-		$Simplezip->addFolder($tmpDir);
-		$Simplezip->download($this->siteConfigs['theme']);
+		$bcZip = new BcZip();
+		$bcZip->create($sourcePath, $distPath);
+		
+		header("Cache-Control: no-store");
+		header("Content-Type: application/zip");
+		header("Content-Disposition: attachment; filename=" . basename($distPath) . ";");
+		header("Content-Length: " . filesize($distPath));
+		while (ob_get_level()) { ob_end_clean(); }
+		echo readfile($distPath);
+		
 		$Folder->delete($tmpDir);
 	}
 }
diff --git a/lib/Baser/Controller/ToolsController.php b/lib/Baser/Controller/ToolsController.php
index 834118549b..ba97f4b099 100644
--- a/lib/Baser/Controller/ToolsController.php
+++ b/lib/Baser/Controller/ToolsController.php
@@ -8,11 +8,11 @@
  * @package         Baser.Controller
  * @since           baserCMS v 0.1.0
  * @license         https://basercms.net/license/index.html
  */
 
-App::uses('Simplezip', 'Vendor');
+App::uses('BcZip', 'Lib');
 
 /**
  * Class ToolsController
  *
  * ツールコントローラー
@@ -91,13 +91,17 @@ class ToolsController extends AppController
 	 * @return void
 	 */
 	public function admin_maintenance($mode = '')
 	{
 		$this->_checkReferer();
+		$zipEnable = extension_loaded('zip');
+		if (!$zipEnable) {
+			$this->notFound();
+		}
+		
 		switch($mode) {
 			case 'backup':
-				set_time_limit(0);
 				$this->_backupDb($this->request->query['backup_encoding']);
 				break;
 			case 'restore':
 				set_time_limit(0);
 				$messages = [];
@@ -153,14 +157,14 @@ class ToolsController extends AppController
 		$targetPath = $tmpPath . $data['Tool']['backup']['name'];
 
 		if (!move_uploaded_file($data['Tool']['backup']['tmp_name'], $targetPath)) {
 			return false;
 		}
-
+		
 		/* ZIPファイルを解凍する */
-		$Simplezip = new Simplezip();
-		if (!$Simplezip->unzip($targetPath, $tmpPath)) {
+		$bcZip = new BcZip();
+		if (!$bcZip->extract($targetPath, $tmpPath)) {
 			return false;
 		}
 		@unlink($targetPath);
 
 		$result = true;
@@ -253,29 +257,41 @@ class ToolsController extends AppController
 	 *
 	 * @return void
 	 */
 	protected function _backupDb($encoding)
 	{
-		$tmpDir = TMP . 'schemas' . DS;
+		set_time_limit(0);
+		
 		$version = str_replace(' ', '_', $this->getBaserVersion());
+		$tmpDir = TMP . 'schemas' . DS;
+		$distPath = TMP . 'baserbackup_' . $version . '_' . date('Ymd_His') . '.zip';
+		
 		$this->_resetTmpSchemaFolder();
 		clearAllCache();
 		$this->_writeBackup($tmpDir . 'core' . DS, '', $encoding);
 		$Plugin = ClassRegistry::init('Plugin');
 		$plugins = $Plugin->find('all');
 		if ($plugins) {
 			foreach($plugins as $plugin) {
 				$this->_writeBackup($tmpDir . 'plugin' . DS, $plugin['Plugin']['name'], $encoding);
 			}
 		}
+		
 		// ZIP圧縮して出力
-		$fileName = 'baserbackup_' . $version . '_' . date('Ymd_His');
-		$Simplezip = new Simplezip();
-		$Simplezip->addFolder($tmpDir);
-		$Simplezip->download($fileName);
+		$bcZip = new BcZip();
+		$bcZip->create($tmpDir, $distPath);
+		
+		header("Cache-Control: no-store");
+		header("Content-Type: application/zip");
+		header("Content-Disposition: attachment; filename=" . basename($distPath) . ";");
+		header("Content-Length: " . filesize($distPath));
+		while (ob_get_level()) { ob_end_clean(); }
+		echo readfile($distPath);
+		
+		unlink($distPath);
 		$this->_resetTmpSchemaFolder();
-		exit();
+		return true;
 	}
 
 	/**
 	 * バックアップファイルを書きだす
 	 *
@@ -310,10 +326,11 @@ class ToolsController extends AppController
 	 * @return void
 	 */
 	public function admin_write_schema()
 	{
 		$path = TMP . 'schemas' . DS;
+		$distPath = TMP . 'schemas.zip';
 
 		/* 表示設定 */
 		$this->pageTitle = __d('baser', 'スキーマファイル生成');
 		$this->help = 'tools_write_schema';
 
@@ -334,14 +351,30 @@ class ToolsController extends AppController
 		if (!$this->Tool->writeSchema($this->request->data, $path)) {
 			$this->BcMessage->setError(__d('baser', 'スキーマファイルの生成に失敗しました。'));
 			return;
 		}
 
-		$Simplezip = new Simplezip();
-		$Simplezip->addFolder($path);
-		$Simplezip->download('schemas');
-		exit();
+		// ZIP圧縮して出力
+		set_time_limit(0);
+		$this->autoRender = false;
+		
+		// 不要ディレクトリの削除
+		rmdir($path . 'core');
+		rmdir($path . 'plugin');
+		
+		$bcZip = new BcZip();
+		$bcZip->create($path, $distPath);
+		
+		header("Cache-Control: no-store");
+		header("Content-Type: application/zip");
+		header("Content-Disposition: attachment; filename=" . basename($distPath) . ";");
+		header("Content-Length: " . filesize($distPath));
+		while (ob_get_level()) { ob_end_clean(); }
+		echo readfile($distPath);
+		
+		unlink($distPath);
+		return true;
 	}
 
 	/**
 	 * スキーマファイルを読み込みテーブルを生成する
 	 *
@@ -399,16 +432,20 @@ class ToolsController extends AppController
 	 * @param string $mode
 	 * @return void
 	 */
 	public function admin_log($mode = '')
 	{
+		$zipEnable = extension_loaded('zip');
 		$errorLogPath = TMP . 'logs' . DS . 'error.log';
 		switch($mode) {
 			case 'download':
-				set_time_limit(0);
+				if (!$zipEnable) {
+					$this->notFound();
+				}
+				$this->autoRender = false;
 				if ($this->_downloadErrorLog()) {
-					exit();
+					return;
 				}
 				$this->BcMessage->setInfo('エラーログが存在しません。');
 				$this->redirect(['action' => 'log']);
 				break;
 			case 'delete':
@@ -439,31 +476,42 @@ class ToolsController extends AppController
 			$fileSize = filesize($errorLogPath);
 		}
 
 		$this->pageTitle = __d('baser', 'データメンテナンス');
 		$this->help = 'tools_log';
+		$this->set('zipEnable', $zipEnable);
 		$this->set('fileSize', $fileSize);
 	}
 
 	/**
 	 * ログフォルダを圧縮ダウンロードする
 	 *
 	 * @return bool
 	 */
 	protected function _downloadErrorLog()
 	{
+		set_time_limit(0);
+		
 		$tmpDir = TMP . 'logs' . DS;
+		$distPath = TMP . 'basercms_logs_' . date('Ymd_His') . '.zip';
 		$Folder = new Folder($tmpDir);
 		$files = $Folder->read(true, true, false);
 		if (count($files[0]) === 0 && count($files[1]) === 0) {
 			return false;
 		}
 		// ZIP圧縮して出力
-		$fileName = 'basercms_logs_' . date('Ymd_His');
-		$Simplezip = new Simplezip();
-		$Simplezip->addFolder($tmpDir);
-		$Simplezip->download($fileName);
+		$bcZip = new BcZip();
+		$bcZip->create($tmpDir, $distPath);
+		
+		header("Cache-Control: no-store");
+		header("Content-Type: application/zip");
+		header("Content-Disposition: attachment; filename=" . basename($distPath) . ";");
+		header("Content-Length: " . filesize($distPath));
+		while (ob_get_level()) { ob_end_clean(); }
+		echo readfile($distPath);
+		
+		unlink($distPath);
 		return true;
 	}
 
 	/**
 	 * 管理システム用アセットファイルを削除する
diff --git a/lib/Baser/Lib/BcZip.php b/lib/Baser/Lib/BcZip.php
index e339219358..f54fdbbde5 100644
--- a/lib/Baser/Lib/BcZip.php
+++ b/lib/Baser/Lib/BcZip.php
@@ -82,10 +82,11 @@ class BcZip
 	{
 		if ($this->Zip->open($source) === true && $this->Zip->extractTo($target)) {
 			$archivePath = $this->Zip->getNameIndex(0);
 			$archivePathAry = explode(DS, $archivePath);
 			$this->topArchiveName = $archivePathAry[0];
+			$this->Zip->close();
 			return true;
 		} else {
 			return false;
 		}
 	}
@@ -118,20 +119,10 @@ class BcZip
 			$this->error = $errs;
 			return false;
 		}
 	}
 
-	/**
-	 * Destruct
-	 */
-	public function __destruct()
-	{
-		if (class_exists('ZipArchive')) {
-			$this->Zip->close();
-		}
-	}
-
 	/**
 	 * CUI 向けにパスをエスケープする
 	 *
 	 * @param $path
 	 * @return string
@@ -143,6 +134,48 @@ class BcZip
 			$pathAry[$key] = escapeshellarg($value);
 		}
 		return implode(DS, $pathAry);
 	}
 
+	/**
+	 * zip生成
+	 *
+	 * @param string $sorce 元データ
+	 * @param string $dist 出力先
+	 * @return void
+	 */
+    public function create($sorce, $dist)
+    {
+        $za = new \ZipArchive();
+        $za->open($dist, \ZIPARCHIVE::CREATE);
+        $this->zipSub($za, $sorce);
+        $za->close();
+    }
+
+	/**
+	 * 再帰的にzip生成対象ファイルを追加する
+	 *
+	 * @param ZipArchive $za
+	 * @param string $path
+	 * @param string $parentPath
+	 * @return void
+	 */
+    private function zipSub($za, $path, $parentPath = '')
+    {
+        $dh = opendir($path);
+        while (($entry = readdir($dh)) !== false) {
+            if ($entry == '.' || $entry == '..') {
+            } else {
+                $localPath = $parentPath . $entry;
+                $fullpath = $path . DS . $entry;
+                if (is_file($fullpath)) {
+                    $za->addFile($fullpath, $localPath);
+                } else if (is_dir($fullpath)) {
+                    $za->addEmptyDir($localPath);
+                    $this->zipSub($za, $fullpath, $localPath . DS);
+                }
+            }
+        }
+        closedir($dh);
+    }
+
 }
diff --git a/lib/Baser/Vendor/Simplezip.php b/lib/Baser/Vendor/Simplezip.php
deleted file mode 100644
index 2827014cd2..0000000000
--- a/lib/Baser/Vendor/Simplezip.php
+++ /dev/null
@@ -1,499 +0,0 @@
-<?php
-/**
- * Simplezip
- *
- * baserCMS :  Based Website Development Project <https://basercms.net>
- * Copyright (c) baserCMS Users Community <https://basercms.net/community/>
- *
- * @copyright       Copyright (c) baserCMS Users Community
- * @link            https://basercms.net baserCMS Project
- * @package         Baser.Vendor
- * @since           baserCMS v 3.0.0
- * @license         https://basercms.net/license/index.html
- */
-
-/**
- * シンプルなZIP利用クラス
- *
- * CAUTION 現在、Macで圧縮したZipファイルに対応していない
- */
-class Simplezip
-{
-
-	/**
-	 * 圧縮データ
-	 *
-	 * @var        array
-	 * @access    public
-	 */
-	var $compressedData = [];
-
-	/**
-	 * Central Directory
-	 *
-	 * @var        array
-	 * @access    public
-	 */
-	var $centralDirectory = [];
-
-	/**
-	 * End Of Central Directory Record
-	 *
-	 * @var        string
-	 * @access    public
-	 */
-	var $endOfCentralDirectory = "\x50\x4b\x05\x06\x00\x00\x00\x00";
-
-	/**
-	 * オフセット
-	 *
-	 * @var        int
-	 * @access    public
-	 */
-	var $oldOffset = 0;
-
-	/**
-	 * エントリ（解凍用）
-	 *
-	 * @var        array
-	 * @access    public
-	 */
-	var $entries = [];
-
-	/**
-	 * Get Hexd Time
-	 *
-	 * @param int $time Unix timestamp
-	 * @return    hex    the date formated as a ZIP date
-	 * @access    public
-	 */
-	function getMTime($time)
-	{
-		$mtime = ($time !== null? getdate($time) : getdate());
-		$mtime = preg_replace(
-			"/(..){1}(..){1}(..){1}(..){1}/",
-			"\\x\\4\\x\\3\\x\\2\\x\\1",
-			dechex(($mtime['year'] - 1980 << 25) |
-				($mtime['mon'] << 21) |
-				($mtime['mday'] << 16) |
-				($mtime['hours'] << 11) |
-				($mtime['minutes'] << 5) |
-				($mtime['seconds'] >> 1)));
-		eval('$mtime = "' . $mtime . '";');
-		return $mtime;
-	}
-
-	/**
-	 * フォルダを追加する
-	 *
-	 * @param string $directory
-	 * @param string $put_int 追加対象位置
-	 */
-	function addFolder($directory, $put_into = '')
-	{
-
-		$handle = opendir($directory);
-		if ($handle) {
-			while(false !== ($file = readdir($handle))) {
-				if (is_file($directory . $file)) {
-					$fp = fopen($directory . $file, 'rb');
-					$fileContents = fread($fp, filesize($directory . $file));
-					fclose($fp);
-					$this->addFile($fileContents, $put_into . $file, filemtime($directory . $file));
-				} elseif ($file != '.' and $file != '..' and is_dir($directory . $file)) {
-					$this->addFolder($directory . $file . DS, $put_into . $file . DS);
-				}
-			}
-		}
-
-		closedir($handle);
-
-	}
-
-	/**
-	 * 圧縮対象データを追加
-	 *
-	 * @param binary $data
-	 * @param string $directoryName
-	 * @param int $time
-	 * @access    public
-	 */
-	function addFile($data, $directoryName, $time = 0)
-	{
-
-		$directoryName = str_replace("\\", "/", $directoryName);
-		$hexdtime = $this->getMTime($time);
-
-		$feedArrayRow = "\x50\x4b\x03\x04";
-		$feedArrayRow .= "\x14\x00";
-		$feedArrayRow .= "\x00\x00";
-		$feedArrayRow .= "\x08\x00";
-		$feedArrayRow .= $hexdtime;
-
-		$uncompressedLength = strlen($data);
-		$compression = crc32($data);
-		$gzCompressedData = gzcompress($data);
-		$gzCompressedData = substr($gzCompressedData, 2, strlen($gzCompressedData) - 6);
-		$compressedLength = strlen($gzCompressedData);
-
-		$feedArrayRow .= pack("V", $compression);
-		$feedArrayRow .= pack("V", $compressedLength);
-		$feedArrayRow .= pack("V", $uncompressedLength);
-		$feedArrayRow .= pack("v", strlen($directoryName));
-		$feedArrayRow .= pack("v", 0);
-		$feedArrayRow .= $directoryName;
-
-		$feedArrayRow .= $gzCompressedData;
-
-		$this->compressedData[] = $feedArrayRow;
-
-		$newOffset = strlen($feedArrayRow);
-
-		$addCentralRecord = "\x50\x4b\x01\x02";
-		$addCentralRecord .= "\x00\x00";
-		$addCentralRecord .= "\x14\x00";
-		$addCentralRecord .= "\x00\x00";
-		$addCentralRecord .= "\x08\x00";
-		$addCentralRecord .= $hexdtime;
-		$addCentralRecord .= pack("V", $compression);
-		$addCentralRecord .= pack("V", $compressedLength);
-		$addCentralRecord .= pack("V", $uncompressedLength);
-		$addCentralRecord .= pack("v", strlen($directoryName));
-		$addCentralRecord .= pack("v", 0);
-		$addCentralRecord .= pack("v", 0);
-		$addCentralRecord .= pack("v", 0);
-		$addCentralRecord .= pack("v", 0);
-		$addCentralRecord .= pack("V", 32);
-		$addCentralRecord .= pack("V", $this->oldOffset);
-		$addCentralRecord .= $directoryName;
-
-		$this->oldOffset += $newOffset;
-		$this->centralDirectory[] = $addCentralRecord;
-
-	}
-
-	/**
-	 * Adds "file" to archive
-	 *
-	 * @param string   file contents
-	 * @param string   name of the file in the archive (may contains the path)
-	 * @param integer  the current timestamp
-	 */
-	function unix2DosTime($unixtime = 0)
-	{
-		$timearray = ($unixtime == 0)? getdate() : getdate($unixtime);
-
-		if ($timearray['year'] < 1980) {
-			$timearray['year'] = 1980;
-			$timearray['mon'] = 1;
-			$timearray['mday'] = 1;
-			$timearray['hours'] = 0;
-			$timearray['minutes'] = 0;
-			$timearray['seconds'] = 0;
-		} // end if
-
-		return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) |
-			($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
-	} // end of the 'unix2DosTime()' method
-
-	/**
-	 * addFile2
-	 *
-	 * @param mixed $data
-	 * @param string $name
-	 * @param int $time
-	 */
-	function addFile2($data, $name, $time = 0)
-	{
-		$name = str_replace('\\', '/', $name);
-
-		$dtime = dechex($this->unix2DosTime($time));
-		$hexdtime = '\x' . $dtime[6] . $dtime[7]
-			. '\x' . $dtime[4] . $dtime[5]
-			. '\x' . $dtime[2] . $dtime[3]
-			. '\x' . $dtime[0] . $dtime[1];
-		eval('$hexdtime = "' . $hexdtime . '";');
-
-		$fr = "\x50\x4b\x03\x04";
-		$fr .= "\x14\x00";            // ver needed to extract
-		$fr .= "\x00\x00";            // gen purpose bit flag
-		$fr .= "\x08\x00";            // compression method
-		$fr .= $hexdtime;             // last mod time and date
-
-		// "local file header" segment
-		$unc_len = strlen($data);
-		$crc = crc32($data);
-		$zdata = gzcompress($data);
-		$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug
-		$c_len = strlen($zdata);
-		$fr .= pack('V', $crc);             // crc32
-		$fr .= pack('V', $c_len);           // compressed filesize
-		$fr .= pack('V', $unc_len);         // uncompressed filesize
-		$fr .= pack('v', strlen($name));    // length of filename
-		$fr .= pack('v', 0);                // extra field length
-		$fr .= $name;
-
-		// "file data" segment
-		$fr .= $zdata;
-
-		// "data descriptor" segment (optional but necessary if archive is not
-		// served as file)
-		// nijel(2004-10-19): this seems not to be needed at all and causes
-		// problems in some cases (bug #1037737)
-		//$fr .= pack('V', $crc);                 // crc32
-		//$fr .= pack('V', $c_len);               // compressed filesize
-		//$fr .= pack('V', $unc_len);             // uncompressed filesize
-
-		// add this entry to array
-		$this->compressedData[] = $fr;
-
-		// now add to central directory record
-		$cdrec = "\x50\x4b\x01\x02";
-		$cdrec .= "\x00\x00";                // version made by
-		$cdrec .= "\x14\x00";                // version needed to extract
-		$cdrec .= "\x00\x00";                // gen purpose bit flag
-		$cdrec .= "\x08\x00";                // compression method
-		$cdrec .= $hexdtime;                 // last mod time & date
-		$cdrec .= pack('V', $crc);           // crc32
-		$cdrec .= pack('V', $c_len);         // compressed filesize
-		$cdrec .= pack('V', $unc_len);       // uncompressed filesize
-		$cdrec .= pack('v', strlen($name)); // length of filename
-		$cdrec .= pack('v', 0);             // extra field length
-		$cdrec .= pack('v', 0);             // file comment length
-		$cdrec .= pack('v', 0);             // disk number start
-		$cdrec .= pack('v', 0);             // internal file attributes
-		$cdrec .= pack('V', 32);            // external file attributes - 'archive' bit set
-
-		$cdrec .= pack('V', $this->oldOffset); // relative offset of local header
-		$this->oldOffset += strlen($fr);
-
-		$cdrec .= $name;
-
-		// optional extra field, file comment goes here
-		// save to central directory
-		$this->centralDirectory[] = $cdrec;
-	} // end of the 'addFile()' method
-
-	/**
-	 * 圧縮されたデータを取得する
-	 *
-	 * @return    binary    $zipedData
-	 * @access    public
-	 */
-	function getZippedData()
-	{
-
-		$data = implode("", $this->compressedData);
-		$controlDirectory = implode("", $this->centralDirectory);
-
-		return
-			$data .
-			$controlDirectory .
-			$this->endOfCentralDirectory .
-			pack("v", sizeof($this->centralDirectory)) .
-			pack("v", sizeof($this->centralDirectory)) .
-			pack("V", strlen($controlDirectory)) .
-			pack("V", strlen($data)) . "\x00\x00";
-	}
-
-	/**
-	 * 圧縮ファイルをダウンロードする
-	 *
-	 * @param string $archiveName
-	 */
-	function download($archiveName)
-	{
-
-		$headerInfo = '';
-
-		if (ini_get('zlib.output_compression')) {
-			ini_set('zlib.output_compression', 'Off');
-		}
-
-		// Security checks
-		if ($archiveName == "") {
-			echo "<html><title>Download Error</title><body><BR><B>ERROR:</B> The download file was NOT SPECIFIED.</body></html>";
-			exit;
-		}
-
-		if (!preg_match('/\.zip$/', $archiveName)) {
-			$archiveName .= '.zip';
-		}
-		$zippedData = $this->getZippedData();
-		$size = strlen(bin2hex($zippedData)) / 2;
-
-		header("Pragma: public");
-		header("Expires: 0");
-		header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
-		header("Cache-Control: private", false);
-		header("Content-Type: application/zip");
-		header("Content-Disposition: attachment; filename=" . $archiveName . ";");
-		header("Content-Transfer-Encoding: binary");
-		header("Content-Length: " . $size);
-		echo $zippedData;
-
-	}
-
-	/**
-	 * 解凍したファイルを出力する
-	 *
-	 * @param <type> $path
-	 * @return <type>
-	 */
-	function unzip($source, $tareget)
-	{
-		$tareget = preg_replace('/\/$/', '', $tareget);
-		$entries = $this->_readFile($source);
-		if (!$entries) {
-			return false;
-		}
-		$result = true;
-		foreach($entries as $entry) {
-			$folder = new Folder($tareget . DS . $entry['Path'], true, 0777);
-			$fp = @fopen($tareget . DS . $entry['Path'] . DS . $entry['Name'], 'wb');
-			if ($fp) {
-				if (!fwrite($fp, $entry['Data'])) {
-					$result = false;
-				}
-			} else {
-				$result = false;
-				continue;
-			}
-			fclose($fp);
-		}
-		return $result;
-	}
-
-	/**
-	 * ZIPファイルを読み込む
-	 *
-	 * @param string $path
-	 * @return    array
-	 * @access    protected
-	 */
-	function _readFile($path)
-	{
-		ini_set('mbstring.func_overload', '0');
-		$this->entries = [];
-
-		$oF = fopen($path, 'rb');
-		$vZ = fread($oF, filesize($path));
-		fclose($oF);
-
-		$aE = explode("\x50\x4b\x05\x06", $vZ);
-		$aP = unpack('x16/v1CL', $aE[1]);
-		$this->Comment = substr($aE[1], 18, $aP['CL']);
-		$this->Comment = strtr($this->Comment, ["\r\n" => "\n",
-			"\r" => "\n"]);
-		$aE = explode("\x50\x4b\x01\x02", $vZ);
-		$aE = explode("\x50\x4b\x03\x04", $aE[0]);
-		array_shift($aE);
-
-		foreach($aE as $vZ) {
-			$aI = [];
-			$aI['E'] = 0;
-			$aI['EM'] = '';
-			$aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL', $vZ);
-			$bE = ($aP['GPF'] & 0x0001)? TRUE : FALSE;
-			$nF = $aP['FNL'];
-
-			if ($aP['GPF'] & 0x0008) {
-				$aP1 = unpack('V1CRC/V1CS/V1UCS', substr($vZ, -12));
-				$aP['CRC'] = $aP1['CRC'];
-				$aP['CS'] = $aP1['CS'];
-				$aP['UCS'] = $aP1['UCS'];
-				$vZ = substr($vZ, 0, -12);
-			}
-
-			$aI['N'] = substr($vZ, 26, $nF);
-			$aI['N'] = str_replace('/', DS, $aI['N']);
-			if (substr($aI['N'], -1) == DS) {
-				continue;
-			}
-
-			$aI['P'] = dirname($aI['N']);
-			$aI['P'] = $aI['P'] == '.'? '' : $aI['P'];
-			$aI['N'] = basename($aI['N']);
-
-			$vZ = substr($vZ, 26 + $nF);
-
-			if (strlen($vZ) != $aP['CS']) {
-				$aI['E'] = 1;
-				$aI['EM'] = 'Compressed size is not equal with the value in header information.';
-			} else {
-				if ($bE) {
-					$aI['E'] = 5;
-					$aI['EM'] = 'File is encrypted, which is not supported from this class.';
-				} else {
-					switch($aP['CM']) {
-						case 0:
-							break;
-
-						case 8:
-							$vZ = gzinflate($vZ);
-							break;
-
-						case 12:
-							if (!extension_loaded('bz2')) {
-								if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
-									@dl('php_bz2.dll');
-								} else {
-									@dl('bz2.so');
-								}
-							}
-
-							if (extension_loaded('bz2')) {
-								$vZ = bzdecompress($vZ);
-							} else {
-								$aI['E'] = 7;
-								$aI['EM'] = "PHP BZIP2 extension not available.";
-							}
-							break;
-
-						default:
-							$aI['E'] = 6;
-							$aI['EM'] = "De-/Compression method {$aP['CM']} is not supported.";
-					}
-
-					if (!$aI['E']) {
-						if ($vZ === FALSE) {
-							$aI['E'] = 2;
-							$aI['EM'] = 'Decompression of data failed.';
-						} else {
-							if (strlen($vZ) != $aP['UCS']) {
-								$aI['E'] = 3;
-								$aI['EM'] = 'Uncompressed size is not equal with the value in header information.';
-							} else {
-								if (crc32($vZ) != $aP['CRC']) {
-									$aI['E'] = 4;
-									$aI['EM'] = 'CRC32 checksum is not equal with the value in header information.';
-								}
-							}
-						}
-					}
-				}
-			}
-
-			$aI['D'] = $vZ;
-
-			$aI['T'] = mktime(($aP['FT'] & 0xf800) >> 11,
-				($aP['FT'] & 0x07e0) >> 5,
-				($aP['FT'] & 0x001f) << 1,
-				($aP['FD'] & 0x01e0) >> 5,
-				($aP['FD'] & 0x001f),
-				(($aP['FD'] & 0xfe00) >> 9) + 1980);
-			$entry = [];
-			$entry['Data'] = $aI['D'];
-			$entry['Error'] = $aI['E'];
-			$entry['ErrorMsg'] = $aI['EM'];
-			$entry['Name'] = $aI['N'];
-			$entry['Path'] = $aI['P'];
-			$entry['Time'] = $aI['T'];
-			$this->entries[] = $entry;
-		}
-
-		return $this->entries;
-
-	}
-
-}

Vulnerable code: App::uses('Simplezip', 'Vendor');
		App::uses('BcZip', 'Lib');
		$fileName = 'default';
		$Simplezip = new Simplezip();
		$Simplezip->addFolder($tmpDir);
		$Simplezip->download($fileName);
		exit();
		$path = BASER_THEMES . $this->siteConfigs['theme'] . DS;
			'from' => $path,
			'to' => $tmpDir . $this->siteConfigs['theme'],
		$Simplezip = new Simplezip();
		$Simplezip->addFolder($tmpDir);
		$Simplezip->download($this->siteConfigs['theme']);
App::uses('Simplezip', 'Vendor');
				set_time_limit(0);

		$Simplezip = new Simplezip();
		if (!$Simplezip->unzip($targetPath, $tmpPath)) {
		$tmpDir = TMP . 'schemas' . DS;
		$fileName = 'baserbackup_' . $version . '_' . date('Ymd_His');
		$Simplezip = new Simplezip();
		$Simplezip->addFolder($tmpDir);
		$Simplezip->download($fileName);
		exit();
		$Simplezip = new Simplezip();
		$Simplezip->addFolder($path);
		$Simplezip->download('schemas');
		exit();
				set_time_limit(0);
					exit();
		$fileName = 'basercms_logs_' . date('Ymd_His');
		$Simplezip = new Simplezip();
		$Simplezip->addFolder($tmpDir);
		$Simplezip->download($fileName);
	/**
	 * Destruct
	 */
	public function __destruct()
	{
		if (class_exists('ZipArchive')) {
			$this->Zip->close();
		}
	}

<?php
/**
 * Simplezip
 *
 * baserCMS :  Based Website Development Project <https://basercms.net>
 * Copyright (c) baserCMS Users Community <https://basercms.net/community/>
 *
 * @copyright       Copyright (c) baserCMS Users Community
 * @link            https://basercms.net baserCMS Project
 * @package         Baser.Vendor
 * @since           baserCMS v 3.0.0
 * @license         https://basercms.net/license/index.html
 */

/**
 * シンプルなZIP利用クラス
 *
 * CAUTION 現在、Macで圧縮したZipファイルに対応していない
 */
class Simplezip
{

	/**
	 * 圧縮データ
	 *
	 * @var        array
	 * @access    public
	 */
	var $compressedData = [];

	/**
	 * Central Directory
	 *
	 * @var        array
	 * @access    public
	 */
	var $centralDirectory = [];

	/**
	 * End Of Central Directory Record
	 *
	 * @var        string
	 * @access    public
	 */
	var $endOfCentralDirectory = "\x50\x4b\x05\x06\x00\x00\x00\x00";

	/**
	 * オフセット
	 *
	 * @var        int
	 * @access    public
	 */
	var $oldOffset = 0;

	/**
	 * エントリ（解凍用）
	 *
	 * @var        array
	 * @access    public
	 */
	var $entries = [];

	/**
	 * Get Hexd Time
	 *
	 * @param int $time Unix timestamp
	 * @return    hex    the date formated as a ZIP date
	 * @access    public
	 */
	function getMTime($time)
	{
		$mtime = ($time !== null? getdate($time) : getdate());
		$mtime = preg_replace(
			"/(..){1}(..){1}(..){1}(..){1}/",
			"\\x\\4\\x\\3\\x\\2\\x\\1",
			dechex(($mtime['year'] - 1980 << 25) |
				($mtime['mon'] << 21) |
				($mtime['mday'] << 16) |
				($mtime['hours'] << 11) |
				($mtime['minutes'] << 5) |
				($mtime['seconds'] >> 1)));
		eval('$mtime = "' . $mtime . '";');
		return $mtime;
	}

	/**
	 * フォルダを追加する
	 *
	 * @param string $directory
	 * @param string $put_int 追加対象位置
	 */
	function addFolder($directory, $put_into = '')
	{

		$handle = opendir($directory);
		if ($handle) {
			while(false !== ($file = readdir($handle))) {
				if (is_file($directory . $file)) {
					$fp = fopen($directory . $file, 'rb');
					$fileContents = fread($fp, filesize($directory . $file));
					fclose($fp);
					$this->addFile($fileContents, $put_into . $file, filemtime($directory . $file));
				} elseif ($file != '.' and $file != '..' and is_dir($directory . $file)) {
					$this->addFolder($directory . $file . DS, $put_into . $file . DS);
				}
			}
		}

		closedir($handle);

	}

	/**
	 * 圧縮対象データを追加
	 *
	 * @param binary $data
	 * @param string $directoryName
	 * @param int $time
	 * @access    public
	 */
	function addFile($data, $directoryName, $time = 0)
	{

		$directoryName = str_replace("\\", "/", $directoryName);
		$hexdtime = $this->getMTime($time);

		$feedArrayRow = "\x50\x4b\x03\x04";
		$feedArrayRow .= "\x14\x00";
		$feedArrayRow .= "\x00\x00";
		$feedArrayRow .= "\x08\x00";
		$feedArrayRow .= $hexdtime;

		$uncompressedLength = strlen($data);
		$compression = crc32($data);
		$gzCompressedData = gzcompress($data);
		$gzCompressedData = substr($gzCompressedData, 2, strlen($gzCompressedData) - 6);
		$compressedLength = strlen($gzCompressedData);

		$feedArrayRow .= pack("V", $compression);
		$feedArrayRow .= pack("V", $compressedLength);
		$feedArrayRow .= pack("V", $uncompressedLength);
		$feedArrayRow .= pack("v", strlen($directoryName));
		$feedArrayRow .= pack("v", 0);
		$feedArrayRow .= $directoryName;

		$feedArrayRow .= $gzCompressedData;

		$this->compressedData[] = $feedArrayRow;

		$newOffset = strlen($feedArrayRow);

		$addCentralRecord = "\x50\x4b\x01\x02";
		$addCentralRecord .= "\x00\x00";
		$addCentralRecord .= "\x14\x00";
		$addCentralRecord .= "\x00\x00";
		$addCentralRecord .= "\x08\x00";
		$addCentralRecord .= $hexdtime;
		$addCentralRecord .= pack("V", $compression);
		$addCentralRecord .= pack("V", $compressedLength);
		$addCentralRecord .= pack("V", $uncompressedLength);
		$addCentralRecord .= pack("v", strlen($directoryName));
		$addCentralRecord .= pack("v", 0);
		$addCentralRecord .= pack("v", 0);
		$addCentralRecord .= pack("v", 0);
		$addCentralRecord .= pack("v", 0);
		$addCentralRecord .= pack("V", 32);
		$addCentralRecord .= pack("V", $this->oldOffset);
		$addCentralRecord .= $directoryName;

		$this->oldOffset += $newOffset;
		$this->centralDirectory[] = $addCentralRecord;

	}

	/**
	 * Adds "file" to archive
	 *
	 * @param string   file contents
	 * @param string   name of the file in the archive (may contains the path)
	 * @param integer  the current timestamp
	 */
	function unix2DosTime($unixtime = 0)
	{
		$timearray = ($unixtime == 0)? getdate() : getdate($unixtime);

		if ($timearray['year'] < 1980) {
			$timearray['year'] = 1980;
			$timearray['mon'] = 1;
			$timearray['mday'] = 1;
			$timearray['hours'] = 0;
			$timearray['minutes'] = 0;
			$timearray['seconds'] = 0;
		} // end if

		return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) |
			($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
	} // end of the 'unix2DosTime()' method

	/**
	 * addFile2
	 *
	 * @param mixed $data
	 * @param string $name
	 * @param int $time
	 */
	function addFile2($data, $name, $time = 0)
	{
		$name = str_replace('\\', '/', $name);

		$dtime = dechex($this->unix2DosTime($time));
		$hexdtime = '\x' . $dtime[6] . $dtime[7]
			. '\x' . $dtime[4] . $dtime[5]
			. '\x' . $dtime[2] . $dtime[3]
			. '\x' . $dtime[0] . $dtime[1];
		eval('$hexdtime = "' . $hexdtime . '";');

		$fr = "\x50\x4b\x03\x04";
		$fr .= "\x14\x00";            // ver needed to extract
		$fr .= "\x00\x00";            // gen purpose bit flag
		$fr .= "\x08\x00";            // compression method
		$fr .= $hexdtime;             // last mod time and date

		// "local file header" segment
		$unc_len = strlen($data);
		$crc = crc32($data);
		$zdata = gzcompress($data);
		$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug
		$c_len = strlen($zdata);
		$fr .= pack('V', $crc);             // crc32
		$fr .= pack('V', $c_len);           // compressed filesize
		$fr .= pack('V', $unc_len);         // uncompressed filesize
		$fr .= pack('v', strlen($name));    // length of filename
		$fr .= pack('v', 0);                // extra field length
		$fr .= $name;

		// "file data" segment
		$fr .= $zdata;

		// "data descriptor" segment (optional but necessary if archive is not
		// served as file)
		// nijel(2004-10-19): this seems not to be needed at all and causes
		// problems in some cases (bug #1037737)
		//$fr .= pack('V', $crc);                 // crc32
		//$fr .= pack('V', $c_len);               // compressed filesize
		//$fr .= pack('V', $unc_len);             // uncompressed filesize

		// add this entry to array
		$this->compressedData[] = $fr;

		// now add to central directory record
		$cdrec = "\x50\x4b\x01\x02";
		$cdrec .= "\x00\x00";                // version made by
		$cdrec .= "\x14\x00";                // version needed to extract
		$cdrec .= "\x00\x00";                // gen purpose bit flag
		$cdrec .= "\x08\x00";                // compression method
		$cdrec .= $hexdtime;                 // last mod time & date
		$cdrec .= pack('V', $crc);           // crc32
		$cdrec .= pack('V', $c_len);         // compressed filesize
		$cdrec .= pack('V', $unc_len);       // uncompressed filesize
		$cdrec .= pack('v', strlen($name)); // length of filename
		$cdrec .= pack('v', 0);             // extra field length
		$cdrec .= pack('v', 0);             // file comment length
		$cdrec .= pack('v', 0);             // disk number start
		$cdrec .= pack('v', 0);             // internal file attributes
		$cdrec .= pack('V', 32);            // external file attributes - 'archive' bit set

		$cdrec .= pack('V', $this->oldOffset); // relative offset of local header
		$this->oldOffset += strlen($fr);

		$cdrec .= $name;

		// optional extra field, file comment goes here
		// save to central directory
		$this->centralDirectory[] = $cdrec;
	} // end of the 'addFile()' method

	/**
	 * 圧縮されたデータを取得する
	 *
	 * @return    binary    $zipedData
	 * @access    public
	 */
	function getZippedData()
	{

		$data = implode("", $this->compressedData);
		$controlDirectory = implode("", $this->centralDirectory);

		return
			$data .
			$controlDirectory .
			$this->endOfCentralDirectory .
			pack("v", sizeof($this->centralDirectory)) .
			pack("v", sizeof($this->centralDirectory)) .
			pack("V", strlen($controlDirectory)) .
			pack("V", strlen($data)) . "\x00\x00";
	}

	/**
	 * 圧縮ファイルをダウンロードする
	 *
	 * @param string $archiveName
	 */
	function download($archiveName)
	{

		$headerInfo = '';

		if (ini_get('zlib.output_compression')) {
			ini_set('zlib.output_compression', 'Off');
		}

		// Security checks
		if ($archiveName == "") {
			echo "<html><title>Download Error</title><body><BR><B>ERROR:</B> The download file was NOT SPECIFIED.</body></html>";
			exit;
		}

		if (!preg_match('/\.zip$/', $archiveName)) {
			$archiveName .= '.zip';
		}
		$zippedData = $this->getZippedData();
		$size = strlen(bin2hex($zippedData)) / 2;

		header("Pragma: public");
		header("Expires: 0");
		header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
		header("Cache-Control: private", false);
		header("Content-Type: application/zip");
		header("Content-Disposition: attachment; filename=" . $archiveName . ";");
		header("Content-Transfer-Encoding: binary");
		header("Content-Length: " . $size);
		echo $zippedData;

	}

	/**
	 * 解凍したファイルを出力する
	 *
	 * @param <type> $path
	 * @return <type>
	 */
	function unzip($source, $tareget)
	{
		$tareget = preg_replace('/\/$/', '', $tareget);
		$entries = $this->_readFile($source);
		if (!$entries) {
			return false;
		}
		$result = true;
		foreach($entries as $entry) {
			$folder = new Folder($tareget . DS . $entry['Path'], true, 0777);
			$fp = @fopen($tareget . DS . $entry['Path'] . DS . $entry['Name'], 'wb');
			if ($fp) {
				if (!fwrite($fp, $entry['Data'])) {
					$result = false;
				}
			} else {
				$result = false;
				continue;
			}
			fclose($fp);
		}
		return $result;
	}

	/**
	 * ZIPファイルを読み込む
	 *
	 * @param string $path
	 * @return    array
	 * @access    protected
	 */
	function _readFile($path)
	{
		ini_set('mbstring.func_overload', '0');
		$this->entries = [];

		$oF = fopen($path, 'rb');
		$vZ = fread($oF, filesize($path));
		fclose($oF);

		$aE = explode("\x50\x4b\x05\x06", $vZ);
		$aP = unpack('x16/v1CL', $aE[1]);
		$this->Comment = substr($aE[1], 18, $aP['CL']);
		$this->Comment = strtr($this->Comment, ["\r\n" => "\n",
			"\r" => "\n"]);
		$aE = explode("\x50\x4b\x01\x02", $vZ);
		$aE = explode("\x50\x4b\x03\x04", $aE[0]);
		array_shift($aE);

		foreach($aE as $vZ) {
			$aI = [];
			$aI['E'] = 0;
			$aI['EM'] = '';
			$aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL', $vZ);
			$bE = ($aP['GPF'] & 0x0001)? TRUE : FALSE;
			$nF = $aP['FNL'];

			if ($aP['GPF'] & 0x0008) {
				$aP1 = unpack('V1CRC/V1CS/V1UCS', substr($vZ, -12));
				$aP['CRC'] = $aP1['CRC'];
				$aP['CS'] = $aP1['CS'];
				$aP['UCS'] = $aP1['UCS'];
				$vZ = substr($vZ, 0, -12);
			}

			$aI['N'] = substr($vZ, 26, $nF);
			$aI['N'] = str_replace('/', DS, $aI['N']);
			if (substr($aI['N'], -1) == DS) {
				continue;
			}

			$aI['P'] = dirname($aI['N']);
			$aI['P'] = $aI['P'] == '.'? '' : $aI['P'];
			$aI['N'] = basename($aI['N']);

			$vZ = substr($vZ, 26 + $nF);

			if (strlen($vZ) != $aP['CS']) {
				$aI['E'] = 1;
				$aI['EM'] = 'Compressed size is not equal with the value in header information.';
			} else {
				if ($bE) {
					$aI['E'] = 5;
					$aI['EM'] = 'File is encrypted, which is not supported from this class.';
				} else {
					switch($aP['CM']) {
						case 0:
							break;

						case 8:
							$vZ = gzinflate($vZ);
							break;

						case 12:
							if (!extension_loaded('bz2')) {
								if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
									@dl('php_bz2.dll');
								} else {
									@dl('bz2.so');
								}
							}

							if (extension_loaded('bz2')) {
								$vZ = bzdecompress($vZ);
							} else {
								$aI['E'] = 7;
								$aI['EM'] = "PHP BZIP2 extension not available.";
							}
							break;

						default:
							$aI['E'] = 6;
							$aI['EM'] = "De-/Compression method {$aP['CM']} is not supported.";
					}

					if (!$aI['E']) {
						if ($vZ === FALSE) {
							$aI['E'] = 2;
							$aI['EM'] = 'Decompression of data failed.';
						} else {
							if (strlen($vZ) != $aP['UCS']) {
								$aI['E'] = 3;
								$aI['EM'] = 'Uncompressed size is not equal with the value in header information.';
							} else {
								if (crc32($vZ) != $aP['CRC']) {
									$aI['E'] = 4;
									$aI['EM'] = 'CRC32 checksum is not equal with the value in header information.';
								}
							}
						}
					}
				}
			}

			$aI['D'] = $vZ;

			$aI['T'] = mktime(($aP['FT'] & 0xf800) >> 11,
				($aP['FT'] & 0x07e0) >> 5,
				($aP['FT'] & 0x001f) << 1,
				($aP['FD'] & 0x01e0) >> 5,
				($aP['FD'] & 0x001f),
				(($aP['FD'] & 0xfe00) >> 9) + 1980);
			$entry = [];
			$entry['Data'] = $aI['D'];
			$entry['Error'] = $aI['E'];
			$entry['ErrorMsg'] = $aI['EM'];
			$entry['Name'] = $aI['N'];
			$entry['Path'] = $aI['P'];
			$entry['Time'] = $aI['T'];
			$this->entries[] = $entry;
		}

		return $this->entries;

	}

}
Fixed code: 		
		<div class="panel-box bca-panel-box corner10">
			<h3 class="bca-panel-box__title"><?php echo __d('baser', '拡張モジュール') ?></h3>
			<ul class="section">
				<li class='<?php if ($zipOk) echo 'check'; else echo 'failed'; ?>'>
					<?php echo __d('baser', 'Zip') ?><br/>
					<div class="check-result">
						<?php if ($zipOk): ?>
							<?php echo __d('baser', '利用可') ?>
						<?php else: ?>
							<?php echo __d('baser', '利用不可') ?><br/>
							<small><?php echo __d('baser', 'テーマなどのzipダウンロードが制限されます。') ?></small>
						<?php endif ?>
					</div>
				</li>
			</ul>
		</div>
		
<?php if ($zipEnable): ?>
<?php endif; ?>
/**
 * Zipモジュールなし
 */
if (!extension_loaded('zip')) {
	unset($config['BcApp']['adminNavigation']['Systems']['Theme']['menus']['ThemesDownload']);
	unset($config['BcApp']['adminNavigation']['Systems']['Theme']['menus']['ThemesDownloadDefaultDataPattern']);
	unset($config['BcApp']['adminNavigation']['Systems']['Tools']['menus']['ToolsMaintenance']);
}


			'phpZip' => extension_loaded('zip'),
			'zipOk' => $status['phpZip'],
				// 入力validation
				$check = ['content_tmp' => $this->request->data['Page']['contents_tmp']];
				if (!$this->Page->containsScript($check)) {
					$this->BcMessage->setError(__d('baser', '本稿欄でスクリプトの入力は許可されていません。'));
					$this->notFound();
				}
				
				
				// 入力validation
				$check = ['content_tmp' => $this->request->data['Page']['contents_tmp']];
				if (!$this->Page->containsScript($check)) {
					$this->BcMessage->setError(__d('baser', '本稿欄でスクリプトの入力は許可されていません。'));
					$this->notFound();
				}
				
App::uses('BcZip', 'Lib');
		if (!extension_loaded('zip')) {
			$this->notFound();
		}
		
		$this->autoRender = false;
		$distPath = TMP . 'default.zip';
		
		$bcZip = new BcZip();
		$bcZip->create($tmpDir, $distPath);
		
		header("Cache-Control: no-store");
		header("Content-Type: application/zip");
		header("Content-Disposition: attachment; filename=" . basename($distPath) . ";");
		header("Content-Length: " . filesize($distPath));
		while (ob_get_level()) { ob_end_clean(); }
		echo readfile($distPath);
		
		unlink($distPath);
		if (!extension_loaded('zip')) {
			$this->notFound();
		}
		
		$orgPath = BASER_THEMES . $this->siteConfigs['theme'] . DS;
		$sourcePath = $tmpDir . $this->siteConfigs['theme'];
		$distPath = $sourcePath . '.zip';
		
			'from' => $orgPath,
			'to' => $sourcePath,
		$bcZip = new BcZip();
		$bcZip->create($sourcePath, $distPath);
		
		header("Cache-Control: no-store");
		header("Content-Type: application/zip");
		header("Content-Disposition: attachment; filename=" . basename($distPath) . ";");
		header("Content-Length: " . filesize($distPath));
		while (ob_get_level()) { ob_end_clean(); }
		echo readfile($distPath);
		
App::uses('BcZip', 'Lib');
		$zipEnable = extension_loaded('zip');
		if (!$zipEnable) {
			$this->notFound();
		}
		
		
		$bcZip = new BcZip();
		if (!$bcZip->extract($targetPath, $tmpPath)) {
		set_time_limit(0);
		
		$tmpDir = TMP . 'schemas' . DS;
		$distPath = TMP . 'baserbackup_' . $version . '_' . date('Ymd_His') . '.zip';
		
		
		$bcZip = new BcZip();
		$bcZip->create($tmpDir, $distPath);
		
		header("Cache-Control: no-store");
		header("Content-Type: application/zip");
		header("Content-Disposition: attachment; filename=" . basename($distPath) . ";");
		header("Content-Length: " . filesize($distPath));
		while (ob_get_level()) { ob_end_clean(); }
		echo readfile($distPath);
		
		unlink($distPath);
		return true;
		$distPath = TMP . 'schemas.zip';
		// ZIP圧縮して出力
		set_time_limit(0);
		$this->autoRender = false;
		
		// 不要ディレクトリの削除
		rmdir($path . 'core');
		rmdir($path . 'plugin');
		
		$bcZip = new BcZip();
		$bcZip->create($path, $distPath);
		
		header("Cache-Control: no-store");
		header("Content-Type: application/zip");
		header("Content-Disposition: attachment; filename=" . basename($distPath) . ";");
		header("Content-Length: " . filesize($distPath));
		while (ob_get_level()) { ob_end_clean(); }
		echo readfile($distPath);
		
		unlink($distPath);
		return true;
		$zipEnable = extension_loaded('zip');
				if (!$zipEnable) {
					$this->notFound();
				}
				$this->autoRender = false;
					return;
		$this->set('zipEnable', $zipEnable);
		set_time_limit(0);
		
		$distPath = TMP . 'basercms_logs_' . date('Ymd_His') . '.zip';
		$bcZip = new BcZip();
		$bcZip->create($tmpDir, $distPath);
		
		header("Cache-Control: no-store");
		header("Content-Type: application/zip");
		header("Content-Disposition: attachment; filename=" . basename($distPath) . ";");
		header("Content-Length: " . filesize($distPath));
		while (ob_get_level()) { ob_end_clean(); }
		echo readfile($distPath);
		
		unlink($distPath);
			$this->Zip->close();
	/**
	 * zip生成
	 *
	 * @param string $sorce 元データ
	 * @param string $dist 出力先
	 * @return void
	 */
    public function create($sorce, $dist)
    {
        $za = new \ZipArchive();
        $za->open($dist, \ZIPARCHIVE::CREATE);
        $this->zipSub($za, $sorce);
        $za->close();
    }

	/**
	 * 再帰的にzip生成対象ファイルを追加する
	 *
	 * @param ZipArchive $za
	 * @param string $path
	 * @param string $parentPath
	 * @return void
	 */
    private function zipSub($za, $path, $parentPath = '')
    {
        $dh = opendir($path);
        while (($entry = readdir($dh)) !== false) {
            if ($entry == '.' || $entry == '..') {
            } else {
                $localPath = $parentPath . $entry;
                $fullpath = $path . DS . $entry;
                if (is_file($fullpath)) {
                    $za->addFile($fullpath, $localPath);
                } else if (is_dir($fullpath)) {
                    $za->addEmptyDir($localPath);
                    $this->zipSub($za, $fullpath, $localPath . DS);
                }
            }
        }
        closedir($dh);
    }