feat: h5ai 0.30.0 with PHP 8.4.26 security patch for PHP2Shell
This commit is contained in:
+188
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
class Archive {
|
||||
const NULL_BYTE = "\0";
|
||||
|
||||
private static $SEGMENT_SIZE = 16777216; // 1024 * 1024 * 16 = 16MiB
|
||||
private static $TAR_PASSTHRU_CMD = 'cd [ROOTDIR] && tar --no-recursion -c -- [DIRS] [FILES]';
|
||||
private static $ZIP_PASSTHRU_CMD = 'cd [ROOTDIR] && zip - -- [FILES]';
|
||||
|
||||
private $context;
|
||||
private $base_path;
|
||||
private $dirs;
|
||||
private $files;
|
||||
|
||||
public function __construct($context) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function output($type, $base_href, $hrefs) {
|
||||
$this->base_path = $this->context->to_path($base_href);
|
||||
if (!$this->context->is_managed_path($this->base_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->dirs = [];
|
||||
$this->files = [];
|
||||
|
||||
$this->add_hrefs($hrefs);
|
||||
|
||||
if (count($this->dirs) === 0 && count($this->files) === 0) {
|
||||
if ($type === 'php-tar') {
|
||||
$this->add_dir($this->base_path, '/');
|
||||
} else {
|
||||
$this->add_dir($this->base_path, '.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === 'php-tar') {
|
||||
return $this->php_tar($this->dirs, $this->files);
|
||||
} elseif ($type === 'shell-tar') {
|
||||
return $this->shell_cmd(Archive::$TAR_PASSTHRU_CMD);
|
||||
} elseif ($type === 'shell-zip') {
|
||||
return $this->shell_cmd(Archive::$ZIP_PASSTHRU_CMD);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function shell_cmd($cmd) {
|
||||
$cmd = str_replace('[ROOTDIR]', escapeshellarg($this->base_path), $cmd);
|
||||
$cmd = str_replace('[DIRS]', count($this->dirs) ? implode(' ', array_map('escapeshellarg', $this->dirs)) : '', $cmd);
|
||||
$cmd = str_replace('[FILES]', count($this->files) ? implode(' ', array_map('escapeshellarg', $this->files)) : '', $cmd);
|
||||
try {
|
||||
Util::passthru_cmd($cmd);
|
||||
} catch (Exeption $err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function php_tar($dirs, $files) {
|
||||
$filesizes = [];
|
||||
$total_size = 512 * count($dirs);
|
||||
foreach (array_keys($files) as $real_file) {
|
||||
$size = filesize($real_file);
|
||||
|
||||
$filesizes[$real_file] = $size;
|
||||
$total_size += 512 + $size;
|
||||
if ($size % 512 != 0) {
|
||||
$total_size += 512 - ($size % 512);
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Length: ' . $total_size);
|
||||
|
||||
foreach ($dirs as $real_dir => $archived_dir) {
|
||||
echo $this->php_tar_header($archived_dir, 0, @filemtime($real_dir . DIRECTORY_SEPARATOR . '.'), 5);
|
||||
}
|
||||
|
||||
foreach ($files as $real_file => $archived_file) {
|
||||
$size = $filesizes[$real_file];
|
||||
|
||||
echo $this->php_tar_header($archived_file, $size, @filemtime($real_file), 0);
|
||||
$this->print_file($real_file);
|
||||
|
||||
if ($size % 512 != 0) {
|
||||
echo str_repeat(Archive::NULL_BYTE, 512 - ($size % 512));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function php_tar_header($filename, $size, $mtime, $type) {
|
||||
$name = substr(basename($filename), -99);
|
||||
$prefix = substr(Util::normalize_path(dirname($filename)), -154);
|
||||
if ($prefix === '.') {
|
||||
$prefix = '';
|
||||
}
|
||||
|
||||
$header =
|
||||
str_pad($name, 100, Archive::NULL_BYTE) // filename [100]
|
||||
. '0000755' . Archive::NULL_BYTE // file mode [8]
|
||||
. '0000000' . Archive::NULL_BYTE // uid [8]
|
||||
. '0000000' . Archive::NULL_BYTE // gid [8]
|
||||
. str_pad(decoct($size), 11, '0', STR_PAD_LEFT) . Archive::NULL_BYTE // file size [12]
|
||||
. str_pad(decoct($mtime), 11, '0', STR_PAD_LEFT) . Archive::NULL_BYTE // file modification time [12]
|
||||
. ' ' // checksum [8]
|
||||
. str_pad($type, 1) // file type [1]
|
||||
. str_repeat(Archive::NULL_BYTE, 100) // linkname [100]
|
||||
. 'ustar' . Archive::NULL_BYTE // magic [6]
|
||||
. '00' // version [2]
|
||||
. str_repeat(Archive::NULL_BYTE, 80) // uname, gname, defmajor, devminor [32 + 32 + 8 + 8]
|
||||
. str_pad($prefix, 155, Archive::NULL_BYTE) // filename [155]
|
||||
. str_repeat(Archive::NULL_BYTE, 12); // fill [12]
|
||||
assert(strlen($header) === 512);
|
||||
|
||||
// checksum
|
||||
$checksum = array_sum(array_map('ord', str_split($header)));
|
||||
$checksum = str_pad(decoct($checksum), 6, '0', STR_PAD_LEFT) . Archive::NULL_BYTE . ' ';
|
||||
$header = substr_replace($header, $checksum, 148, 8);
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
private function print_file($file) {
|
||||
// Send file content in segments to not hit PHP's memory limit (default: 128M)
|
||||
if ($fd = fopen($file, 'rb')) {
|
||||
while (!feof($fd)) {
|
||||
print fread($fd, Archive::$SEGMENT_SIZE);
|
||||
@ob_flush();
|
||||
@flush();
|
||||
}
|
||||
fclose($fd);
|
||||
}
|
||||
}
|
||||
|
||||
private function add_hrefs($hrefs) {
|
||||
if (!is_array($hrefs)) {
|
||||
$hrefs = array($hrefs);
|
||||
}
|
||||
|
||||
foreach ($hrefs as $href) {
|
||||
if (trim($href) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$href = Util::normalize_path($href, false);
|
||||
$d = dirname($href);
|
||||
$n = basename($href);
|
||||
|
||||
if ($this->context->is_managed_href($d) && !$this->context->is_hidden($n)) {
|
||||
|
||||
$real_file = $this->context->to_path($href);
|
||||
$archived_file = preg_replace('!^' . preg_quote(Util::normalize_path($this->base_path, true)) . '!', '', $real_file);
|
||||
|
||||
if (is_dir($real_file)) {
|
||||
$this->add_dir($real_file, $archived_file);
|
||||
} else {
|
||||
$this->add_file($real_file, $archived_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function add_file($real_file, $archived_file) {
|
||||
if (is_readable($real_file)) {
|
||||
$this->files[$real_file] = $archived_file;
|
||||
}
|
||||
}
|
||||
|
||||
private function add_dir($real_dir, $archived_dir) {
|
||||
if ($this->context->is_managed_path($real_dir)) {
|
||||
$this->dirs[$real_dir] = $archived_dir;
|
||||
|
||||
$files = $this->context->read_dir($real_dir);
|
||||
foreach ($files as $file) {
|
||||
$real_file = $real_dir . '/' . $file;
|
||||
$archived_file = $archived_dir . '/' . $file;
|
||||
|
||||
if (is_dir($real_file)) {
|
||||
$this->add_dir($real_file, $archived_file);
|
||||
} else {
|
||||
$this->add_file($real_file, $archived_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
class Custom {
|
||||
private static $EXTENSIONS = ['html', 'md'];
|
||||
private $context;
|
||||
|
||||
public function __construct($context) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
private function read_custom_file($path, $name, &$content, &$type) {
|
||||
$file_prefix = $this->context->get_setup()->get('FILE_PREFIX');
|
||||
|
||||
foreach (Custom::$EXTENSIONS as $ext) {
|
||||
$file = $path . '/' . $file_prefix . '.' . $name . '.' . $ext;
|
||||
if (is_readable($file)) {
|
||||
$content = file_get_contents($file);
|
||||
$type = $ext;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get_customizations($href) {
|
||||
if (!$this->context->query_option('custom.enabled', false)) {
|
||||
return [
|
||||
'header' => ['content' => null, 'type' => null],
|
||||
'footer' => ['content' => null, 'type' => null]
|
||||
];
|
||||
}
|
||||
|
||||
$root_path = $this->context->get_setup()->get('FILE_PREFIX');
|
||||
$path = $this->context->to_path($href);
|
||||
|
||||
$header = null;
|
||||
$header_type = null;
|
||||
$footer = null;
|
||||
$footer_type = null;
|
||||
|
||||
$this->read_custom_file($path, 'header', $header, $header_type);
|
||||
$this->read_custom_file($path, 'footer', $footer, $footer_type);
|
||||
|
||||
while ($header === null || $footer === null) {
|
||||
if ($header === null) {
|
||||
$this->read_custom_file($path, 'headers', $header, $header_type);
|
||||
}
|
||||
if ($footer === null) {
|
||||
$this->read_custom_file($path, 'footers', $footer, $footer_type);
|
||||
}
|
||||
if ($path === $root_path) {
|
||||
break;
|
||||
}
|
||||
$parent_path = Util::normalize_path(dirname($path));
|
||||
if ($parent_path === $path) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop once we reach the root
|
||||
if (
|
||||
$this->context->query_option('custom.stopSearchingAtRoot', true) &&
|
||||
$path === $this->context->get_setup()->get('ROOT_PATH')
|
||||
) {
|
||||
break;
|
||||
}
|
||||
$path = $parent_path;
|
||||
}
|
||||
|
||||
return [
|
||||
'header' => ['content' => $header, 'type' => $header_type],
|
||||
'footer' => ['content' => $footer, 'type' => $footer_type]
|
||||
];
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
class Search {
|
||||
private $context;
|
||||
|
||||
public function __construct($context) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function get_paths($root, $pattern = null, $ignorecase = false) {
|
||||
$paths = [];
|
||||
if ($pattern && $this->context->is_managed_path($root)) {
|
||||
$re = Util::wrap_pattern($pattern);
|
||||
if ($ignorecase) {
|
||||
$re .= 'i';
|
||||
}
|
||||
$names = $this->context->read_dir($root);
|
||||
foreach ($names as $name) {
|
||||
$path = $root . '/' . $name;
|
||||
if (preg_match($re, @basename($path))) {
|
||||
$paths[] = $path;
|
||||
}
|
||||
if (@is_dir($path)) {
|
||||
$paths = array_merge($paths, $this->get_paths($path, $pattern, $ignorecase));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $paths;
|
||||
}
|
||||
|
||||
public function get_items($href, $pattern = null, $ignorecase = false) {
|
||||
$cache = [];
|
||||
$root = $this->context->to_path($href);
|
||||
$paths = $this->get_paths($root, $pattern, $ignorecase);
|
||||
$items = array_map(function ($path) {
|
||||
return Item::get($this->context, $path, $cache)->to_json_object();
|
||||
}, $paths);
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
Executable
+253
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
class Thumb {
|
||||
private static $FFMPEG_CMDV = ['ffmpeg', '-ss', '0:00:10', '-i', '[SRC]', '-an', '-vframes', '1', '[DEST]'];
|
||||
private static $AVCONV_CMDV = ['avconv', '-ss', '0:00:10', '-i', '[SRC]', '-an', '-vframes', '1', '[DEST]'];
|
||||
private static $CONVERT_CMDV = ['convert', '-density', '200', '-quality', '100', '-strip', '[SRC][0]', '[DEST]'];
|
||||
private static $GM_CONVERT_CMDV = ['gm', 'convert', '-density', '200', '-quality', '100', '[SRC][0]', '[DEST]'];
|
||||
private static $THUMB_CACHE = 'thumbs';
|
||||
|
||||
private $context;
|
||||
private $setup;
|
||||
private $thumbs_path;
|
||||
private $thumbs_href;
|
||||
|
||||
public function __construct($context) {
|
||||
$this->context = $context;
|
||||
$this->setup = $context->get_setup();
|
||||
$this->thumbs_path = $this->setup->get('CACHE_PUB_PATH') . '/' . Thumb::$THUMB_CACHE;
|
||||
$this->thumbs_href = $this->setup->get('CACHE_PUB_HREF') . Thumb::$THUMB_CACHE;
|
||||
|
||||
if (!is_dir($this->thumbs_path)) {
|
||||
@mkdir($this->thumbs_path, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function thumb($type, $source_href, $width, $height) {
|
||||
$source_path = $this->context->to_path($source_href);
|
||||
if (!file_exists($source_path) || Util::starts_with($source_path, $this->setup->get('CACHE_PUB_PATH'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$capture_path = $source_path;
|
||||
if ($type === 'img') {
|
||||
$capture_path = $source_path;
|
||||
} elseif ($type === 'mov') {
|
||||
if ($this->setup->get('HAS_CMD_AVCONV')) {
|
||||
$capture_path = $this->capture(Thumb::$AVCONV_CMDV, $source_path);
|
||||
} elseif ($this->setup->get('HAS_CMD_FFMPEG')) {
|
||||
$capture_path = $this->capture(Thumb::$FFMPEG_CMDV, $source_path);
|
||||
}
|
||||
} elseif ($type === 'doc') {
|
||||
if ($this->setup->get('HAS_CMD_CONVERT')) {
|
||||
$capture_path = $this->capture(Thumb::$CONVERT_CMDV, $source_path);
|
||||
} elseif ($this->setup->get('HAS_CMD_GM')) {
|
||||
$capture_path = $this->capture(Thumb::$GM_CONVERT_CMDV, $source_path);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->thumb_href($capture_path, $width, $height);
|
||||
}
|
||||
|
||||
private function thumb_href($source_path, $width, $height) {
|
||||
if (!file_exists($source_path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = 'thumb-' . sha1($source_path) . '-' . $width . 'x' . $height . '.jpg';
|
||||
$thumb_path = $this->thumbs_path . '/' . $name;
|
||||
$thumb_href = $this->thumbs_href . '/' . $name;
|
||||
|
||||
if (!file_exists($thumb_path) || filemtime($source_path) >= filemtime($thumb_path)) {
|
||||
$image = new Image();
|
||||
|
||||
$et = false;
|
||||
if ($this->setup->get('HAS_PHP_EXIF') && $this->context->query_option('thumbnails.exif', false) === true && $height != 0) {
|
||||
$et = @exif_thumbnail($source_path);
|
||||
}
|
||||
if($et !== false) {
|
||||
file_put_contents($thumb_path, $et);
|
||||
$image->set_source($thumb_path);
|
||||
$image->normalize_exif_orientation($source_path);
|
||||
} else {
|
||||
$image->set_source($source_path);
|
||||
}
|
||||
|
||||
$image->thumb($width, $height);
|
||||
$image->save_dest_jpeg($thumb_path, 80);
|
||||
}
|
||||
|
||||
return file_exists($thumb_path) ? $thumb_href : null;
|
||||
}
|
||||
|
||||
private function capture($cmdv, $source_path) {
|
||||
if (!file_exists($source_path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$capture_path = $this->thumbs_path . '/capture-' . sha1($source_path) . '.jpg';
|
||||
|
||||
if (!file_exists($capture_path) || filemtime($source_path) >= filemtime($capture_path)) {
|
||||
foreach ($cmdv as &$arg) {
|
||||
$arg = str_replace('[SRC]', $source_path, $arg);
|
||||
$arg = str_replace('[DEST]', $capture_path, $arg);
|
||||
}
|
||||
|
||||
Util::exec_cmdv($cmdv);
|
||||
}
|
||||
|
||||
return file_exists($capture_path) ? $capture_path : null;
|
||||
}
|
||||
}
|
||||
|
||||
class Image {
|
||||
private $source_file;
|
||||
private $source;
|
||||
private $width;
|
||||
private $height;
|
||||
private $type;
|
||||
private $dest;
|
||||
|
||||
public function __construct($filename = null) {
|
||||
$this->source_file = null;
|
||||
$this->source = null;
|
||||
$this->width = null;
|
||||
$this->height = null;
|
||||
$this->type = null;
|
||||
|
||||
$this->dest = null;
|
||||
|
||||
$this->set_source($filename);
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->release_source();
|
||||
$this->release_dest();
|
||||
}
|
||||
|
||||
public function set_source($filename) {
|
||||
$this->release_source();
|
||||
$this->release_dest();
|
||||
|
||||
if (is_null($filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->source_file = $filename;
|
||||
|
||||
list($this->width, $this->height, $this->type) = @getimagesize($this->source_file);
|
||||
|
||||
if (!$this->width || !$this->height) {
|
||||
$this->source_file = null;
|
||||
$this->width = null;
|
||||
$this->height = null;
|
||||
$this->type = null;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->source = imagecreatefromstring(file_get_contents($this->source_file));
|
||||
}
|
||||
|
||||
public function save_dest_jpeg($filename, $quality = 80) {
|
||||
if (!is_null($this->dest)) {
|
||||
@imagejpeg($this->dest, $filename, $quality);
|
||||
@chmod($filename, 0775);
|
||||
}
|
||||
}
|
||||
|
||||
public function release_dest() {
|
||||
if (!is_null($this->dest)) {
|
||||
@imagedestroy($this->dest);
|
||||
$this->dest = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function release_source() {
|
||||
if (!is_null($this->source)) {
|
||||
@imagedestroy($this->source);
|
||||
$this->source_file = null;
|
||||
$this->source = null;
|
||||
$this->width = null;
|
||||
$this->height = null;
|
||||
$this->type = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function thumb($width, $height) {
|
||||
if (is_null($this->source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$src_r = 1.0 * $this->width / $this->height;
|
||||
|
||||
if ($height == 0) {
|
||||
if ($src_r >= 1) {
|
||||
$height = 1.0 * $width / $src_r;
|
||||
} else {
|
||||
$height = $width;
|
||||
$width = 1.0 * $height * $src_r;
|
||||
}
|
||||
if ($width > $this->width) {
|
||||
$width = $this->width;
|
||||
$height = $this->height;
|
||||
}
|
||||
}
|
||||
|
||||
$ratio = 1.0 * $width / $height;
|
||||
|
||||
if ($src_r <= $ratio) {
|
||||
$src_w = $this->width;
|
||||
$src_h = $src_w / $ratio;
|
||||
$src_x = 0;
|
||||
} else {
|
||||
$src_h = $this->height;
|
||||
$src_w = $src_h * $ratio;
|
||||
$src_x = 0.5 * ($this->width - $src_w);
|
||||
}
|
||||
|
||||
$width = intval($width);
|
||||
$height = intval($height);
|
||||
$src_x = intval($src_x);
|
||||
$src_w = intval($src_w);
|
||||
$src_h = intval($src_h);
|
||||
|
||||
$this->dest = imagecreatetruecolor($width, $height);
|
||||
$icol = imagecolorallocate($this->dest, 255, 255, 255);
|
||||
imagefill($this->dest, 0, 0, $icol);
|
||||
imagecopyresampled($this->dest, $this->source, 0, 0, $src_x, 0, $width, $height, $src_w, $src_h);
|
||||
}
|
||||
|
||||
public function rotate($angle) {
|
||||
if (is_null($this->source) || ($angle !== 90 && $angle !== 180 && $angle !== 270)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->source = imagerotate($this->source, $angle, 0);
|
||||
if ( $angle === 90 || $angle === 270 ) {
|
||||
list($this->width, $this->height) = [$this->height, $this->width];
|
||||
}
|
||||
}
|
||||
|
||||
public function normalize_exif_orientation($exif_source_file = null) {
|
||||
if (is_null($this->source) || !function_exists('exif_read_data')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($exif_source_file === null) {
|
||||
$exif_source_file = $this->source_file;
|
||||
}
|
||||
|
||||
$exif = exif_read_data($exif_source_file);
|
||||
switch (@$exif['Orientation']) {
|
||||
case 3:
|
||||
$this->rotate(180);
|
||||
break;
|
||||
case 6:
|
||||
$this->rotate(270);
|
||||
break;
|
||||
case 8:
|
||||
$this->rotate(90);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user