| 1 |
<?php |
| 2 |
/** |
| 3 |
* Proof of Concept: PHP Object Injection Exploit |
| 4 |
* |
| 5 |
* WARNING: FOR TESTING PURPOSES ONLY |
| 6 |
* This demonstrates the vulnerability but uses a safe payload |
| 7 |
* that only writes to a log file, not actual RCE. |
| 8 |
* |
| 9 |
* @package WeForms Security Tests |
| 10 |
* @version 1.0.0 |
| 11 |
*/ |
| 12 |
|
| 13 |
/** |
| 14 |
* Evil class that demonstrates object injection |
| 15 |
* Uses __destruct() magic method to execute code when object is destroyed |
| 16 |
*/ |
| 17 |
class EvilPayload { |
| 18 |
public $log_file; |
| 19 |
public $message; |
| 20 |
|
| 21 |
public function __construct($log_file = '/tmp/exploit-test.log', $message = 'EXPLOIT SUCCESSFUL') { |
| 22 |
$this->log_file = $log_file; |
| 23 |
$this->message = $message; |
| 24 |
} |
| 25 |
|
| 26 |
/** |
| 27 |
* This gets called when the object is unserialized |
| 28 |
*/ |
| 29 |
public function __wakeup() { |
| 30 |
error_log("[__wakeup] Evil object instantiated at " . date('Y-m-d H:i:s')); |
| 31 |
$this->execute_payload(); |
| 32 |
} |
| 33 |
|
| 34 |
/** |
| 35 |
* This gets called when the object is destroyed |
| 36 |
*/ |
| 37 |
public function __destruct() { |
| 38 |
error_log("[__destruct] Evil object destroyed at " . date('Y-m-d H:i:s')); |
| 39 |
$this->execute_payload(); |
| 40 |
} |
| 41 |
|
| 42 |
/** |
| 43 |
* Payload execution - writes to log file to prove exploitation |
| 44 |
* In a real attack, this could execute arbitrary code |
| 45 |
*/ |
| 46 |
private function execute_payload() { |
| 47 |
$timestamp = date('Y-m-d H:i:s'); |
| 48 |
$content = "[{$timestamp}] {$this->message}\n"; |
| 49 |
file_put_contents($this->log_file, $content, FILE_APPEND); |
| 50 |
|
| 51 |
// This demonstrates the severity - in a real attack: |
| 52 |
// - Could execute system commands |
| 53 |
// - Could read sensitive files |
| 54 |
// - Could establish reverse shell |
| 55 |
// - Could steal database credentials |
| 56 |
echo "⚠️ VULNERABILITY EXPLOITED: Magic method executed!\n"; |
| 57 |
} |
| 58 |
} |
| 59 |
|
| 60 |
/** |
| 61 |
* Generate malicious serialized payload |
| 62 |
*/ |
| 63 |
function generate_malicious_payload($log_file = '/tmp/exploit-test.log') { |
| 64 |
$evil = new EvilPayload($log_file, 'PHP OBJECT INJECTION - EXPLOIT SUCCESSFUL!'); |
| 65 |
return serialize($evil); |
| 66 |
} |
| 67 |
|
| 68 |
/** |
| 69 |
* Test unsafe deserialization (VULNERABLE) |
| 70 |
*/ |
| 71 |
function test_unsafe_unserialize($payload) { |
| 72 |
echo "\n" . str_repeat("=", 70) . "\n"; |
| 73 |
echo "❌ TESTING UNSAFE DESERIALIZATION (VULNERABLE)\n"; |
| 74 |
echo str_repeat("=", 70) . "\n"; |
| 75 |
|
| 76 |
echo "Payload: " . $payload . "\n\n"; |
| 77 |
|
| 78 |
// VULNERABLE CODE - DO NOT USE IN PRODUCTION |
| 79 |
$result = unserialize($payload); |
| 80 |
|
| 81 |
echo "Result type: " . gettype($result) . "\n"; |
| 82 |
if (is_object($result)) { |
| 83 |
echo "⚠️ CRITICAL: Object was instantiated!\n"; |
| 84 |
echo "Class: " . get_class($result) . "\n"; |
| 85 |
} |
| 86 |
|
| 87 |
return $result; |
| 88 |
} |
| 89 |
|
| 90 |
/** |
| 91 |
* Test safe deserialization (PATCHED) |
| 92 |
*/ |
| 93 |
function test_safe_unserialize($payload) { |
| 94 |
echo "\n" . str_repeat("=", 70) . "\n"; |
| 95 |
echo "� |
| 96 |
TESTING SAFE DESERIALIZATION (PATCHED)\n"; |
| 97 |
echo str_repeat("=", 70) . "\n"; |
| 98 |
|
| 99 |
echo "Payload: " . $payload . "\n\n"; |
| 100 |
|
| 101 |
// PATCHED CODE - SAFE |
| 102 |
$result = is_string($payload) && is_serialized($payload) |
| 103 |
? @unserialize($payload, ['allowed_classes' => false]) |
| 104 |
: $payload; |
| 105 |
|
| 106 |
echo "Result type: " . gettype($result) . "\n"; |
| 107 |
if (is_object($result)) { |
| 108 |
$class = get_class($result); |
| 109 |
echo "Class: " . $class . "\n"; |
| 110 |
|
| 111 |
if ($class === '__PHP_Incomplete_Class') { |
| 112 |
echo "� |
| 113 |
SUCCESS: Object converted to __PHP_Incomplete_Class (expected safe behavior)\n"; |
| 114 |
echo " This prevents magic methods from executing and blocks the exploit!\n"; |
| 115 |
} else { |
| 116 |
echo "⚠️ WARNING: Real object was instantiated (should not happen)!\n"; |
| 117 |
echo " Expected __PHP_Incomplete_Class but got: " . $class . "\n"; |
| 118 |
} |
| 119 |
} else { |
| 120 |
echo "� |
| 121 |
SUCCESS: Object instantiation blocked!\n"; |
| 122 |
if (is_array($result)) { |
| 123 |
echo "Result converted to array safely\n"; |
| 124 |
} |
| 125 |
} |
| 126 |
|
| 127 |
return $result; |
| 128 |
} |
| 129 |
|
| 130 |
/** |
| 131 |
* Helper function to check if string is serialized |
| 132 |
*/ |
| 133 |
function is_serialized($data, $strict = true) { |
| 134 |
if (!is_string($data)) { |
| 135 |
return false; |
| 136 |
} |
| 137 |
$data = trim($data); |
| 138 |
if ('N;' === $data) { |
| 139 |
return true; |
| 140 |
} |
| 141 |
if (strlen($data) < 4) { |
| 142 |
return false; |
| 143 |
} |
| 144 |
if (':' !== $data[1]) { |
| 145 |
return false; |
| 146 |
} |
| 147 |
if ($strict) { |
| 148 |
$lastc = substr($data, -1); |
| 149 |
if (';' !== $lastc && '}' !== $lastc) { |
| 150 |
return false; |
| 151 |
} |
| 152 |
} else { |
| 153 |
$semicolon = strpos($data, ';'); |
| 154 |
$brace = strpos($data, '}'); |
| 155 |
if (false === $semicolon && false === $brace) { |
| 156 |
return false; |
| 157 |
} |
| 158 |
if (false !== $semicolon && $semicolon < 3) { |
| 159 |
return false; |
| 160 |
} |
| 161 |
if (false !== $brace && $brace < 4) { |
| 162 |
return false; |
| 163 |
} |
| 164 |
} |
| 165 |
$token = $data[0]; |
| 166 |
switch ($token) { |
| 167 |
case 's': |
| 168 |
if ($strict) { |
| 169 |
if ('"' !== substr($data, -2, 1)) { |
| 170 |
return false; |
| 171 |
} |
| 172 |
} elseif (false === strpos($data, '"')) { |
| 173 |
return false; |
| 174 |
} |
| 175 |
case 'a': |
| 176 |
case 'O': |
| 177 |
return (bool) preg_match("/^{$token}:[0-9]+:/s", $data); |
| 178 |
case 'b': |
| 179 |
case 'i': |
| 180 |
case 'd': |
| 181 |
$end = $strict ? '$' : ''; |
| 182 |
return (bool) preg_match("/^{$token}:[0-9.E+-]+;$end/", $data); |
| 183 |
} |
| 184 |
return false; |
| 185 |
} |
| 186 |
|
| 187 |
// Main execution |
| 188 |
if (php_sapi_name() === 'cli') { |
| 189 |
echo "\n"; |
| 190 |
echo str_repeat("=", 70) . "\n"; |
| 191 |
echo " WEFORMS PHP OBJECT INJECTION VULNERABILITY TEST\n"; |
| 192 |
echo str_repeat("=", 70) . "\n"; |
| 193 |
|
| 194 |
// Generate malicious payload |
| 195 |
$malicious_payload = generate_malicious_payload(); |
| 196 |
|
| 197 |
echo "\n📝 Generated malicious payload:\n"; |
| 198 |
echo $malicious_payload . "\n"; |
| 199 |
|
| 200 |
// Test 1: Vulnerable version |
| 201 |
echo "\n\n🔴 TEST 1: VULNERABLE CODE (BEFORE PATCH)\n"; |
| 202 |
echo str_repeat("-", 70) . "\n"; |
| 203 |
$result1 = test_unsafe_unserialize($malicious_payload); |
| 204 |
|
| 205 |
// Clear the log for clean test |
| 206 |
@unlink('/tmp/exploit-test.log'); |
| 207 |
|
| 208 |
// Test 2: Patched version |
| 209 |
echo "\n\n🟢 TEST 2: PATCHED CODE (AFTER PATCH)\n"; |
| 210 |
echo str_repeat("-", 70) . "\n"; |
| 211 |
$result2 = test_safe_unserialize($malicious_payload); |
| 212 |
|
| 213 |
// Check if exploit file was created |
| 214 |
echo "\n\n" . str_repeat("=", 70) . "\n"; |
| 215 |
echo "📊 EXPLOIT DETECTION RESULTS\n"; |
| 216 |
echo str_repeat("=", 70) . "\n"; |
| 217 |
|
| 218 |
if (file_exists('/tmp/exploit-test.log')) { |
| 219 |
echo "❌ VULNERABLE: Exploit log file created!\n"; |
| 220 |
echo "Contents:\n"; |
| 221 |
echo file_get_contents('/tmp/exploit-test.log'); |
| 222 |
echo "\nThis confirms the vulnerability is exploitable.\n"; |
| 223 |
} else { |
| 224 |
echo "� |
| 225 |
SECURE: No exploit log file created!\n"; |
| 226 |
echo "The patch successfully prevented exploitation.\n"; |
| 227 |
} |
| 228 |
|
| 229 |
echo "\n" . str_repeat("=", 70) . "\n"; |
| 230 |
echo "TEST COMPLETE\n"; |
| 231 |
echo str_repeat("=", 70) . "\n\n"; |
| 232 |
} |
| 233 |
|