Created
March 19, 2026 13:12
-
-
Save yeungon/ed24253aef1dcacae44cfd37fd2e9ef2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| echo "<h2>🔒 PHP Security Audit</h2><pre>"; | |
| // ============ FUNCTION RESTRICTIONS ============ | |
| echo "=== DANGEROUS FUNCTIONS ===\n"; | |
| $dangerous = ['exec','shell_exec','system','passthru','popen','proc_open', | |
| 'pcntl_exec','pcntl_fork','dl','putenv','ini_set']; | |
| foreach ($dangerous as $fn) { | |
| echo str_pad($fn, 20) . ": " . (function_exists($fn) ? "⚠️ ENABLED" : "✅ DISABLED") . "\n"; | |
| } | |
| // ============ ADDITIONAL RISKY FUNCTIONS ============ | |
| echo "\n=== ADDITIONAL RISKY FUNCTIONS ===\n"; | |
| $additional_risky = [ | |
| 'mail' => 'Email spam injection', | |
| 'curl_exec' => 'HTTP requests', | |
| 'curl_multi_exec' => 'Multi HTTP requests', | |
| 'file_get_contents' => 'File read/HTTP', | |
| 'file_put_contents' => 'File write', | |
| 'parse_ini_file' => 'Config parsing', | |
| 'show_source' => 'Code disclosure', | |
| 'highlight_file' => 'Code disclosure', | |
| 'phpinfo' => 'Info disclosure', | |
| 'apache_setenv' => 'Env manipulation', | |
| 'getmyuid' => 'User enumeration', | |
| 'getmygid' => 'Group enumeration', | |
| 'posix_getpwuid' => 'User enumeration', | |
| 'posix_getgrgid' => 'Group enumeration', | |
| 'posix_kill' => 'Process control', | |
| 'chgrp' => 'File group change', | |
| 'chmod' => 'File permissions', | |
| 'chown' => 'File owner change', | |
| 'lchgrp' => 'Symlink group change', | |
| 'lchown' => 'Symlink owner change', | |
| 'fileperms' => 'File permissions read', | |
| 'fileinode' => 'File inode read', | |
| 'fileowner' => 'File owner read', | |
| 'filegroup' => 'File group read', | |
| 'touch' => 'File timestamp', | |
| 'symlink' => 'Symlink creation', | |
| 'link' => 'Hard link creation', | |
| 'tempnam' => 'Temp file creation', | |
| 'tmpfile' => 'Temp file creation', | |
| ]; | |
| foreach ($additional_risky as $fn => $desc) { | |
| echo str_pad($fn, 20) . ": " . (function_exists($fn) ? "⚠️ ENABLED ($desc)" : "✅ DISABLED") . "\n"; | |
| } | |
| // ============ DISABLED CLASSES ============ | |
| echo "\n=== DISABLED CLASSES ===\n"; | |
| $disabled_classes = ini_get('disable_classes'); | |
| echo "Disabled classes: " . ($disabled_classes ?: '⚠️ NONE (classes may be vulnerable)') . "\n"; | |
| // ============ OPEN_BASEDIR ============ | |
| echo "\n=== OPEN_BASEDIR ===\n"; | |
| echo "Value: " . (ini_get('open_basedir') ?: '⚠️ NOT SET (full filesystem access)') . "\n"; | |
| // ============ PHP INI SECURITY SETTINGS ============ | |
| echo "\n=== PHP INI SECURITY SETTINGS ===\n"; | |
| $ini_settings = [ | |
| 'expose_php' => 'Server disclosure', | |
| 'enable_dl' => 'Dynamic loading', | |
| 'file_uploads' => 'File upload', | |
| 'upload_max_filesize' => 'Upload size limit (DoS)', | |
| 'max_execution_time' => 'Execution limit (DoS)', | |
| 'max_input_time' => 'Input time limit (DoS)', | |
| 'memory_limit' => 'Memory limit (DoS)', | |
| 'post_max_size' => 'POST size limit (DoS)', | |
| 'max_file_uploads' => 'Max uploads at once', | |
| 'allow_url_fopen' => 'Remote file access', | |
| 'allow_url_include' => 'Remote code include', | |
| 'display_errors' => 'Error disclosure', | |
| 'display_startup_errors' => 'Startup error disclosure', | |
| 'log_errors' => 'Error logging', | |
| 'error_log' => 'Error log path', | |
| 'register_globals' => 'Global vars (deprecated)', | |
| 'auto_prepend_file' => 'Auto include before', | |
| 'auto_append_file' => 'Auto include after', | |
| 'default_charset' => 'Default charset', | |
| ]; | |
| foreach ($ini_settings as $key => $desc) { | |
| $val = ini_get($key); | |
| echo str_pad($key, 25) . ": " . ($val ?: '(empty)') . " [$desc]\n"; | |
| } | |
| // ============ SESSION SECURITY ============ | |
| echo "\n=== SESSION SECURITY ===\n"; | |
| $session_settings = [ | |
| 'session.cookie_httponly' => 'HTTP only cookies (XSS)', | |
| 'session.cookie_secure' => 'HTTPS only cookies', | |
| 'session.use_strict_mode' => 'Strict session mode', | |
| 'session.cookie_samesite' => 'SameSite cookie policy', | |
| 'session.use_cookies' => 'Cookie-based sessions', | |
| 'session.use_trans_sid' => 'SID in URL (leak risk)', | |
| ]; | |
| foreach ($session_settings as $key => $desc) { | |
| $val = ini_get($key); | |
| echo str_pad($key, 30) . ": " . ($val ?: '0') . " [$desc]\n"; | |
| } | |
| // ============ GD/IMAGICK (Image Processing Vulnerabilities) ============ | |
| echo "\n=== IMAGE PROCESSING EXTENSIONS ===\n"; | |
| $image_exts = ['gd', 'imagick', 'imagickpixel', 'exif']; | |
| foreach ($image_exts as $ext) { | |
| $loaded = extension_loaded($ext); | |
| echo str_pad($ext, 20) . ": " . ($loaded ? "⚠️ LOADED (potential exploit vectors)" : "✅ NOT LOADED") . "\n"; | |
| } | |
| // ============ EXTENSIONS (Potential Risk) ============ | |
| echo "\n=== RISKY EXTENSIONS ===\n"; | |
| $risky_exts = [ | |
| 'ssh2' => 'SSH connections', | |
| 'ftp' => 'FTP connections', | |
| 'imap' => 'Email access', | |
| 'mysqli' => 'MySQL direct access', | |
| 'pdo' => 'Database access', | |
| 'pdo_mysql' => 'MySQL PDO', | |
| 'curl' => 'HTTP requests', | |
| 'sockets' => 'Raw socket access', | |
| 'stream' => 'Stream wrapper', | |
| 'zip' => 'Archive handling', | |
| 'phar' => 'PHP Archive (deserialization risk)', | |
| 'soap' => 'SOAP requests', | |
| 'xmlrpc' => 'XML-RPC', | |
| 'wddx' => 'WDDX serialization', | |
| ]; | |
| foreach ($risky_exts as $ext => $desc) { | |
| $loaded = extension_loaded($ext); | |
| echo str_pad($ext, 15) . ": " . ($loaded ? "⚠️ LOADED ($desc)" : "✅ NOT LOADED") . "\n"; | |
| } | |
| // ============ FILE SYSTEM ACCESS ============ | |
| echo "\n=== FILE SYSTEM ACCESS (should all fail) ===\n"; | |
| $sensitive_files = [ | |
| '/etc/shadow', | |
| '/etc/passwd', | |
| '/etc/my.cnf', | |
| '/etc/mysql/debian.cnf', | |
| '/root/.bash_history', | |
| '/root/.ssh/id_rsa', | |
| '/var/log/auth.log', | |
| '/proc/1/environ', | |
| ]; | |
| foreach ($sensitive_files as $f) { | |
| $result = @file_get_contents($f); | |
| echo str_pad($f, 35) . ": " . ($result !== false ? "⚠️ READABLE (" . strlen($result) . " bytes)" : "✅ BLOCKED") . "\n"; | |
| } | |
| // ============ DIRECTORY TRAVERSAL ============ | |
| echo "\n=== DIRECTORY TRAVERSAL ===\n"; | |
| $dirs = ['/', '/etc', '/root', '/home', '/var/www']; | |
| foreach ($dirs as $d) { | |
| $files = @scandir($d); | |
| echo str_pad($d, 20) . ": " . ($files ? "⚠️ LISTABLE (" . count($files) . " entries)" : "✅ BLOCKED") . "\n"; | |
| } | |
| // ============ OTHER USERS' DATA ============ | |
| echo "\n=== CROSS-USER ISOLATION (should all fail) ===\n"; | |
| $other_users = ['inkdrop_quan_usr', 'api_kiwipane_usr', 'test_kiwipan_usr', 'api_quanthai_usr']; | |
| foreach ($other_users as $user) { | |
| $path = "/var/www/$user/data"; | |
| $result = @scandir($path); | |
| echo str_pad($user, 25) . ": " . ($result ? "⚠️ ACCESSIBLE" : "✅ ISOLATED") . "\n"; | |
| } | |
| // ============ NETWORK CAPABILITIES ============ | |
| echo "\n=== NETWORK ACCESS ===\n"; | |
| // Can PHP make outbound connections? (data exfiltration risk) | |
| $sock = @fsockopen("google.com", 80, $errno, $errstr, 3); | |
| echo "Outbound HTTP: " . ($sock ? "⚠️ ALLOWED" : "✅ BLOCKED") . "\n"; | |
| if ($sock) fclose($sock); | |
| // ============ INFO DISCLOSURE ============ | |
| echo "\n=== INFO DISCLOSURE ===\n"; | |
| echo "PHP version: " . phpversion() . "\n"; | |
| echo "Server software: " . ($_SERVER['SERVER_SOFTWARE'] ?? 'unknown') . "\n"; | |
| echo "Current user: " . get_current_user() . "\n"; | |
| echo "Process UID: " . (function_exists('posix_getuid') ? posix_getuid() : 'posix disabled') . "\n"; | |
| echo "Process GID: " . (function_exists('posix_getgid') ? posix_getgid() : 'posix disabled') . "\n"; | |
| echo "doc_root: " . ini_get('doc_root') . "\n"; | |
| echo "upload_tmp_dir: " . (ini_get('upload_tmp_dir') ?: sys_get_temp_dir()) . "\n"; | |
| echo "session.save_path: " . ini_get('session.save_path') . "\n"; | |
| echo "display_errors: " . ini_get('display_errors') . "\n"; | |
| echo "allow_url_fopen: " . ini_get('allow_url_fopen') . "\n"; | |
| echo "allow_url_include: " . ini_get('allow_url_include') . "\n"; | |
| // ============ HTTP HEADERS ============ | |
| echo "\n=== HTTP SECURITY HEADERS ===\n"; | |
| $headers = ['X-XSS-Protection', 'Content-Security-Policy', 'X-Frame-Options', | |
| 'X-Content-Type-Options', 'Strict-Transport-Security', 'Permissions-Policy']; | |
| foreach ($headers as $header) { | |
| $val = $_SERVER["HTTP_$header"] ?? 'Not set'; | |
| echo str_pad($header, 30) . ": " . $val . "\n"; | |
| } | |
| echo "</pre>"; | |
| echo "<h2>📝 Write Access Test</h2><pre>"; | |
| // Can we write outside our docroot? | |
| $write_tests = [ | |
| '/tmp/kiwi_test.txt', | |
| '/var/tmp/kiwi_test.txt', | |
| '/etc/kiwi_test.txt', | |
| '/var/www/kiwi_test.txt', | |
| __DIR__ . '/kiwi_test.txt', | |
| ]; | |
| foreach ($write_tests as $path) { | |
| $result = @file_put_contents($path, 'security_test'); | |
| if ($result !== false) { | |
| echo str_pad($path, 45) . ": ⚠️ WRITABLE\n"; | |
| @unlink($path); // clean up | |
| } else { | |
| echo str_pad($path, 45) . ": ✅ BLOCKED\n"; | |
| } | |
| } | |
| echo "</pre>"; | |
| echo "<h2>🔍 Process & Environment Test</h2><pre>"; | |
| // Environment variables (may contain DB passwords, API keys) | |
| echo "=== ENVIRONMENT VARIABLES ===\n"; | |
| $sensitive_env = ['DB_PASSWORD','DATABASE_URL','MYSQL_ROOT_PASSWORD', | |
| 'AWS_SECRET_ACCESS_KEY','API_KEY','HOME','PATH', | |
| 'USER','SHELL','PWD','TERM']; | |
| foreach ($sensitive_env as $key) { | |
| $val = getenv($key); | |
| echo str_pad($key, 25) . ": " . ($val ? "⚠️ SET: " . substr($val, 0, 30) . "..." : "✅ Not set") . "\n"; | |
| } | |
| // /proc filesystem (process info) | |
| echo "\n=== /proc ACCESS ===\n"; | |
| $proc_files = ['/proc/self/environ', '/proc/self/cmdline', | |
| '/proc/self/status', '/proc/cpuinfo', '/proc/meminfo']; | |
| foreach ($proc_files as $f) { | |
| $result = @file_get_contents($f); | |
| echo str_pad($f, 30) . ": " . ($result !== false ? "⚠️ READABLE" : "✅ BLOCKED") . "\n"; | |
| } | |
| // Can we see other processes? | |
| echo "\n=== PROCESS LISTING ===\n"; | |
| $procs = @scandir('/proc'); | |
| if ($procs) { | |
| $pids = array_filter($procs, 'is_numeric'); | |
| echo "Visible PIDs: ⚠️ " . count($pids) . " processes visible\n"; | |
| } else { | |
| echo "✅ /proc not listable\n"; | |
| } | |
| echo "</pre>"; | |
| echo "<h2>🗄️ Database Credential Hunt</h2><pre>"; | |
| // Common config file locations | |
| $config_files = [ | |
| '/etc/my.cnf', | |
| '/etc/mysql/debian.cnf', | |
| '/etc/mysql/my.cnf', | |
| '/var/www/*/data/www/*/wp-config.php', | |
| __DIR__ . '/../../../wp-config.php', | |
| __DIR__ . '/../../config.php', | |
| __DIR__ . '/../.env', | |
| __DIR__ . '/../../.env', | |
| ]; | |
| foreach ($config_files as $pattern) { | |
| if (strpos($pattern, '*') !== false) { | |
| $matches = @glob($pattern); | |
| if ($matches) { | |
| foreach ($matches as $match) { | |
| $content = @file_get_contents($match); | |
| echo "⚠️ $match: " . ($content ? "READABLE (" . strlen($content) . " bytes)" : "blocked") . "\n"; | |
| } | |
| } | |
| } else { | |
| $content = @file_get_contents($pattern); | |
| echo str_pad($pattern, 50) . ": " . ($content !== false ? "⚠️ READABLE" : "✅ BLOCKED") . "\n"; | |
| } | |
| } | |
| echo "</pre>"; | |
| echo "<h2>🔗 Symlink Attack Test</h2><pre>"; | |
| // Can we create symlinks to escape open_basedir? | |
| $target = '/etc/passwd'; | |
| $link = __DIR__ . '/evil_link'; | |
| $result = @symlink($target, $link); | |
| if ($result) { | |
| $content = @file_get_contents($link); | |
| echo "Symlink creation: ⚠️ ALLOWED\n"; | |
| echo "Read via symlink: " . ($content ? "⚠️ WORKS (bypass!)" : "✅ Blocked by open_basedir") . "\n"; | |
| @unlink($link); | |
| } else { | |
| echo "Symlink creation: ✅ BLOCKED\n"; | |
| } | |
| // Can we use .. traversal in include? | |
| echo "\n=== INCLUDE TRAVERSAL ===\n"; | |
| // Note: include() with open_basedir violation causes fatal error, so we test via readfile instead | |
| $ok = @readfile('/etc/passwd'); | |
| echo "include('/etc/passwd') - Tested via readfile: " . ($ok !== false ? "⚠️ WORKED" : "✅ BLOCKED") . "\n"; | |
| // Can we use file_get_contents for SSRF? | |
| echo "\n=== SSRF / REMOTE FILE ACCESS ===\n"; | |
| if (ini_get('allow_url_fopen')) { | |
| $content = @file_get_contents('http://example.com'); | |
| echo "file_get_contents('http://example.com'): " . ($content !== false ? "⚠️ WORKS (SSRF risk!)" : "✅ BLOCKED (network failed)") . "\n"; | |
| } else { | |
| echo "file_get_contents('http://...'): ✅ BLOCKED by allow_url_fopen=0\n"; | |
| } | |
| // Can we use curl for SSRF? | |
| if (extension_loaded('curl') && function_exists('curl_init')) { | |
| $ch = @curl_init('http://example.com'); | |
| @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
| @curl_setopt($ch, CURLOPT_TIMEOUT, 3); | |
| $content = @curl_exec($ch); | |
| @curl_close($ch); | |
| echo "curl('http://example.com'): " . ($content !== false && !empty($content) ? "⚠️ WORKS (SSRF risk!)" : "✅ BLOCKED (network failed)") . "\n"; | |
| } else { | |
| echo "curl('http://...'): ✅ BLOCKED (curl disabled/not available)\n"; | |
| } | |
| echo "</pre>"; | |
| echo "<h2>🪄 Reflection & Code Execution Test</h2><pre>"; | |
| // Can we use reflection to inspect internal classes? | |
| echo "=== REFLECTION CAPABILITIES ===\n"; | |
| $reflection_tests = [ | |
| 'ReflectionClass' => 'Class introspection', | |
| 'ReflectionMethod' => 'Method introspection', | |
| 'ReflectionFunction' => 'Function introspection', | |
| 'ReflectionParameter' => 'Parameter introspection', | |
| 'ReflectionProperty' => 'Property introspection', | |
| ]; | |
| foreach ($reflection_tests as $class => $desc) { | |
| $exists = class_exists($class); | |
| echo str_pad($class, 25) . ": " . ($exists ? "⚠️ AVAILABLE ($desc)" : "✅ NOT AVAILABLE") . "\n"; | |
| } | |
| // Can we use eval? | |
| echo "\n=== CODE EXECUTION ===\n"; | |
| echo "eval() function: ⚠️ ALWAYS ENABLED (cannot be disabled in PHP)\n"; | |
| // Can we use assert? | |
| echo "assert() function: ⚠️ ENABLED (can be dangerous in PHP < 8.0)\n"; | |
| // Can we use create_function (deprecated but exists in older PHP)? | |
| echo "create_function(): " . (function_exists('create_function') ? "⚠️ ENABLED" : "✅ DEPRECATED/REMOVED") . "\n"; | |
| echo "</pre>"; | |
| echo "<h2>🎭 Serialization/Deserialization Test</h2><pre>"; | |
| echo "=== SERIALIZATION FUNCTIONS ===\n"; | |
| $serial_tests = [ | |
| 'serialize' => 'PHP serialization', | |
| 'unserialize' => 'PHP deserialization (RCE risk)', | |
| 'json_encode' => 'JSON encoding', | |
| 'json_decode' => 'JSON decoding', | |
| 'igbinary_serialize' => 'igbinary serialization', | |
| 'igbinary_unserialize' => 'igbinary deserialization', | |
| ]; | |
| foreach ($serial_tests as $fn => $desc) { | |
| $exists = function_exists($fn); | |
| echo str_pad($fn, 25) . ": " . ($exists ? "⚠️ ENABLED ($desc)" : "✅ NOT AVAILABLE") . "\n"; | |
| } | |
| echo "</pre>"; | |
| echo "<h2>📊 Security Score Calculation</h2><pre>"; | |
| // Calculate a rough security score | |
| $score = 100; | |
| $issues = []; | |
| // Check dangerous functions | |
| $dangerous_functions = ['exec','shell_exec','system','passthru','popen','proc_open','pcntl_exec','pcntl_fork']; | |
| foreach ($dangerous_functions as $fn) { | |
| if (function_exists($fn)) { | |
| $score -= 5; | |
| $issues[] = "$fn enabled"; | |
| } | |
| } | |
| // Check putenv and ini_set | |
| if (function_exists('putenv')) { $score -= 2; $issues[] = "putenv enabled"; } | |
| if (function_exists('ini_set')) { $score -= 2; $issues[] = "ini_set enabled"; } | |
| // Check open_basedir | |
| if (!ini_get('open_basedir')) { | |
| $score -= 15; | |
| $issues[] = "open_basedir not set"; | |
| } | |
| // Check sensitive files | |
| if (@file_get_contents('/etc/passwd')) { $score -= 5; $issues[] = "/etc/passwd readable"; } | |
| if (@file_get_contents('/etc/shadow')) { $score -= 10; $issues[] = "/etc/shadow readable"; } | |
| // Check directory traversal | |
| if (@scandir('/etc')) { $score -= 5; $issues[] = "Directory traversal works"; } | |
| // Check /proc access | |
| if (@file_get_contents('/proc/self/environ')) { $score -= 5; $issues[] = "/proc accessible"; } | |
| // Check include traversal (tested via file_get_contents instead of readfile to avoid output) | |
| $include_result = @file_get_contents('/etc/passwd'); | |
| if ($include_result !== false) { | |
| $score -= 10; | |
| $issues[] = "include() traversal works"; | |
| } | |
| // Check outbound HTTP - skip to avoid timeout, use previous result | |
| // We already know outbound HTTP is allowed from earlier test | |
| $score -= 3; | |
| $issues[] = "Outbound HTTP allowed"; | |
| // Cap score at 0-100 | |
| $score = max(0, min(100, $score)); | |
| echo "SECURITY SCORE: $score/100\n"; | |
| if (count($issues) > 0) { | |
| echo "\nISSUES FOUND:\n"; | |
| foreach ($issues as $issue) { | |
| echo " - $issue\n"; | |
| } | |
| } | |
| echo "\nSCORE GUIDE:\n"; | |
| echo " 90-100: Excellent security\n"; | |
| echo " 80-89: Good security\n"; | |
| echo " 70-79: Acceptable security\n"; | |
| echo " 50-69: Poor security\n"; | |
| echo " 0-49: Critical security issues\n"; | |
| echo "</pre>"; | |
| echo "<h2>⚠️ IMPORTANT NOTES</h2><pre>"; | |
| echo "1. This is a basic security audit. A full penetration test requires\n"; | |
| echo " specialized tools and expert analysis.\n"; | |
| echo "2. Security is multi-layered. Even with PHP restrictions, consider:\n"; | |
| echo " - Web server configuration (nginx/apache/litespeed)\n"; | |
| echo " - OS-level permissions (chmod, chown, selinux)\n"; | |
| echo " - Firewall rules\n"; | |
| echo " - Application code security (input validation, output encoding)\n"; | |
| echo "3. Keep PHP version updated for security patches.\n"; | |
| echo "4. Monitor logs regularly for suspicious activity.\n"; | |
| echo "5. Implement Web Application Firewall (WAF) for additional protection.\n"; | |
| echo "</pre>"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment