PHP Malware Analysis

unzipper.php, unzipper.phtml

md5: 08cad40bad07557ef283883c4d3d3f3c

Jump to:

Screenshot


Attributes

Input

Title

URLs


Deobfuscated PHP code

<?php

/**
 * The Unzipper extracts .zip or .rar archives and .gz files on webservers.
 * It's handy if you do not have shell access. E.g. if you want to upload a lot
 * of files (php framework or image collection) as an archive to save time.
 * As of version 0.1.0 it also supports creating archives.
 *
 * @author  Andreas Tasch, at[tec], attec.at
 * @license GNU GPL v3
 * @package attec.toolbox
 * @version 0.1.1
 */
define('VERSION', '0.1.1');
$timestart = microtime(TRUE);
$GLOBALS['status'] = array();
$unzipper = new Unzipper();
if (isset($_POST['dounzip'])) {
    // Check if an archive was selected for unzipping.
    $archive = isset($_POST['zipfile']) ? strip_tags($_POST['zipfile']) : '';
    $destination = isset($_POST['extpath']) ? strip_tags($_POST['extpath']) : '';
    $unzipper->prepareExtraction($archive, $destination);
}
if (isset($_POST['dozip'])) {
    $zippath = !empty($_POST['zippath']) ? strip_tags($_POST['zippath']) : '.';
    // Resulting zipfile e.g. zipper--2016-07-23--11-55.zip.
    $zipfile = 'zipper-' . date("Y-m-d--H-i") . '.zip';
    Zipper::zipDir($zippath, $zipfile);
}
$timeend = microtime(TRUE);
$time = round($timeend - $timestart, 4);
/**
 * Class Unzipper
 */
class Unzipper
{
    public $localdir = '.';
    public $zipfiles = array();
    public function __construct()
    {
        // Read directory and pick .zip, .rar and .gz files.
        if ($dh = opendir($this->localdir)) {
            while (($file = readdir($dh)) !== FALSE) {
                if (pathinfo($file, PATHINFO_EXTENSION) === 'zip' || pathinfo($file, PATHINFO_EXTENSION) === 'gz' || pathinfo($file, PATHINFO_EXTENSION) === 'rar') {
                    $this->zipfiles[] = $file;
                }
            }
            closedir($dh);
            if (!empty($this->zipfiles)) {
                $GLOBALS['status'] = array('info' => '.zip or .gz or .rar files found, ready for extraction');
            } else {
                $GLOBALS['status'] = array('info' => 'No .zip or .gz or rar files found. So only zipping functionality available.');
            }
        }
    }
    /**
     * Prepare and check zipfile for extraction.
     *
     * @param string $archive
     *   The archive name including file extension. E.g. my_archive.zip.
     * @param string $destination
     *   The relative destination path where to extract files.
     */
    public function prepareExtraction($archive, $destination = '')
    {
        // Determine paths.
        if (empty($destination)) {
            $extpath = $this->localdir;
        } else {
            $extpath = $this->localdir . '/' . $destination;
            // Todo: move this to extraction function.
            if (!is_dir($extpath)) {
                mkdir($extpath);
            }
        }
        // Only local existing archives are allowed to be extracted.
        if (in_array($archive, $this->zipfiles)) {
            self::extract($archive, $extpath);
        }
    }
    /**
     * Checks file extension and calls suitable extractor functions.
     *
     * @param string $archive
     *   The archive name including file extension. E.g. my_archive.zip.
     * @param string $destination
     *   The relative destination path where to extract files.
     */
    public static function extract($archive, $destination)
    {
        $ext = pathinfo($archive, PATHINFO_EXTENSION);
        switch ($ext) {
            case 'zip':
                self::extractZipArchive($archive, $destination);
                break;
            case 'gz':
                self::extractGzipFile($archive, $destination);
                break;
            case 'rar':
                self::extractRarArchive($archive, $destination);
                break;
        }
    }
    /**
     * Decompress/extract a zip archive using ZipArchive.
     *
     * @param $archive
     * @param $destination
     */
    public static function extractZipArchive($archive, $destination)
    {
        // Check if webserver supports unzipping.
        if (!class_exists('ZipArchive')) {
            $GLOBALS['status'] = array('error' => 'Error: Your PHP version does not support unzip functionality.');
            return;
        }
        $zip = new ZipArchive();
        // Check if archive is readable.
        if ($zip->open($archive) === TRUE) {
            // Check if destination is writable
            if (is_writeable($destination . '/')) {
                $zip->extractTo($destination);
                $zip->close();
                $GLOBALS['status'] = array('success' => 'Files unzipped successfully');
            } else {
                $GLOBALS['status'] = array('error' => 'Error: Directory not writeable by webserver.');
            }
        } else {
            $GLOBALS['status'] = array('error' => 'Error: Cannot read .zip archive.');
        }
    }
    /**
     * Decompress a .gz File.
     *
     * @param string $archive
     *   The archive name including file extension. E.g. my_archive.zip.
     * @param string $destination
     *   The relative destination path where to extract files.
     */
    public static function extractGzipFile($archive, $destination)
    {
        // Check if zlib is enabled
        if (!function_exists('gzopen')) {
            $GLOBALS['status'] = array('error' => 'Error: Your PHP has no zlib support enabled.');
            return;
        }
        $filename = pathinfo($archive, PATHINFO_FILENAME);
        $gzipped = gzopen($archive, "rb");
        $file = fopen($destination . '/' . $filename, "w");
        while ($string = gzread($gzipped, 4096)) {
            fwrite($file, $string, strlen($string));
        }
        gzclose($gzipped);
        fclose($file);
        // Check if file was extracted.
        if (file_exists($destination . '/' . $filename)) {
            $GLOBALS['status'] = array('success' => 'File unzipped successfully.');
            // If we had a tar.gz file, let's extract that tar file.
            if (pathinfo($destination . '/' . $filename, PATHINFO_EXTENSION) == 'tar') {
                $phar = new PharData($destination . '/' . $filename);
                if ($phar->extractTo($destination)) {
                    $GLOBALS['status'] = array('success' => 'Extracted tar.gz archive successfully.');
                    // Delete .tar.
                    unlink($destination . '/' . $filename);
                }
            }
        } else {
            $GLOBALS['status'] = array('error' => 'Error unzipping file.');
        }
    }
    /**
     * Decompress/extract a Rar archive using RarArchive.
     *
     * @param string $archive
     *   The archive name including file extension. E.g. my_archive.zip.
     * @param string $destination
     *   The relative destination path where to extract files.
     */
    public static function extractRarArchive($archive, $destination)
    {
        // Check if webserver supports unzipping.
        if (!class_exists('RarArchive')) {
            $GLOBALS['status'] = array('error' => 'Error: Your PHP version does not support .rar archive functionality. <a class="info" href="http://php.net/manual/en/rar.installation.php" target="_blank">How to install RarArchive</a>');
            return;
        }
        // Check if archive is readable.
        if ($rar = RarArchive::open($archive)) {
            // Check if destination is writable
            if (is_writeable($destination . '/')) {
                $entries = $rar->getEntries();
                foreach ($entries as $entry) {
                    $entry->extract($destination);
                }
                $rar->close();
                $GLOBALS['status'] = array('success' => 'Files extracted successfully.');
            } else {
                $GLOBALS['status'] = array('error' => 'Error: Directory not writeable by webserver.');
            }
        } else {
            $GLOBALS['status'] = array('error' => 'Error: Cannot read .rar archive.');
        }
    }
}
/**
 * Class Zipper
 *
 * Copied and slightly modified from http://at2.php.net/manual/en/class.ziparchive.php#110719
 * @author umbalaconmeogia
 */
