本文总结php的反序列化,有php反序列字符串逃逸,php反序列化pop链构造,php反序列化原生类的利用,phar反序列化,session反序列化,反序列化小技巧,并附带ctf小题来说明,还有php反序列化的预防方法(个人想法),建议按需查看,如有错误还望斧正。
如非特别说明运行环境为PHP 7.2.33-1+ubuntu18.04.1
为什么要序列化?
序列化可以将对象,类,数组,变量,匿名函数等,转换为字符串,这样用户就方便存储和传输,同时方便恢复使用,对服务器也减轻一定的压力。
序列化基础
序列化为字符串时候,变量和参数之间用;隔开,同一个变量和参数间用:号隔开,以}作为结尾,具体结构,用以下代码来看下结构
<?php
class Lmg
{
public $name = 'Lmg';
public $age = 19;
public $blog = 'https://lmg66.github.io';
}
$lmg1 = new Lmg;
echo serialize($lmg1)."\n";
?>
序列化属性
在一个可以序列化的字符串后加其他参数不影响序列化后的结果
如:
测试代码:
<?php
class Lmg
{
public $name = 'Lmg';
public $age = 19;
public $blog = 'https://lmg66.github.io';
}
$lmg1 = new Lmg;
echo serialize($lmg1)."\n";
$Lmg2 = serialize($lmg1).'s:4:"blog";s:23:"https://lmg66.github.io";}';
echo $Lmg2."\n";
print_r($lmg1);
print_r(unserialize($Lmg2));
?>
效果:可以发现,后面加了其他参数并不影响序列化后的结果
显示变量长度和实际长度不匹配就会报错,在这里在某些情况就会产生字符串逃逸
如:
测试代码:
<?php
class Lmg
{
public $name = 'Lmg';
public $age = 19;
public $blog = 'https://lmg66.github.io';
}
$lmg4 = 'O:3:"Lmg":3:{s:4:"name";s:3:"Lmg";s:3:"age";i:19;s:4:"blog";s:23:"https://lmg66.github.io";}';
$lmg5 = 'O:3:"Lmg":3:{s:4:"uname";s:3:"Lmg";s:3:"age";i:19;s:4:"blog";s:23:"https://lmg66.github.io";}';
print_r(unserialize($lmg4));
print_r(unserialize($lmg5));
?>
效果:可以发现我改了变量名name使它的长度和实际4不符,就发生了报错,改其他类似
反序列常见魔术函数总览,可构造pop链
__construct: 当创建类的时候自动调用,也就是构造函数,无返回值
__destruct: 当类实例子销毁时候自动调用,也就是析构函数,无返回值,其不能带参数
__toString:当对象被当做一个字符串使用时调用,比如echo $obj 。
__sleep: 当类的实例被序列化时调用(其返回需要一个数组或者对象,一般返回对象的$this,返回的值被用来做序列化的值,如果不返回,表示序列化失败)
__wakeup: 当反序列化时被调用
__call:当调用对象中不存在的方法会自动调用该方法。
__get:在调用私有属性的时候会自动执行
__isset()在不可访问的属性上调用isset()或empty()触发
__unset()在不可访问的属性上使用unset()时触发
反序列化字符串逃逸(替换后导致字符串变长)
字符串逃逸利用的是反序列化的属性如上文,出现原因是在序列化前进行了字符串的替换,导致字符串被拓冲,可以将后面的字符串挤出去,挤到后一个对象的变量从而改变其他的变量值,造成逃逸。
如:
测试代码:
<?php
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='aaaa';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass;
?>
序列化后的字符串为:
O:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
如果能让name变量的参数为
";s:4:"pass";s:6:"hack";}
用}号闭合掉后面的pass参数,就能改pass变量的参数值从而逃逸
要解决的就是这个位置的长度问题,只用读取到足够的长度,才会停止
可以发现在序列化进行了字符串的替换,但替换的时候bb替换成了ccc,也就是字符串变长了,达到我们上面想要的目的
先判断想要构造的字符串长度
<?php
$lmg = '";s:4:"pass";s:6:"hack";}';
echo strlen($lmg)."\n";
// $lmg3 = "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
// echo strlen($lmg3);
// $lmg2 = "bb";
// echo str_repeat($lmg2, 25);
?>