Files
SODEW/sodew-bash-main/assets/wordpress/__test.php
T
2026-08-18 09:40:08 +02:00

647 lines
20 KiB
PHP

<?php
declare(strict_types=1);
header('Content-Type: text/plain; charset=utf-8');
$SCORE = ['PASS' => 0, 'WARN' => 0, 'FAIL' => 0];
$FINDINGS = [];
function add_result(string $level, string $title, string $detail = ''): void {
global $SCORE, $FINDINGS;
if (!isset($SCORE[$level])) {
$level = 'WARN';
}
$SCORE[$level]++;
$FINDINGS[] = [$level, $title, $detail];
}
function line(string $label, $value = null): void {
if ($value === null) {
echo $label . PHP_EOL;
return;
}
if (is_bool($value)) {
$value = $value ? 'YES' : 'NO';
} elseif (is_array($value) || is_object($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
echo str_pad($label, 46) . ': ' . $value . PHP_EOL;
}
function section(string $title): void {
echo PHP_EOL . "--- {$title} ---" . PHP_EOL;
}
function bytes_from_ini(?string $val): ?int {
if ($val === null || $val === '') return null;
$val = trim($val);
if ($val === '-1') return -1;
$last = strtolower(substr($val, -1));
$num = (float)$val;
return match($last) {
'g' => (int)($num * 1024 * 1024 * 1024),
'm' => (int)($num * 1024 * 1024),
'k' => (int)($num * 1024),
default => (int)$num,
};
}
function with_error_capture(callable $fn): array {
$error = null;
set_error_handler(function($severity, $message) use (&$error) {
$error = $message;
return true;
});
try {
$result = $fn();
restore_error_handler();
return ['result' => $result, 'error' => $error];
} catch (Throwable $e) {
restore_error_handler();
return ['result' => null, 'error' => $e->getMessage()];
}
}
function try_read_file(string $path): array {
return with_error_capture(function() use ($path) {
$data = @file_get_contents($path);
if ($data === false) return false;
return 'len=' . strlen($data);
}) + ['path' => $path];
}
function try_scandir_path(string $path): array {
return with_error_capture(function() use ($path) {
$data = @scandir($path);
if ($data === false) return false;
return array_slice($data, 0, 15);
}) + ['path' => $path];
}
function try_socket(string $target, int $port, float $timeout = 1.2): array {
$errno = 0;
$errstr = '';
$fp = @fsockopen($target, $port, $errno, $errstr, $timeout);
$ok = is_resource($fp);
if ($ok) fclose($fp);
return [
'target' => $target,
'port' => $port,
'connected' => $ok,
'errno' => $errno,
'errstr' => $errstr,
];
}
function try_unix_socket(string $path, float $timeout = 1.0): array {
$errno = 0;
$errstr = '';
$fp = @stream_socket_client('unix://' . $path, $errno, $errstr, $timeout);
$ok = is_resource($fp);
if ($ok) fclose($fp);
return [
'path' => $path,
'connected' => $ok,
'errno' => $errno,
'errstr' => $errstr,
];
}
echo "=== SHARED HOSTING SAFE AUDIT v2.1 ===" . PHP_EOL;
echo "Time: " . date('c') . PHP_EOL;
$docRoot = realpath($_SERVER['DOCUMENT_ROOT'] ?? getcwd()) ?: getcwd();
$scriptFile = $_SERVER['SCRIPT_FILENAME'] ?? __FILE__;
$baseTmp = $docRoot . '/.audit_tmp_' . getmypid() . '_' . mt_rand(1000, 9999);
@mkdir($baseTmp, 0700, true);
section('Runtime identity');
line('PHP version', PHP_VERSION);
line('SAPI', PHP_SAPI);
line('OS', PHP_OS_FAMILY);
line('Document root', $docRoot);
line('Script filename', $scriptFile);
line('Current dir', getcwd());
line('Loaded php.ini', php_ini_loaded_file() ?: 'none');
line('Additional .ini files', php_ini_scanned_files() ?: 'none');
line('Hostname', php_uname('n'));
line('Server software', $_SERVER['SERVER_SOFTWARE'] ?? 'n/a');
line('Server addr', $_SERVER['SERVER_ADDR'] ?? 'n/a');
line('Server port', $_SERVER['SERVER_PORT'] ?? 'n/a');
line('Remote addr', $_SERVER['REMOTE_ADDR'] ?? 'n/a');
section('PHP limits and config');
$iniKeys = [
'memory_limit',
'max_execution_time',
'max_input_time',
'max_input_vars',
'post_max_size',
'upload_max_filesize',
'open_basedir',
'disable_functions',
'disable_classes',
'user_ini.filename',
'user_ini.cache_ttl',
'file_uploads',
'allow_url_fopen',
'allow_url_include',
'session.save_path',
'upload_tmp_dir',
'sys_temp_dir',
'display_errors',
'log_errors',
'expose_php',
'mail.add_x_header',
'mysqli.default_socket',
'pdo_mysql.default_socket',
];
foreach ($iniKeys as $k) {
line($k, ini_get($k));
}
section('Dangerous functions present');
$dangerFns = [
'exec','shell_exec','system','passthru','proc_open','popen',
'pcntl_exec','pcntl_fork','putenv','mail','symlink','link',
'stream_socket_client','fsockopen'
];
foreach ($dangerFns as $fn) {
line("function_exists($fn)", function_exists($fn));
}
section('Loaded extensions');
$exts = ['mysqli','pdo_mysql','redis','ftp','curl','openssl','pcntl','posix','sockets','imap','intl'];
foreach ($exts as $ext) {
line("extension_loaded($ext)", extension_loaded($ext));
}
section('Filesystem isolation');
$fsTests = [
$docRoot,
$docRoot . '/../',
$docRoot . '/../../',
'/var/www',
'/var/www/vhosts',
'/etc/passwd',
'/etc/hosts',
'/proc/self/environ',
'/proc/meminfo',
'/tmp',
'/var/tmp',
'/run',
'/run/php',
'/run/mysqld',
'/dev/shm',
'/var/spool/postfix',
];
foreach ($fsTests as $path) {
$isDir = @is_dir($path);
$result = $isDir ? try_scandir_path($path) : try_read_file($path);
line($path, $result);
}
section('Path traversal / realpath checks');
$traversalTests = [
$docRoot . '/../www',
$docRoot . '/../tmp',
$docRoot . '/../session',
$docRoot . '/../../../../etc/passwd',
$docRoot . '/../other-vhost/www',
];
foreach ($traversalTests as $path) {
line("realpath($path)", @realpath($path) ?: 'false');
line("read($path)", try_read_file($path));
}
section('Allowed path write tests');
$writeFile = $baseTmp . '/write-test.txt';
$res = with_error_capture(function() use ($writeFile) {
return file_put_contents($writeFile, "audit\n");
});
line('Write file in docroot tmp', ['path' => $writeFile] + $res);
line('File exists after write', file_exists($writeFile));
line('File readable after write', is_readable($writeFile));
line('File writable after write', is_writable($writeFile));
$renameTo = $baseTmp . '/write-test-renamed.txt';
$res = with_error_capture(function() use ($writeFile, $renameTo) {
return @rename($writeFile, $renameTo);
});
line('Rename inside allowed path', ['from' => $writeFile, 'to' => $renameTo] + $res);
$copyToSession = dirname($docRoot) . '/session/audit-copy.txt';
$res = with_error_capture(function() use ($renameTo, $copyToSession) {
return @copy($renameTo, $copyToSession);
});
line('Copy from docroot to session dir', ['to' => $copyToSession] + $res);
section('Symlink / hardlink inside allowed path');
$src = $baseTmp . '/src.txt';
@file_put_contents($src, 'x');
$symlinkTarget = $baseTmp . '/sym.txt';
$hardlinkTarget = $baseTmp . '/hard.txt';
$symlinkRes = with_error_capture(fn() => @symlink($src, $symlinkTarget));
$hardlinkRes = with_error_capture(fn() => @link($src, $hardlinkTarget));
line('Symlink allowed path', $symlinkRes);
line('Hardlink allowed path', $hardlinkRes);
line('Symlink exists', is_link($symlinkTarget));
line('Hardlink exists', file_exists($hardlinkTarget));
section('Runtime override attempts');
$overrideTests = [
'memory_limit' => '2048M',
'max_execution_time' => '600',
'upload_max_filesize' => '2048M',
'post_max_size' => '2048M',
'open_basedir' => '/',
];
$overrideResults = [];
foreach ($overrideTests as $key => $value) {
$before = ini_get($key);
$ret = @ini_set($key, $value);
$after = ini_get($key);
$overrideResults[$key] = [
'before' => $before,
'return' => $ret,
'after' => $after,
'changed' => ($before !== $after),
];
line("ini_set($key)", $overrideResults[$key]);
}
section('Execution capability tests');
$execResults = [];
if (function_exists('exec')) {
$execResults['exec'] = with_error_capture(function() {
$out = [];
$rc = 0;
@exec('id 2>&1', $out, $rc);
return ['rc' => $rc, 'out' => implode("\n", array_slice($out, 0, 5))];
});
line('exec("id")', $execResults['exec']);
}
if (function_exists('shell_exec')) {
$execResults['shell_exec'] = with_error_capture(function() {
$out = @shell_exec('whoami 2>&1');
return $out === null ? null : trim($out);
});
line('shell_exec("whoami")', $execResults['shell_exec']);
}
if (function_exists('system')) {
$execResults['system'] = with_error_capture(function() {
ob_start();
$rc = 0;
@system('pwd 2>&1', $rc);
$out = ob_get_clean();
return ['rc' => $rc, 'out' => trim((string)$out)];
});
line('system("pwd")', $execResults['system']);
}
if (function_exists('proc_open')) {
$execResults['proc_open'] = with_error_capture(function() {
$desc = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$proc = @proc_open('id', $desc, $pipes);
if (!is_resource($proc)) return 'proc_open failed';
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$code = proc_close($proc);
return ['rc' => $code, 'stdout' => trim($stdout), 'stderr' => trim($stderr)];
});
line('proc_open("id")', $execResults['proc_open']);
}
if (function_exists('popen')) {
$execResults['popen'] = with_error_capture(function() {
$h = @popen('id 2>&1', 'r');
if (!is_resource($h)) return 'popen failed';
$out = stream_get_contents($h);
$rc = pclose($h);
return ['rc' => $rc, 'out' => trim((string)$out)];
});
line('popen("id")', $execResults['popen']);
}
section('Fork capability');
line('extension_loaded(pcntl)', extension_loaded('pcntl'));
line('function_exists(pcntl_fork)', function_exists('pcntl_fork'));
line('Active fork test', 'SKIPPED in web SAPI for safety');
section('Controlled memory probe');
$memoryLimit = bytes_from_ini(ini_get('memory_limit'));
$chunks = [];
$allocated = 0;
$chunkSize = 4 * 1024 * 1024;
$target = ($memoryLimit !== null && $memoryLimit > 0) ? (int)($memoryLimit * 0.70) : 64 * 1024 * 1024;
$oom = false;
$oomMsg = null;
try {
while ($allocated + $chunkSize <= $target) {
$chunks[] = str_repeat('A', $chunkSize);
$allocated += $chunkSize;
}
} catch (Throwable $e) {
$oom = true;
$oomMsg = $e->getMessage();
}
line('memory_limit parsed', $memoryLimit);
line('allocated safely', $allocated);
line('oom caught', $oom);
line('oom message', $oomMsg ?: 'none');
unset($chunks);
section('Controlled CPU / timeout probe');
$start = microtime(true);
$iterations = 0;
$limitSeconds = max(1, (int)ini_get('max_execution_time'));
$softBudget = min(3, max(1, $limitSeconds - 1));
while ((microtime(true) - $start) < $softBudget) {
hash('sha256', random_bytes(256), false);
$iterations++;
}
line('elapsed', round(microtime(true) - $start, 3) . 's');
line('iterations', $iterations);
line('soft budget', $softBudget . 's');
section('set_time_limit test');
$setTimeLimitRes = with_error_capture(function() {
return @set_time_limit(600);
});
line('set_time_limit(600)', $setTimeLimitRes);
line('max_execution_time after set_time_limit', ini_get('max_execution_time'));
section('Network reachability');
$netTargets = [
['127.0.0.1', 25],
['127.0.0.1', 3306],
['127.0.0.1', 6379],
['127.0.0.1', 11211],
['127.0.0.1', 7080],
['127.0.0.1', 80],
['127.0.0.1', 443],
];
$netResults = [];
foreach ($netTargets as [$host, $port]) {
$netResults["$host:$port"] = try_socket($host, $port);
line("$host:$port", $netResults["$host:$port"]);
}
section('Unix socket reachability');
$unixCandidates = [
'/run/mysqld/mysqld.sock',
'/var/run/mysqld/mysqld.sock',
'/tmp/mysql.sock',
'/run/redis/redis-server.sock',
'/var/run/redis/redis.sock',
'/tmp/redis.sock',
'/usr/local/lsws/admin/tmp/admin.sock',
];
$unixResults = [];
foreach ($unixCandidates as $sock) {
$unixResults[$sock] = try_unix_socket($sock);
line($sock, $unixResults[$sock]);
}
section('Stream wrappers');
$wrappers = stream_get_wrappers();
sort($wrappers);
line('wrappers', $wrappers);
$wrapperReads = [
'php://memory',
'php://temp',
'data://text/plain;base64,SGVsbG8=',
];
foreach ($wrapperReads as $wrapper) {
line("read $wrapper", with_error_capture(function() use ($wrapper) {
$d = @file_get_contents($wrapper);
if ($d === false) return false;
return 'len=' . strlen($d) . ' data=' . substr($d, 0, 40);
}));
}
section('Include wrapper tests');
$includeFile = $baseTmp . '/include-test.php';
file_put_contents($includeFile, "<?php return ['ok' => true, 'time' => time()];");
$includeLocal = with_error_capture(function() use ($includeFile) {
return include $includeFile;
});
$includeData = with_error_capture(function() {
return @include 'data://text/plain;base64,PD9waHAgcmV0dXJuIFsiZGF0YSI9PnRydWVdOw==';
});
line('include local file', $includeLocal);
line('include data:// wrapper', $includeData);
section('Environment leakage');
$envKeys = ['HOME','USER','LOGNAME','PATH','TMPDIR','TEMP','HOSTNAME'];
$envOut = [];
foreach ($envKeys as $k) {
$envOut[$k] = getenv($k);
}
line('getenv selected', $envOut);
line('_ENV count', is_array($_ENV) ? count($_ENV) : 'n/a');
line('_SERVER selected', [
'PATH' => $_SERVER['PATH'] ?? null,
'USER' => $_SERVER['USER'] ?? null,
'HOME' => $_SERVER['HOME'] ?? null,
]);
section('Self-request capability');
$selfUrl = null;
if (!empty($_SERVER['HTTP_HOST'])) {
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$selfUrl = $scheme . '://' . $_SERVER['HTTP_HOST'] . ($_SERVER['REQUEST_URI'] ?? '/');
}
line('self url', $selfUrl ?: 'n/a');
if ($selfUrl && function_exists('file_get_contents')) {
line('self file_get_contents', with_error_capture(function() use ($selfUrl) {
$ctx = stream_context_create(['http' => ['timeout' => 2]]);
$data = @file_get_contents($selfUrl, false, $ctx);
if ($data === false) return false;
return 'len=' . strlen($data);
}));
}
section('Session basics');
$sessionRes = with_error_capture(function() {
if (session_status() !== PHP_SESSION_ACTIVE) {
@session_start();
}
$_SESSION['audit_test'] = 'ok';
return session_id();
});
line('session.save_path', ini_get('session.save_path'));
line('session_start()', $sessionRes);
line('session file expected', ini_get('session.save_path') . '/sess_' . session_id());
section('mail() basics');
line('function_exists(mail)', function_exists('mail'));
line('sendmail_path', ini_get('sendmail_path'));
line('mail() active send test', 'SKIPPED by design');
section('.user.ini verification hint');
$userIniFile = $docRoot . '/.user.ini.audit-test';
$userIniContent = <<<TXT
; Rename this file to .user.ini for a live test, then wait for user_ini.cache_ttl
memory_limit=3072M
max_execution_time=900
upload_max_filesize=3072M
post_max_size=3072M
auto_prepend_file=
TXT;
@file_put_contents($userIniFile, $userIniContent);
line('Prepared helper file', $userIniFile);
line('How to test .user.ini', 'Rename .user.ini.audit-test -> .user.ini, wait cache_ttl, reload script, compare values.');
section('Assessment');
$openBasedir = (string)ini_get('open_basedir');
$disableFunctions = (string)ini_get('disable_functions');
$userIni = (string)ini_get('user_ini.filename');
$allowUrlInclude = (string)ini_get('allow_url_include');
if ($openBasedir !== '') {
add_result('PASS', 'open_basedir is set', $openBasedir);
} else {
add_result('FAIL', 'open_basedir is empty');
}
if (!empty($overrideResults['memory_limit']['changed'])) {
add_result('FAIL', 'memory_limit can be changed via ini_set()', json_encode($overrideResults['memory_limit']));
} else {
add_result('PASS', 'memory_limit is not changeable via ini_set()');
}
if (!empty($overrideResults['max_execution_time']['changed'])) {
add_result('FAIL', 'max_execution_time can be changed via ini_set()', json_encode($overrideResults['max_execution_time']));
} else {
add_result('PASS', 'max_execution_time is not changeable via ini_set()');
}
if (!empty($overrideResults['upload_max_filesize']['changed'])) {
add_result('FAIL', 'upload_max_filesize can be changed via ini_set()', json_encode($overrideResults['upload_max_filesize']));
} else {
add_result('PASS', 'upload_max_filesize resisted ini_set()');
}
if (!empty($overrideResults['post_max_size']['changed'])) {
add_result('FAIL', 'post_max_size can be changed via ini_set()', json_encode($overrideResults['post_max_size']));
} else {
add_result('PASS', 'post_max_size resisted ini_set()');
}
if (!empty($overrideResults['open_basedir']['changed'])) {
add_result('FAIL', 'open_basedir can be changed via ini_set()', json_encode($overrideResults['open_basedir']));
} else {
add_result('PASS', 'open_basedir resisted ini_set()');
}
$dangerousAvailable = [];
foreach (['exec','shell_exec','system','passthru','proc_open','popen'] as $fn) {
if (function_exists($fn) && !str_contains($disableFunctions, $fn)) {
$dangerousAvailable[] = $fn;
}
}
if ($dangerousAvailable) {
add_result('FAIL', 'Command execution functions are available', implode(', ', $dangerousAvailable));
} else {
add_result('PASS', 'Command execution functions are blocked');
}
if (extension_loaded('pcntl')) {
add_result('FAIL', 'pcntl extension is loaded', 'Not recommended for shared hosting web SAPI');
} else {
add_result('PASS', 'pcntl extension is not loaded');
}
if ($userIni === '' || strtolower($userIni) === 'none') {
add_result('PASS', '.user.ini appears disabled');
} else {
add_result('WARN', '.user.ini appears enabled', $userIni);
}
if ($allowUrlInclude === '' || $allowUrlInclude === '0') {
add_result('PASS', 'allow_url_include is off');
} else {
add_result('FAIL', 'allow_url_include is on');
}
if (is_link($symlinkTarget)) {
add_result('WARN', 'Symlink creation works inside allowed path', $symlinkTarget);
} else {
add_result('PASS', 'Symlink creation did not work');
}
if (file_exists($hardlinkTarget)) {
add_result('WARN', 'Hardlink creation works inside allowed path', $hardlinkTarget);
} else {
add_result('PASS', 'Hardlink creation did not work');
}
$localhostSensitiveOpen = [];
foreach (['127.0.0.1:25','127.0.0.1:3306','127.0.0.1:6379','127.0.0.1:7080'] as $k) {
if (!empty($netResults[$k]['connected'])) {
$localhostSensitiveOpen[] = $k;
}
}
if ($localhostSensitiveOpen) {
add_result('WARN', 'Sensitive localhost TCP ports reachable', implode(', ', $localhostSensitiveOpen));
} else {
add_result('PASS', 'Sensitive localhost TCP ports not reachable');
}
$reachableUnix = [];
foreach ($unixResults as $sock => $res) {
if (!empty($res['connected'])) {
$reachableUnix[] = $sock;
}
}
if ($reachableUnix) {
add_result('WARN', 'Sensitive UNIX sockets reachable', implode(', ', $reachableUnix));
} else {
add_result('PASS', 'Sensitive UNIX sockets not reachable');
}
section('Score');
line('PASS', $SCORE['PASS']);
line('WARN', $SCORE['WARN']);
line('FAIL', $SCORE['FAIL']);
section('Findings');
foreach ($FINDINGS as [$level, $title, $detail]) {
echo '[' . $level . '] ' . $title;
if ($detail !== '') {
echo ' :: ' . $detail;
}
echo PHP_EOL;
}
section('Cleanup');
$cleanupFiles = [
$renameTo,
$copyToSession,
$src,
$symlinkTarget,
$hardlinkTarget,
$includeFile,
$userIniFile,
];
foreach ($cleanupFiles as $f) {
if (is_link($f) || file_exists($f)) {
@unlink($f);
}
}
@rmdir($baseTmp);
echo PHP_EOL . "Done." . PHP_EOL;