class Zipper
{
    /**
     * Add files and sub-directories in a folder to zip file.
     *
     * @param string $folder
     *   Path to folder that should be zipped.
     *
     * @param ZipArchive $zipFile
     *   Zipfile where files end up.
     *
     * @param int $exclusiveLength
     *   Number of text to be exclusived from the file path.
     */
    private static function folderToZip($folder, &$zipFile, $exclusiveLength)
    {
        $handle = opendir($folder);
        while (FALSE !== ($f = readdir($handle))) {
            // Check for local/parent path or zipping file itself and skip.
            if ($f != '.' && $f != '..' && $f != basename("/var/www/html/unzipper.php.baa49571da5a968003f21177ad5b6c57.bin")) {
                $filePath = "{$folder}/{$f}";
                // Remove prefix from file path before add to zip.
                $localPath = substr($filePath, $exclusiveLength);
                if (is_file($filePath)) {
                    $zipFile->addFile($filePath, $localPath);
                } elseif (is_dir($filePath)) {
                    // Add sub-directory.
                    $zipFile->addEmptyDir($localPath);
                    self::folderToZip($filePath, $zipFile, $exclusiveLength);
                }
            }
        }
        closedir($handle);
    }
    /**
     * Zip a folder (including itself).
     *
     * Usage:
     *   Zipper::zipDir('path/to/sourceDir', 'path/to/out.zip');
     *
     * @param string $sourcePath
     *   Relative path of directory to be zipped.
     *
     * @param string $outZipPath
     *   Relative path of the resulting output zip file.
     */
    public static function zipDir($sourcePath, $outZipPath)
    {
        $pathInfo = pathinfo($sourcePath);
        $parentPath = $pathInfo['dirname'];
        $dirName = $pathInfo['basename'];
        $z = new ZipArchive();
        $z->open($outZipPath, ZipArchive::CREATE);
        $z->addEmptyDir($dirName);
        if ($sourcePath == $dirName) {
            self::folderToZip($sourcePath, $z, 0);
        } else {
            self::folderToZip($sourcePath, $z, strlen("{$parentPath}/"));
        }
        $z->close();
        $GLOBALS['status'] = array('success' => 'Successfully created archive ' . $outZipPath);
    }
}
?>

<!DOCTYPE html>
<html>
<head>
  <title>File Unzipper + Zipper</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <style type="text/css">
    <!--
    body {
      font-family: Arial, sans-serif;
      line-height: 150%;
    }

    label {
      display: block;
      margin-top: 20px;
    }

    fieldset {
      border: 0;
      background-color: #EEE;
      margin: 10px 0 10px 0;
    }

    .select {
      padding: 5px;
      font-size: 110%;
    }

    .status {
      margin: 0;
      margin-bottom: 20px;
      padding: 10px;
      font-size: 80%;
      background: #EEE;
      border: 1px dotted #DDD;
    }

    .status--ERROR {
      background-color: red;
      color: white;
      font-size: 120%;
    }

    .status--SUCCESS {
      background-color: green;
      font-weight: bold;
      color: white;
      font-size: 120%
    }

    .small {
      font-size: 0.7rem;
      font-weight: normal;
    }

    .version {
      font-size: 80%;
    }

    .form-field {
      border: 1px solid #AAA;
      padding: 8px;
      width: 280px;
    }

    .info {
      margin-top: 0;
      font-size: 80%;
      color: #777;
    }

    .submit {
      background-color: #378de5;
      border: 0;
      color: #ffffff;
      font-size: 15px;
      padding: 10px 24px;
      margin: 20px 0 20px 0;
      text-decoration: none;
    }

    .submit:hover {
      background-color: #2c6db2;
      cursor: pointer;
    }
    -->
  </style>
</head>
<body>
<p class="status status--<?php 
echo strtoupper(key($GLOBALS['status']));
?>">
  Status: <?php 
echo reset($GLOBALS['status']);
?><br/>
  <span class="small">Processing Time: <?php 
echo $time;
?> seconds</span>
</p>
<form action="" method="POST">
  <fieldset>
    <h1>Archive Unzipper</h1>
    <label for="zipfile">Select .zip or .rar archive or .gz file you want to extract:</label>
    <select name="zipfile" size="1" class="select">
      <?php 
foreach ($unzipper->zipfiles as $zip) {
    echo "<option>{$zip}</option>";
}
?>
    </select>
    <label for="extpath">Extraction path (optional):</label>
    <input type="text" name="extpath" class="form-field" />
    <p class="info">Enter extraction path without leading or trailing slashes (e.g. "mypath"). If left empty current directory will be used.</p>
    <input type="submit" name="dounzip" class="submit" value="Unzip Archive"/>
  </fieldset>

  <fieldset>
    <h1>Archive Zipper</h1>
    <label for="zippath">Path that should be zipped (optional):</label>
    <input type="text" name="zippath" class="form-field" />
    <p class="info">Enter path to be zipped without leading or trailing slashes (e.g. "zippath"). If left empty current directory will be used.</p>
    <input type="submit" name="dozip" class="submit" value="Zip Archive"/>
  </fieldset>
</form>
<p class="version">Unzipper version: <?php 
echo "0.1.1";
?></p>
</body>
</html>

Execution traces

