string ) called_pops_obj::method_called_on_object() #0 test.php:22 called_pops_obj::method_called_on_object() #1 test.php:22 called_pops_obj::__call() __sleep() #0 /var/www/wordpress-no8/popfinder/test.php:19 serialize() #1 called_pops_obj::__sleep() # AVAILABLE METHODS IN SCOPE # SELECTION: __wakeup() method_called_on_object() __destruct() DateTime::__wakeup() DateInterval::__wakeup() Phar::__destruct() PharData::__destruct() PharFileInfo::__destruct() PDO::__wakeup() PDOStatement::__wakeup() VulnerableObject::__destruct() ``` The popfinder analysis shows that three methods are called on the unserialized object: "__wakeup()" "method_called_on_object()" and "__destruct()" and that a possible exploitable method object is exposed in current scope, "VulnerableObject::__destruct()". References: [1] OWASP - PHP Object Injection - https://www.owasp.org/index.php/PHP_Object_Injection [2] Utilizing Code Reuse/ROP in PHP Application Exploits - http://media.blackhat.com/bh-us-10/presentations/Esser/BlackHat-USA-2010-Esser-Utilizing-Code-Reuse-Or-Return-Oriented-Programming-In-PHP-Application-Exploits-slides.pdf */ $banner = "POPfinder v 0.1 - Emilio Pinna 2013"; function _get_fileline($step=2) { $bt = debug_backtrace(); $bt_step = $bt[$step]; $repr = ''; if (array_key_exists('file', $bt_step)) $repr .= '_' . basename($bt_step['file']); if (array_key_exists('line', $bt_step)) $repr .= '_' . $bt_step['line']; return $repr; } function _get_stackpoint($step=1, $all=True) { $bt = debug_backtrace(); if(count($bt)<=$step) $step = count($bt)-1; if($all) $bt_steps = array_reverse($bt); else $bt_steps = array($bt[$step]); $repr = ''; foreach($bt_steps as $bt_step_num => $bt_step) { $repr .= PHP_EOL . '#' . $bt_step_num . ' '; if (array_key_exists('file', $bt_step)) $repr .= $bt_step['file']; if (array_key_exists('line', $bt_step)) $repr .= ':' . $bt_step['line']; $repr .= ' '; if (array_key_exists('class', $bt_step)) $repr .= $bt_step['class'] . '::'; $repr .= $bt_step['function'] . '()'; } return $repr . PHP_EOL; } function _get_available_pops() { $pops = array(); $classes = get_declared_classes(); foreach( $classes as $c) { if($c == 'called_pops_obj') continue; $methods = get_class_methods($c); foreach($methods as $m) { array_push($pops, array($c, $m)); } } return $pops; } function _parse_arguments($output, $mode, $methods_included, $methods_excluded) { // Get mode argument from REQUEST if available if(!$mode) { if(in_array('popfinder_mode',$_REQUEST) and $_REQUEST['popfinder_mode']) $GLOBALS['mode'] = $_REQUEST['popfinder_mode']; else $GLOBALS['mode'] = 'find'; } else { $GLOBALS['mode'] = $mode; } // Set mode as default with wrong value if(!in_array($GLOBALS['mode'], array('all', 'find', 'set'))) { $GLOBALS['mode'] = 'find'; } // Set included methods according to set mode if($GLOBALS['mode'] == 'all') { $GLOBALS["methods_inc"]=array( '__construct', '__destruct', '__call', '__callStatic', '__get', '__set', '__isset', '__unset', '__sleep', '__wakeup', '__toString', '__invoke', '__set_state', '__clone'); } else { $GLOBALS["methods_inc"] = $methods_included; } $GLOBALS["methods_exc"] = $methods_excluded; // Get output argument from REQUEST if available if(!$output) { if(in_array('popfinder_output',$_REQUEST) and $_REQUEST['popfinder_output']) $GLOBALS["output"] = $_REQUEST['popfinder_output']; else $GLOBALS["output"] = '/tmp/popfinder' . _get_fileline() . '.txt'; } else { $GLOBALS["output"] = $output; } } function wrap_serialized_object( $serialized_object, $output='', $mode='', $methods_included=array(), $methods_excluded=array( '__construct' )) { $GLOBALS["called"] = array(); $GLOBALS["pops"] = _get_available_pops(); _parse_arguments($output, $mode, $methods_included, $methods_excluded); return 'O:15:"called_pops_obj":1:{s:6:"object";' . $serialized_object . '}'; } function _add_called($method, $formatted_params = '', $stackpoint = 3) { array_push($GLOBALS["called"], array($method, $formatted_params, _get_stackpoint($stackpoint))); } function _dump_all() { if(file_exists($GLOBALS["output"])) unlink($GLOBALS["output"]); $called_methods = array(); $outdata = '# ' . $GLOBALS["banner"] . PHP_EOL . PHP_EOL . '# METHODS CALLED ON THE UNSERIALIZED OBJECT' . PHP_EOL . PHP_EOL; foreach (array_values($GLOBALS["called"]) as $value) { // Save it only if $GLOBALS["mode"] != 'set', where only methods_included and excluded are used if($GLOBALS["mode"] != 'set') array_push($called_methods,$value[0]); $outdata .= $value[0] . "() " . $value[1] . " " . $value[2] . PHP_EOL; } $selected_methods = array_diff(array_merge($called_methods,$GLOBALS["methods_inc"]),$GLOBALS["methods_exc"]); $outdata .= PHP_EOL . '# AVAILABLE METHODS IN SCOPE' . PHP_EOL; $outdata .= '# SELECTION: ' . join("() ", $selected_methods) . "()" . PHP_EOL; foreach (array_values($GLOBALS["pops"]) as $value) { if(in_array($value[1],$selected_methods)) $outdata .= $value[0] . "::" . $value[1] . "()" . PHP_EOL; } file_put_contents($GLOBALS["output"], $outdata); } class called_pops_obj { public $object; public function __construct() { _add_called("__construct"); } public function __destruct( ) { _add_called("__destruct"); _dump_all(); if(method_exists($this->object,'__destruct')) $this->object->__destruct(); } public function __call( $name, $arguments ) { _add_called($name, 'Arguments ' . str_replace(PHP_EOL, ' ', print_r($arguments,True))); call_user_func_array(array($this->object,$name), $arguments); } public static function __callStatic( $name, $arguments ) { _add_called($name, '(static) Arguments ' . str_replace(PHP_EOL, ' ', print_r($arguments,True))); } public function __set($name, $value) { _add_called('__set', $name . ' = ' . $value); $this->object->$name = $value; } public function __get($name) { _add_called('__get', $name); return $this->object->$name; } public function __isset($name) { _add_called('__isset', $name); return isset($this->object->$name); } public function __unset($name) { _add_called('__unset', $name); unset($this->object->$name); } public function __sleep() { _add_called('__sleep'); if(method_exists($this->object,'__sleep')) return $this->object->__sleep(); return array(); } public function __wakeup() { _add_called('__wakeup', ''); } public function __toString() { _add_called('__toString'); return strval($this->object); } public function __invoke($arguments = array()) { _add_called("__invoke", 'Arguments ' . str_replace(PHP_EOL, ' ', print_r($arguments,True))); return $this->object($arguments); } public static function __set_state($properties) { _add_called("__set_state", '(static) Arguments ' . str_replace(PHP_EOL, ' ', print_r($properties,True))); } public function __clone() { _add_called('__clone'); return clone $this->object; } } ?>