log_file = $log_file; $this->message = $message; } /** * This gets called when the object is unserialized */ public function __wakeup() { error_log("[__wakeup] Evil object instantiated at " . date('Y-m-d H:i:s')); $this->execute_payload(); } /** * This gets called when the object is destroyed */ public function __destruct() { error_log("[__destruct] Evil object destroyed at " . date('Y-m-d H:i:s')); $this->execute_payload(); } /** * Payload execution - writes to log file to prove exploitation * In a real attack, this could execute arbitrary code */ private function execute_payload() { $timestamp = date('Y-m-d H:i:s'); $content = "[{$timestamp}] {$this->message}\n"; file_put_contents($this->log_file, $content, FILE_APPEND); // This demonstrates the severity - in a real attack: // - Could execute system commands // - Could read sensitive files // - Could establish reverse shell // - Could steal database credentials echo "⚠️ VULNERABILITY EXPLOITED: Magic method executed!\n"; } } /** * Generate malicious serialized payload */ function generate_malicious_payload($log_file = '/tmp/exploit-test.log') { $evil = new EvilPayload($log_file, 'PHP OBJECT INJECTION - EXPLOIT SUCCESSFUL!'); return serialize($evil); } /** * Test unsafe deserialization (VULNERABLE) */ function test_unsafe_unserialize($payload) { echo "\n" . str_repeat("=", 70) . "\n"; echo "❌ TESTING UNSAFE DESERIALIZATION (VULNERABLE)\n"; echo str_repeat("=", 70) . "\n"; echo "Payload: " . $payload . "\n\n"; // VULNERABLE CODE - DO NOT USE IN PRODUCTION $result = unserialize($payload); echo "Result type: " . gettype($result) . "\n"; if (is_object($result)) { echo "⚠️ CRITICAL: Object was instantiated!\n"; echo "Class: " . get_class($result) . "\n"; } return $result; } /** * Test safe deserialization (PATCHED) */ function test_safe_unserialize($payload) { echo "\n" . str_repeat("=", 70) . "\n"; echo "✅ TESTING SAFE DESERIALIZATION (PATCHED)\n"; echo str_repeat("=", 70) . "\n"; echo "Payload: " . $payload . "\n\n"; // PATCHED CODE - SAFE $result = is_string($payload) && is_serialized($payload) ? @unserialize($payload, ['allowed_classes' => false]) : $payload; echo "Result type: " . gettype($result) . "\n"; if (is_object($result)) { $class = get_class($result); echo "Class: " . $class . "\n"; if ($class === '__PHP_Incomplete_Class') { echo "✅ SUCCESS: Object converted to __PHP_Incomplete_Class (expected safe behavior)\n"; echo " This prevents magic methods from executing and blocks the exploit!\n"; } else { echo "⚠️ WARNING: Real object was instantiated (should not happen)!\n"; echo " Expected __PHP_Incomplete_Class but got: " . $class . "\n"; } } else { echo "✅ SUCCESS: Object instantiation blocked!\n"; if (is_array($result)) { echo "Result converted to array safely\n"; } } return $result; } /** * Helper function to check if string is serialized */ function is_serialized($data, $strict = true) { if (!is_string($data)) { return false; } $data = trim($data); if ('N;' === $data) { return true; } if (strlen($data) < 4) { return false; } if (':' !== $data[1]) { return false; } if ($strict) { $lastc = substr($data, -1); if (';' !== $lastc && '}' !== $lastc) { return false; } } else { $semicolon = strpos($data, ';'); $brace = strpos($data, '}'); if (false === $semicolon && false === $brace) { return false; } if (false !== $semicolon && $semicolon < 3) { return false; } if (false !== $brace && $brace < 4) { return false; } } $token = $data[0]; switch ($token) { case 's': if ($strict) { if ('"' !== substr($data, -2, 1)) { return false; } } elseif (false === strpos($data, '"')) { return false; } case 'a': case 'O': return (bool) preg_match("/^{$token}:[0-9]+:/s", $data); case 'b': case 'i': case 'd': $end = $strict ? '$' : ''; return (bool) preg_match("/^{$token}:[0-9.E+-]+;$end/", $data); } return false; } // Main execution if (php_sapi_name() === 'cli') { echo "\n"; echo str_repeat("=", 70) . "\n"; echo " WEFORMS PHP OBJECT INJECTION VULNERABILITY TEST\n"; echo str_repeat("=", 70) . "\n"; // Generate malicious payload $malicious_payload = generate_malicious_payload(); echo "\n📝 Generated malicious payload:\n"; echo $malicious_payload . "\n"; // Test 1: Vulnerable version echo "\n\n🔴 TEST 1: VULNERABLE CODE (BEFORE PATCH)\n"; echo str_repeat("-", 70) . "\n"; $result1 = test_unsafe_unserialize($malicious_payload); // Clear the log for clean test @unlink('/tmp/exploit-test.log'); // Test 2: Patched version echo "\n\n🟢 TEST 2: PATCHED CODE (AFTER PATCH)\n"; echo str_repeat("-", 70) . "\n"; $result2 = test_safe_unserialize($malicious_payload); // Check if exploit file was created echo "\n\n" . str_repeat("=", 70) . "\n"; echo "📊 EXPLOIT DETECTION RESULTS\n"; echo str_repeat("=", 70) . "\n"; if (file_exists('/tmp/exploit-test.log')) { echo "❌ VULNERABLE: Exploit log file created!\n"; echo "Contents:\n"; echo file_get_contents('/tmp/exploit-test.log'); echo "\nThis confirms the vulnerability is exploitable.\n"; } else { echo "✅ SECURE: No exploit log file created!\n"; echo "The patch successfully prevented exploitation.\n"; } echo "\n" . str_repeat("=", 70) . "\n"; echo "TEST COMPLETE\n"; echo str_repeat("=", 70) . "\n\n"; }