data/traces/08cad40bad07557ef283883c4d3d3f3c_trace-1676246409.68.xt
Version: 3.1.0beta2
File format: 4
TRACE START [2023-02-12 22:00:35.577796]
1	0	1	0.000196	393528
1	3	0	0.000506	455272	{main}	1		/var/www/html/uploads/unzipper.php	0	0
2	4	0	0.000526	455272	define	0		/var/www/html/uploads/unzipper.php	13	2	'VERSION'	'0.1.1'
2	4	1	0.000542	455376
2	4	R			TRUE
2	5	0	0.000556	455304	microtime	0		/var/www/html/uploads/unzipper.php	15	1	TRUE
2	5	1	0.000569	455344
2	5	R			1676246409.6804
1		A						/var/www/html/uploads/unzipper.php	15	$timestart = 1676246409.6804
1		A						/var/www/html/uploads/unzipper.php	16	GLOBALS['status'] = []
2	6	0	0.000624	455384	Unzipper->__construct	1		/var/www/html/uploads/unzipper.php	18	0
3	7	0	0.000640	455384	opendir	0		/var/www/html/uploads/unzipper.php	45	1	'.'
3	7	1	0.000660	455776
3	7	R			resource(4) of type (stream)
2		A						/var/www/html/uploads/unzipper.php	45	$dh = resource(4) of type (stream)
3	8	0	0.000689	455744	readdir	0		/var/www/html/uploads/unzipper.php	46	1	resource(4) of type (stream)
3	8	1	0.000709	455824
3	8	R			'unzipper.php'
2		A						/var/www/html/uploads/unzipper.php	46	$file = 'unzipper.php'
3	9	0	0.000736	455784	pathinfo	0		/var/www/html/uploads/unzipper.php	47	2	'unzipper.php'	4
3	9	1	0.000752	455880
3	9	R			'php'
3	10	0	0.000765	455784	pathinfo	0		/var/www/html/uploads/unzipper.php	48	2	'unzipper.php'	4
3	10	1	0.000780	455880
3	10	R			'php'
3	11	0	0.000793	455784	pathinfo	0		/var/www/html/uploads/unzipper.php	49	2	'unzipper.php'	4
3	11	1	0.000807	455880
3	11	R			'php'
3	12	0	0.000820	455784	readdir	0		/var/www/html/uploads/unzipper.php	46	1	resource(4) of type (stream)
3	12	1	0.000834	455856
3	12	R			'..'
2		A						/var/www/html/uploads/unzipper.php	46	$file = '..'
3	13	0	0.000858	455776	pathinfo	0		/var/www/html/uploads/unzipper.php	47	2	'..'	4
3	13	1	0.000871	455872
3	13	R			''
3	14	0	0.000884	455776	pathinfo	0		/var/www/html/uploads/unzipper.php	48	2	'..'	4
3	14	1	0.000896	455872
3	14	R			''
3	15	0	0.000909	455776	pathinfo	0		/var/www/html/uploads/unzipper.php	49	2	'..'	4
3	15	1	0.000922	455872
3	15	R			''
3	16	0	0.000934	455776	readdir	0		/var/www/html/uploads/unzipper.php	46	1	resource(4) of type (stream)
3	16	1	0.000948	455848
3	16	R			'.'
2		A						/var/www/html/uploads/unzipper.php	46	$file = '.'
3	17	0	0.000971	455776	pathinfo	0		/var/www/html/uploads/unzipper.php	47	2	'.'	4
3	17	1	0.000984	455872
3	17	R			''
3	18	0	0.000996	455776	pathinfo	0		/var/www/html/uploads/unzipper.php	48	2	'.'	4
3	18	1	0.001009	455872
3	18	R			''
3	19	0	0.001021	455776	pathinfo	0		/var/www/html/uploads/unzipper.php	49	2	'.'	4
3	19	1	0.001034	455872
3	19	R			''
3	20	0	0.001046	455776	readdir	0		/var/www/html/uploads/unzipper.php	46	1	resource(4) of type (stream)
3	20	1	0.001059	455856
3	20	R			'prepend.php'
2		A						/var/www/html/uploads/unzipper.php	46	$file = 'prepend.php'
3	21	0	0.001084	455784	pathinfo	0		/var/www/html/uploads/unzipper.php	47	2	'prepend.php'	4
3	21	1	0.001098	455880
3	21	R			'php'
3	22	0	0.001111	455784	pathinfo	0		/var/www/html/uploads/unzipper.php	48	2	'prepend.php'	4
3	22	1	0.001125	455880
3	22	R			'php'
3	23	0	0.001138	455784	pathinfo	0		/var/www/html/uploads/unzipper.php	49	2	'prepend.php'	4
3	23	1	0.001151	455880
3	23	R			'php'
3	24	0	0.001164	455784	readdir	0		/var/www/html/uploads/unzipper.php	46	1	resource(4) of type (stream)
3	24	1	0.001178	455856
3	24	R			'data'
2		A						/var/www/html/uploads/unzipper.php	46	$file = 'data'
3	25	0	0.001201	455776	pathinfo	0		/var/www/html/uploads/unzipper.php	47	2	'data'	4
3	25	1	0.001213	455840
3	25	R			''
3	26	0	0.001226	455776	pathinfo	0		/var/www/html/uploads/unzipper.php	48	2	'data'	4
3	26	1	0.001238	455840
3	26	R			''
3	27	0	0.001251	455776	pathinfo	0		/var/www/html/uploads/unzipper.php	49	2	'data'	4
3	27	1	0.001264	455840
3	27	R			''
3	28	0	0.001276	455776	readdir	0		/var/www/html/uploads/unzipper.php	46	1	resource(4) of type (stream)
3	28	1	0.001295	455856
3	28	R			'.htaccess'
2		A						/var/www/html/uploads/unzipper.php	46	$file = '.htaccess'
3	29	0	0.001321	455784	pathinfo	0		/var/www/html/uploads/unzipper.php	47	2	'.htaccess'	4
3	29	1	0.001334	455888
3	29	R			'htaccess'
3	30	0	0.001348	455784	pathinfo	0		/var/www/html/uploads/unzipper.php	48	2	'.htaccess'	4
3	30	1	0.001362	455888
3	30	R			'htaccess'
3	31	0	0.001375	455784	pathinfo	0		/var/www/html/uploads/unzipper.php	49	2	'.htaccess'	4
3	31	1	0.001388	455888
3	31	R			'htaccess'
3	32	0	0.001401	455784	readdir	0		/var/www/html/uploads/unzipper.php	46	1	resource(4) of type (stream)
3	32	1	0.001415	455824
3	32	R			FALSE
2		A						/var/www/html/uploads/unzipper.php	46	$file = FALSE
3	33	0	0.001439	455744	closedir	0		/var/www/html/uploads/unzipper.php	54	1	resource(4) of type (stream)
3	33	1	0.001455	455560
3	33	R			NULL
2		A						/var/www/html/uploads/unzipper.php	60	GLOBALS['status'] = ['info' => 'No .zip or .gz or rar files found. So only zipping functionality available.']
2	6	1	0.001487	455496
1		A						/var/www/html/uploads/unzipper.php	18	$unzipper = class Unzipper { public $localdir = '.'; public $zipfiles = [] }
2	34	0	0.001571	455872	microtime	0		/var/www/html/uploads/unzipper.php	33	1	TRUE
2	34	1	0.001585	455912
2	34	R			1676246409.6814
1		A						/var/www/html/uploads/unzipper.php	33	$timeend = 1676246409.6814
2	35	0	0.001611	455872	round	0		/var/www/html/uploads/unzipper.php	34	2	0.0010170936584473	4
2	35	1	0.001625	455944
2	35	R			0.001
1		A						/var/www/html/uploads/unzipper.php	34	$time = 0.001
2	36	0	0.001650	455848	key	0		/var/www/html/uploads/unzipper.php	392	1	['info' => 'No .zip or .gz or rar files found. So only zipping functionality available.']
2	36	1	0.001667	455880
2	36	R			'info'
2	37	0	0.001681	455848	strtoupper	0		/var/www/html/uploads/unzipper.php	392	1	'info'
2	37	1	0.001694	455912
2	37	R			'INFO'
2	38	0	0.001708	455872	reset	0		/var/www/html/uploads/unzipper.php	393	1	['info' => 'No .zip or .gz or rar files found. So only zipping functionality available.']
2	38	1	0.001725	456280
2	38	R			'No .zip or .gz or rar files found. So only zipping functionality available.'
1	3	1	0.001746	456248
			0.001776	361584
TRACE END   [2023-02-12 22:00:35.579415]

