序列化:
将对象转换成字符串(包括属性名、属性值、属性类型和该对象对应的类名)---->有利于对象的保存和传输,也可让多个文件共享对象。
反序列化:
将字符串重新恢复成对象的成员变量。
php序列化
<?php class test{ public $flag = 'flag{RedSpeed}'; public $name = 'cxk'; public $age ='10'; } $tester = new test; $tester->flag = 'flag{helloworld}'; $tester->name = 'whiteH'; $tester->age = '25'; echo serialize($tester); ?> #输出: O:4:"test":3:{s:4:"flag";s:16:"flag{RedSpeed}";s:4:"name";s:3:"cxk";s:3:"age";s:2:"25";}
_sleep()
调用serialize()函数时,函数会先检查类中是否存在魔术方法_sleep()。如果存在,_sleep()会先被调用,【若存在,会按照sleep()函数的属性序列化设置】然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
<?php class Person { public $sex; public $name; public $age; public function _construct($name="",$age=25,$sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function _sleep(){ $this->name = base64_encode($this->name); return array('name','age');//必须返回一个数组,里边的元素表示返回的属性名称 } } $person = new Person('小明');//赋初值 echo serialize($person); echo '<br/>'; ?> #输出: O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25}
PHP反序列化
<?php class Test{ public $flag = 'flag{****}'; public $name = 'wty'; public $age = '10'; } $tester = new Test(); //实例化一个对象 $tester->flag = 'flag{ashjfkdhdg}'; $tester->name = 'WhiteH'; $tester->age = '18'; $str = serialize($tester); echo $str; echo '<pre>'; var_dump(unserialize($str));//调用反序列化函数,将str字符串转换成对象 ?> #输出: object(Test) #2 (3){ ["flag"]=> string(16) "flag{ashjfkdhdg}" ["name"]=> string(6) "WhiteH" ["age"]=> string(2) "18" }
_wakeup()
与序列化类似。如果存在_wakeup,先调用_wakeup,(预先准备对象资源,返回void),对属性进行初始化,赋值或改变。
<?php class Test{ public $flag = 'flag{****}'; public $name = 'wty'; public $age = '10'; public function __wakeup(){ //调用wakeup方法的时候将flag属性的值改变 $this ->flag = "This is not a flag"; } } $tester = new Test(); //实例化一个对象 $tester->flag = 'flag{ashjfkdhdg}'; $tester->name = 'WhiteH'; $tester->age = '18'; $str = serialize($tester); echo $str; echo '<pre>'; var_dump(unserialize($str));//调用反序列化函数,将str字符串转换成对象 ?>
常见魔术方法:
_construct()------对象new时自动调用 _destruct()--------对象销毁时自动调用 _wakeup()---------使用unserialize()函数时自动调用 _toString()----------当对象被当作字符串输出时自动调用
绕过
反序列化绕过
php7.1+反序列化对类属性不敏感
如果变量前是protected,可换为public
绕过__wakeup(CVE-2016-7124)
版本:PHP5 < 5.6.25 && PHP7 < 7.0.10
利用:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行,并且不会报错,可以被正常反序列化
绕过部分正则
preg_match('/^O:\d+/')匹配序列化字符串是否是对象字符串开头
**:利用+绕过 或 serialize(array(a)); //a为要反序列化的对象(序列化结果开头是a,不影响作为数组元素的$a的析构)
补充:
正则表达式中^的用法
**限定开头:匹配输入的开始,如果多行标示被设置成了true,同时会匹配后面紧跟的字符。 比如 /^A/会匹配"An e"中的A,但是不会匹配"ab A"中的A
**(否)取反:/[^a-z\s]/会匹配"my 3 sisters"中的"3" 这里的”^”的意思是字符类的否定,上面的正则表达式的意思是匹配不是(a到z和空白字符)的字符。
**只要是”^”这个字符是在中括号”[]”中被使用的话就是表示字符类的否定,如果不是的话就是表示限定开头。其实也就是说”[]”代表的是一个字符集,”^”只有在字符集中才是反向字符集的意思。
PHP反序列化字符串逃逸--改变序列化字符串的长度,导致反序列化漏洞
***共同点:
- php序列化后的字符串经过了替换或者修改,导致字符串长度发生变化。
- 总是先进行序列化,再进行替换修改操作。
过滤后导致序列化字符串变长
<?php function filter($str){ return str_replace('bb','ccc',$str); } class A{ public $name = 'aaabb'; public $pass = '123456'; } $AA = new A(); echo serialize($AA);//序列化后的字符串s为5,内容为aaabb,结果为O:1:"A":2:{s:4:"name";s:5:"aaabb";s:4:"pass";s:6:"123456";} $res = filter(serialize($AA)); //过滤后,s仍为6,当内容变为aaaccc.但此时name只能读取到aaacc,无法读到末尾的c,所以就形成了一个字符串的逃逸。每添加一个bb就可逃逸一个字符 $c = unserialize($res); echo $c->pass; ?> //以上代码可以在不直接修改$pass值得情况下简介修改$pass的值 //先序列化代码,将里面不希望出现的字符bb替换成ccc,然后反序列化,最后输出pass变量
所以可将逃逸字符传的长度填充为我们要反序列化的代码长度,即可控制反序列化的结果以及类里的变量值
在name处改为
O:1:"A":2:{s:4:"name";s:5:"aaabb";s:4:"pass";s:6:"123456";}
";s:4:"pass";s:6:"hacker";}
被反序列化后的$name仍为";s:4:"pass";s:6:"hacker";}。所以主要是看filter()函数检测并替换非法字符串。
";s:4:"pass";s:6:"hacker";}长度为27,所以如果给name再赋值27个bb,就会增加27个长度,就可以逃逸";s:4:"pass";s:6:"hacker";}了
public $name = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:6:"hacker";}';
序列化后字符串如下
O:1:"A":2:{s:4:"name";s:54:"bbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:6:"hacker";}";s:4:"pass";s:6:"123456";}
此时,原先的s:4:"pass";s:6:"123456";}由于前面的字符串已经构造了闭合会被舍弃,逃逸出来的s:4:"pass";s:6:"hacker";}就会被当做当前类的属性被继续执行。然后密码就改完啦~
过滤后导致序列化字符串变短
<?php function str_rep($string){ return preg_replace( '/php|test/','', $string);//在string中匹配到php或test就替换为空 } $test['name'] = $_GET['name']; $test['sign'] = $_GET['sign']; $test['number'] = '2020'; $temp = str_rep(serialize($test)); printf($temp); $fake = unserialize($temp); echo '<br>'; print("name:".$fake['name'].'<br>'); print("sign:".$fake['sign'].'<br>'); print("number:".$fake['number'].'<br>'); ?>
执行结果如下
可通过输入name和sign来间接修改number的值
?name=phpphpphpphpphpphpphpphp&&sign=hello";s:4:"sign";s:4:"eval";s:6:"number";s:4:"2000";}
str_rep()中检测到php,会替换为空,实现字符串逃逸,输入8个php就腾出了24个空间,正好包含进了";s:4:"sign";s:54:"hello,然后就达到目的啦
老感觉这个绕过有点SQL注入的意思
举例就是[安洵杯 2019]easy_serialize_php,可以看buuctf的wp
pop链
我现在的感觉就是逆向分析,不断找跳板,进行触发
php-session反序列化
魔术方法:
__get
使用场景:
当希望访问某个对象的未定义的或者不可访问的属性不报错,我们可以在类里定义__get方法,这样系统在此刻会自动调用该方法
定义格式:
public function __get(参数1) ,这里注意必须是2个__,名称必须是__get,而且必须是要带1个参数,多一个少一个都不行