data/traces/08cad40bad07557ef283883c4d3d3f3c_trace-1676248214.2486.xt
Version: 3.1.0beta2
File format: 4
TRACE START [2023-02-12 22:30:40.146415]
1	0	1	0.000137	393528
1	3	0	0.000427	455272	{main}	1		/var/www/html/uploads/unzipper.phtml	0	0
2	4	0	0.000444	455272	define	0		/var/www/html/uploads/unzipper.phtml	13	2	'VERSION'	'0.1.1'
2	4	1	0.000460	455376
2	4	R			TRUE
2	5	0	0.000474	455304	microtime	0		/var/www/html/uploads/unzipper.phtml	15	1	TRUE
2	5	1	0.000487	455344
2	5	R			1676248214.249
1		A						/var/www/html/uploads/unzipper.phtml	15	$timestart = 1676248214.249
1		A						/var/www/html/uploads/unzipper.phtml	16	GLOBALS['status'] = []
2	6	0	0.000529	455384	Unzipper->__construct	1		/var/www/html/uploads/unzipper.phtml	18	0
3	7	0	0.000544	455384	opendir	0		/var/www/html/uploads/unzipper.phtml	45	1	'.'
3	7	1	0.000564	455776
3	7	R			resource(4) of type (stream)
2		A						/var/www/html/uploads/unzipper.phtml	45	$dh = resource(4) of type (stream)
3	8	0	0.000591	455744	readdir	0		/var/www/html/uploads/unzipper.phtml	46	1	resource(4) of type (stream)
3	8	1	0.000611	455816
3	8	R			'..'
2		A						/var/www/html/uploads/unzipper.phtml	46	$file = '..'
3	9	0	0.000635	455776	pathinfo	0		/var/www/html/uploads/unzipper.phtml	47	2	'..'	4
3	9	1	0.000650	455872
3	9	R			''
3	10	0	0.000663	455776	pathinfo	0		/var/www/html/uploads/unzipper.phtml	48	2	'..'	4
3	10	1	0.000677	455872
3	10	R			''
3	11	0	0.000690	455776	pathinfo	0		/var/www/html/uploads/unzipper.phtml	49	2	'..'	4
3	11	1	0.000702	455872
3	11	R			''
3	12	0	0.000715	455776	readdir	0		/var/www/html/uploads/unzipper.phtml	46	1	resource(4) of type (stream)
3	12	1	0.000729	455848
3	12	R			'.'
2		A						/var/www/html/uploads/unzipper.phtml	46	$file = '.'
3	13	0	0.000752	455776	pathinfo	0		/var/www/html/uploads/unzipper.phtml	47	2	'.'	4
3	13	1	0.000764	455872
3	13	R			''
3	14	0	0.000777	455776	pathinfo	0		/var/www/html/uploads/unzipper.phtml	48	2	'.'	4
3	14	1	0.000790	455872
3	14	R			''
3	15	0	0.000802	455776	pathinfo	0		/var/www/html/uploads/unzipper.phtml	49	2	'.'	4
3	15	1	0.000815	455872
3	15	R			''
3	16	0	0.000827	455776	readdir	0		/var/www/html/uploads/unzipper.phtml	46	1	resource(4) of type (stream)
3	16	1	0.000841	455856
3	16	R			'prepend.php'
2		A						/var/www/html/uploads/unzipper.phtml	46	$file = 'prepend.php'
3	17	0	0.000865	455784	pathinfo	0		/var/www/html/uploads/unzipper.phtml	47	2	'prepend.php'	4
3	17	1	0.000878	455880
3	17	R			'php'
3	18	0	0.000891	455784	pathinfo	0		/var/www/html/uploads/unzipper.phtml	48	2	'prepend.php'	4
3	18	1	0.000904	455880
3	18	R			'php'
3	19	0	0.000916	455784	pathinfo	0		/var/www/html/uploads/unzipper.phtml	49	2	'prepend.php'	4
3	19	1	0.000929	455880
3	19	R			'php'
3	20	0	0.000942	455784	readdir	0		/var/www/html/uploads/unzipper.phtml	46	1	resource(4) of type (stream)
3	20	1	0.000956	455856
3	20	R			'data'
2		A						/var/www/html/uploads/unzipper.phtml	46	$file = 'data'
3	21	0	0.000978	455776	pathinfo	0		/var/www/html/uploads/unzipper.phtml	47	2	'data'	4
3	21	1	0.000991	455840
3	21	R			''
3	22	0	0.001003	455776	pathinfo	0		/var/www/html/uploads/unzipper.phtml	48	2	'data'	4
3	22	1	0.001016	455840
3	22	R			''
3	23	0	0.001028	455776	pathinfo	0		/var/www/html/uploads/unzipper.phtml	49	2	'data'	4
3	23	1	0.001040	455840
3	23	R			''
3	24	0	0.001052	455776	readdir	0		/var/www/html/uploads/unzipper.phtml	46	1	resource(4) of type (stream)
3	24	1	0.001066	455856
3	24	R			'.htaccess'
2		A						/var/www/html/uploads/unzipper.phtml	46	$file = '.htaccess'
3	25	0	0.001089	455784	pathinfo	0		/var/www/html/uploads/unzipper.phtml	47	2	'.htaccess'	4
3	25	1	0.001103	455888
3	25	R			'htaccess'
3	26	0	0.001116	455784	pathinfo	0		/var/www/html/uploads/unzipper.phtml	48	2	'.htaccess'	4
3	26	1	0.001129	455888
3	26	R			'htaccess'
3	27	0	0.001141	455784	pathinfo	0		/var/www/html/uploads/unzipper.phtml	49	2	'.htaccess'	4
3	27	1	0.001154	455888
3	27	R			'htaccess'
3	28	0	0.001167	455784	readdir	0		/var/www/html/uploads/unzipper.phtml	46	1	resource(4) of type (stream)
3	28	1	0.001185	455864
3	28	R			'unzipper.phtml'
2		A						/var/www/html/uploads/unzipper.phtml	46	$file = 'unzipper.phtml'
3	29	0	0.001210	455784	pathinfo	0		/var/www/html/uploads/unzipper.phtml	47	2	'unzipper.phtml'	4
3	29	1	0.001224	455880
3	29	R			'phtml'
3	30	0	0.001237	455784	pathinfo	0		/var/www/html/uploads/unzipper.phtml	48	2	'unzipper.phtml'	4
3	30	1	0.001251	455880
3	30	R			'phtml'
3	31	0	0.001264	455784	pathinfo	0		/var/www/html/uploads/unzipper.phtml	49	2	'unzipper.phtml'	4
3	31	1	0.001277	455880
3	31	R			'phtml'
3	32	0	0.001290	455784	readdir	0		/var/www/html/uploads/unzipper.phtml	46	1	resource(4) of type (stream)
3	32	1	0.001304	455824
3	32	R			FALSE
2		A						/var/www/html/uploads/unzipper.phtml	46	$file = FALSE
3	33	0	0.001327	455744	closedir	0		/var/www/html/uploads/unzipper.phtml	54	1	resource(4) of type (stream)
3	33	1	0.001343	455560
3	33	R			NULL
2		A						/var/www/html/uploads/unzipper.phtml	60	GLOBALS['status'] = ['info' => 'No .zip or .gz or rar files found. So only zipping functionality available.']
2	6	1	0.001380	455496
1		A						/var/www/html/uploads/unzipper.phtml	18	$unzipper = class Unzipper { public $localdir = '.'; public $zipfiles = [] }
2	34	0	0.001408	455872	microtime	0		/var/www/html/uploads/unzipper.phtml	33	1	TRUE
2	34	1	0.001423	455912
2	34	R			1676248214.2499
1		A						/var/www/html/uploads/unzipper.phtml	33	$timeend = 1676248214.2499
2	35	0	0.001448	455872	round	0		/var/www/html/uploads/unzipper.phtml	34	2	0.00093603134155273	4
2	35	1	0.001527	455944
2	35	R			0.0009
1		A						/var/www/html/uploads/unzipper.phtml	34	$time = 0.0009
2	36	0	0.001555	455848	key	0		/var/www/html/uploads/unzipper.phtml	392	1	['info' => 'No .zip or .gz or rar files found. So only zipping functionality available.']
2	36	1	0.001573	455880
2	36	R			'info'
2	37	0	0.001586	455848	strtoupper	0		/var/www/html/uploads/unzipper.phtml	392	1	'info'
2	37	1	0.001599	455912
2	37	R			'INFO'
2	38	0	0.001613	455872	reset	0		/var/www/html/uploads/unzipper.phtml	393	1	['info' => 'No .zip or .gz or rar files found. So only zipping functionality available.']
2	38	1	0.001629	456280
2	38	R			'No .zip or .gz or rar files found. So only zipping functionality available.'
1	3	1	0.001651	456248
			0.001679	361584
TRACE END   [2023-02-12 22:30:40.147982]


Generated HTML code

<html><head>
  <title>File Unzipper + Zipper</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <style type="text/css">
    <!--
    body {
      font-family: Arial, sans-serif;
      line-height: 150%;
    }

    label {
      display: block;
      margin-top: 20px;
    }

    fieldset {
      border: 0;
      background-color: #EEE;
      margin: 10px 0 10px 0;
    }

    .select {
      padding: 5px;
      font-size: 110%;
    }

    .status {
      margin: 0;
      margin-bottom: 20px;
      padding: 10px;
      font-size: 80%;
      background: #EEE;
      border: 1px dotted #DDD;
    }

    .status--ERROR {
      background-color: red;
      color: white;
      font-size: 120%;
    }

    .status--SUCCESS {
      background-color: green;
      font-weight: bold;
      color: white;
      font-size: 120%
    }

    .small {
      font-size: 0.7rem;
      font-weight: normal;
    }

    .version {
      font-size: 80%;
    }

    .form-field {
      border: 1px solid #AAA;
      padding: 8px;
      width: 280px;
    }

    .info {
      margin-top: 0;
      font-size: 80%;
      color: #777;
    }

    .submit {
      background-color: #378de5;
      border: 0;
      color: #ffffff;
      font-size: 15px;
      padding: 10px 24px;
      margin: 20px 0 20px 0;
      text-decoration: none;
    }

    .submit:hover {
      background-color: #2c6db2;
      cursor: pointer;
    }
    -->
  </style>
</head>
<body>
<p class="status status--INFO">
  Status: No .zip or .gz or rar files found. So only zipping functionality available.<br>
  <span class="small">Processing Time: 0.0001 seconds</span>
</p>
<form action="" method="POST">
  <fieldset>
    <h1>Archive Unzipper</h1>
    <label for="zipfile">Select .zip or .rar archive or .gz file you want to extract:</label>
    <select name="zipfile" size="1" class="select">
          </select>
    <label for="extpath">Extraction path (optional):</label>
    <input type="text" name="extpath" class="form-field">
    <p class="info">Enter extraction path without leading or trailing slashes (e.g. "mypath"). If left empty current directory will be used.</p>
    <input type="submit" name="dounzip" class="submit" value="Unzip Archive">
  </fieldset>

  <fieldset>
    <h1>Archive Zipper</h1>
    <label for="zippath">Path that should be zipped (optional):</label>
    <input type="text" name="zippath" class="form-field">
    <p class="info">Enter path to be zipped without leading or trailing slashes (e.g. "zippath"). If left empty current directory will be used.</p>
    <input type="submit" name="dozip" class="submit" value="Zip Archive">
  </fieldset>
</form>
<p class="version">Unzipper version: 0.1.1</p>


</body></html>

Original PHP code

<?php
/**
 * The Unzipper extracts .zip or .rar archives and .gz files on webservers.
 * It's handy if you do not have shell access. E.g. if you want to upload a lot
 * of files (php framework or image collection) as an archive to save time.
 * As of version 0.1.0 it also supports creating archives.
 *
 * @author  Andreas Tasch, at[tec], attec.at
 * @license GNU GPL v3
 * @package attec.toolbox
 * @version 0.1.1
 */
define('VERSION', '0.1.1');

$timestart = microtime(TRUE);
$GLOBALS['status'] = array();

$unzipper = new Unzipper;
if (isset($_POST['dounzip'])) {
  // Check if an archive was selected for unzipping.
  $archive = isset($_POST['zipfile']) ? strip_tags($_POST['zipfile']) : '';
  $destination = isset($_POST['extpath']) ? strip_tags($_POST['extpath']) : '';
  $unzipper->prepareExtraction($archive, $destination);
}

if (isset($_POST['dozip'])) {
  $zippath = !empty($_POST['zippath']) ? strip_tags($_POST['zippath']) : '.';
  // Resulting zipfile e.g. zipper--2016-07-23--11-55.zip.
  $zipfile = 'zipper-' . date("Y-m-d--H-i") . '.zip';
  Zipper::zipDir($zippath, $zipfile);
}

$timeend = microtime(TRUE);
$time = round($timeend - $timestart, 4);

/**
 * Class Unzipper
 */
class Unzipper {
  public $localdir = '.';
  public $zipfiles = array();

  public function __construct() {
    // Read directory and pick .zip, .rar and .gz files.
    if ($dh = opendir($this->localdir)) {
      while (($file = readdir($dh)) !== FALSE) {
        if (pathinfo($file, PATHINFO_EXTENSION) === 'zip'
          || pathinfo($file, PATHINFO_EXTENSION) === 'gz'
          || pathinfo($file, PATHINFO_EXTENSION) === 'rar'
        ) {
          $this->zipfiles[] = $file;
        }
      }
      closedir($dh);

      if (!empty($this->zipfiles)) {
        $GLOBALS['status'] = array('info' => '.zip or .gz or .rar files found, ready for extraction');
      }
      else {
        $GLOBALS['status'] = array('info' => 'No .zip or .gz or rar files found. So only zipping functionality available.');
      }
    }
  }

  /**
   * Prepare and check zipfile for extraction.
   *
   * @param string $archive
   *   The archive name including file extension. E.g. my_archive.zip.
   * @param string $destination
   *   The relative destination path where to extract files.
   */
  public function prepareExtraction($archive, $destination = '') {
    // Determine paths.
    if (empty($destination)) {
      $extpath = $this->localdir;
    }
    else {
      $extpath = $this->localdir . '/' . $destination;
      // Todo: move this to extraction function.
      if (!is_dir($extpath)) {
        mkdir($extpath);
      }
    }
    // Only local existing archives are allowed to be extracted.
    if (in_array($archive, $this->zipfiles)) {
      self::extract($archive, $extpath);
    }
  }

  /**
   * Checks file extension and calls suitable extractor functions.
   *
   * @param string $archive
   *   The archive name including file extension. E.g. my_archive.zip.
   * @param string $destination
   *   The relative destination path where to extract files.
   */
  public static function extract($archive, $destination) {
    $ext = pathinfo($archive, PATHINFO_EXTENSION);
    switch ($ext) {
      case 'zip':
        self::extractZipArchive($archive, $destination);
        break;
      case 'gz':
        self::extractGzipFile($archive, $destination);
        break;
      case 'rar':
        self::extractRarArchive($archive, $destination);
        break;
    }

  }

  /**
   * Decompress/extract a zip archive using ZipArchive.
   *
   * @param $archive
   * @param $destination
   */
  public static function extractZipArchive($archive, $destination) {
    // Check if webserver supports unzipping.
    if (!class_exists('ZipArchive')) {
      $GLOBALS['status'] = array('error' => 'Error: Your PHP version does not support unzip functionality.');
      return;
    }

    $zip = new ZipArchive;

    // Check if archive is readable.
    if ($zip->open($archive) === TRUE) {
      // Check if destination is writable
      if (is_writeable($destination . '/')) {
        $zip->extractTo($destination);
        $zip->close();
        $GLOBALS['status'] = array('success' => 'Files unzipped successfully');
      }
      else {
        $GLOBALS['status'] = array('error' => 'Error: Directory not writeable by webserver.');
      }
    }
    else {
      $GLOBALS['status'] = array('error' => 'Error: Cannot read .zip archive.');
    }
  }

  /**
   * Decompress a .gz File.
   *
   * @param string $archive
   *   The archive name including file extension. E.g. my_archive.zip.
   * @param string $destination
   *   The relative destination path where to extract files.
   */
  public static function extractGzipFile($archive, $destination) {
    // Check if zlib is enabled
    if (!function_exists('gzopen')) {
      $GLOBALS['status'] = array('error' => 'Error: Your PHP has no zlib support enabled.');
      return;
    }

    $filename = pathinfo($archive, PATHINFO_FILENAME);
    $gzipped = gzopen($archive, "rb");
    $file = fopen($destination . '/' . $filename, "w");

    while ($string = gzread($gzipped, 4096)) {
      fwrite($file, $string, strlen($string));
    }
    gzclose($gzipped);
    fclose($file);

    // Check if file was extracted.
    if (file_exists($destination . '/' . $filename)) {
      $GLOBALS['status'] = array('success' => 'File unzipped successfully.');

      // If we had a tar.gz file, let's extract that tar file.
      if (pathinfo($destination . '/' . $filename, PATHINFO_EXTENSION) == 'tar') {
        $phar = new PharData($destination . '/' . $filename);
        if ($phar->extractTo($destination)) {
          $GLOBALS['status'] = array('success' => 'Extracted tar.gz archive successfully.');
          // Delete .tar.
          unlink($destination . '/' . $filename);
        }
      }
    }
    else {
      $GLOBALS['status'] = array('error' => 'Error unzipping file.');
    }

  }

  /**
   * Decompress/extract a Rar archive using RarArchive.
   *
   * @param string $archive
   *   The archive name including file extension. E.g. my_archive.zip.
   * @param string $destination
   *   The relative destination path where to extract files.
   */
  public static function extractRarArchive($archive, $destination) {
    // Check if webserver supports unzipping.
    if (!class_exists('RarArchive')) {
      $GLOBALS['status'] = array('error' => 'Error: Your PHP version does not support .rar archive functionality. <a class="info" href="http://php.net/manual/en/rar.installation.php" target="_blank">How to install RarArchive</a>');
      return;
    }
    // Check if archive is readable.
    if ($rar = RarArchive::open($archive)) {
      // Check if destination is writable
      if (is_writeable($destination . '/')) {
        $entries = $rar->getEntries();
        foreach ($entries as $entry) {
          $entry->extract($destination);
        }
        $rar->close();
        $GLOBALS['status'] = array('success' => 'Files extracted successfully.');
      }
      else {
        $GLOBALS['status'] = array('error' => 'Error: Directory not writeable by webserver.');
      }
    }
    else {
      $GLOBALS['status'] = array('error' => 'Error: Cannot read .rar archive.');
    }
  }

}

/**
 * Class Zipper
 *
 * Copied and slightly modified from http://at2.php.net/manual/en/class.ziparchive.php#110719
 * @author umbalaconmeogia
 */
class Zipper {
  /**
   * Add files and sub-directories in a folder to zip file.
   *
   * @param string $folder
   *   Path to folder that should be zipped.
   *
   * @param ZipArchive $zipFile
   *   Zipfile where files end up.
   *
   * @param int $exclusiveLength
   *   Number of text to be exclusived from the file path.
   */
  private static function folderToZip($folder, &$zipFile, $exclusiveLength) {
    $handle = opendir($folder);

    while (FALSE !== $f = readdir($handle)) {
      // Check for local/parent path or zipping file itself and skip.
      if ($f != '.' && $f != '..' && $f != basename(__FILE__)) {
        $filePath = "$folder/$f";
        // Remove prefix from file path before add to zip.
        $localPath = substr($filePath, $exclusiveLength);

        if (is_file($filePath)) {
          $zipFile->addFile($filePath, $localPath);
        }
        elseif (is_dir($filePath)) {
          // Add sub-directory.
          $zipFile->addEmptyDir($localPath);
          self::folderToZip($filePath, $zipFile, $exclusiveLength);
        }
      }
    }
    closedir($handle);
  }

  /**
   * Zip a folder (including itself).
   *
   * Usage:
   *   Zipper::zipDir('path/to/sourceDir', 'path/to/out.zip');
   *
   * @param string $sourcePath
   *   Relative path of directory to be zipped.
   *
   * @param string $outZipPath
   *   Relative path of the resulting output zip file.
   */
  public static function zipDir($sourcePath, $outZipPath) {
    $pathInfo = pathinfo($sourcePath);
    $parentPath = $pathInfo['dirname'];
    $dirName = $pathInfo['basename'];

    $z = new ZipArchive();
    $z->open($outZipPath, ZipArchive::CREATE);
    $z->addEmptyDir($dirName);
    if ($sourcePath == $dirName) {
      self::folderToZip($sourcePath, $z, 0);
    }
    else {
      self::folderToZip($sourcePath, $z, strlen("$parentPath/"));
    }
    $z->close();

    $GLOBALS['status'] = array('success' => 'Successfully created archive ' . $outZipPath);
  }
}
?>

<!DOCTYPE html>
<html>
<head>
  <title>File Unzipper + Zipper</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <style type="text/css">
    <!--
    body {
      font-family: Arial, sans-serif;
      line-height: 150%;
    }

    label {
      display: block;
      margin-top: 20px;
    }

    fieldset {
      border: 0;
      background-color: #EEE;
      margin: 10px 0 10px 0;
    }

    .select {
      padding: 5px;
      font-size: 110%;
    }

    .status {
      margin: 0;
      margin-bottom: 20px;
      padding: 10px;
      font-size: 80%;
      background: #EEE;
      border: 1px dotted #DDD;
    }

    .status--ERROR {
      background-color: red;
      color: white;
      font-size: 120%;
    }

    .status--SUCCESS {
      background-color: green;
      font-weight: bold;
      color: white;
      font-size: 120%
    }

    .small {
      font-size: 0.7rem;
      font-weight: normal;
    }

    .version {
      font-size: 80%;
    }

    .form-field {
      border: 1px solid #AAA;
      padding: 8px;
      width: 280px;
    }

    .info {
      margin-top: 0;
      font-size: 80%;
      color: #777;
    }

    .submit {
      background-color: #378de5;
      border: 0;
      color: #ffffff;
      font-size: 15px;
      padding: 10px 24px;
      margin: 20px 0 20px 0;
      text-decoration: none;
    }

    .submit:hover {
      background-color: #2c6db2;
      cursor: pointer;
    }
    -->
  </style>
</head>
<body>
<p class="status status--<?php echo strtoupper(key($GLOBALS['status'])); ?>">
  Status: <?php echo reset($GLOBALS['status']); ?><br/>
  <span class="small">Processing Time: <?php echo $time; ?> seconds</span>
</p>
<form action="" method="POST">
  <fieldset>
    <h1>Archive Unzipper</h1>
    <label for="zipfile">Select .zip or .rar archive or .gz file you want to extract:</label>
    <select name="zipfile" size="1" class="select">
      <?php foreach ($unzipper->zipfiles as $zip) {
        echo "<option>$zip</option>";
      }
      ?>
    </select>
    <label for="extpath">Extraction path (optional):</label>
    <input type="text" name="extpath" class="form-field" />
    <p class="info">Enter extraction path without leading or trailing slashes (e.g. "mypath"). If left empty current directory will be used.</p>
    <input type="submit" name="dounzip" class="submit" value="Unzip Archive"/>
  </fieldset>

  <fieldset>
    <h1>Archive Zipper</h1>
    <label for="zippath">Path that should be zipped (optional):</label>
    <input type="text" name="zippath" class="form-field" />
    <p class="info">Enter path to be zipped without leading or trailing slashes (e.g. "zippath"). If left empty current directory will be used.</p>
    <input type="submit" name="dozip" class="submit" value="Zip Archive"/>
  </fieldset>
</form>
<p class="version">Unzipper version: <?php echo VERSION; ?></p>
</body>
</html>