作为最好的语言的旗舰款漏洞,php反序列化在国内CTF基本有百分百的出场率。不过我基本没做过几道,总结一下顺便做几个题。
反序列化的本质就是注入一个对象然后调用它的某些方法,反序列化过程是只能传递变量不能传递方法的,所以需要程序本身定义了这个对象的这些方法。
最基础的就是利用魔术方法,比较复古的利用方法。
1 | __construct() 当一个对象创建时被调用 |
一个常见套路是绕过wakeup方法(CVE-2016-7124),当低版本(PHP5<5.6.25、PHP7<7.0.10)序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过wakeup的执行。比如把
1 | O:9:"Twosmil1e":1:{s:3:"key";s:8:"twosmi1e";} |
一般是利用不同类同名方法来调用,寻找pop链,主要是细心。
看一道例题,eis2019的ezpop
1 |
|
首先找一个切入点,给出了两个类,而这两个类具有同名函数set,并且set中有写文件操作,很明显就是利用这个函数。set是在A类析构的时候由A类的save函数调用。接下来看set的参数,第一个和第三个是可以直接设置的。第二个参数来自getForStorage函数。getForStorage函数又调用了cleanContents。A类的过程就整理清楚了。
然后b类就按照函数需要缺啥补啥,为了省事serialize选择一个不做改变的函数(strval)。最后看到有个死亡exit,利用伪协议作为前缀base64编码绕过,如果不能正确解码就添加1-4个字符满足base64的条件即可。
最后payload
1 | $b = new B(); |
总之就是注意最后的利用点(文件写入/读取、命令执行)以及同名函数。
另一类是session反序列化,利用session不同的存储格式,php格式和php_serialize格式混合起来用导致漏洞。具体方法就是在php_serialize格式存储的文件传入单竖线(|)+恶意对象的序列化字符串,然后访问使用php格式处理session的文件,session_start函数会进行反序列化引入恶意对象。
当然利用这种方法肯定需要控制session值,除了直接$_SESSION[‘session’] = $_GET[‘session’]这种方法,还可以利用session的upload_process参数,没有显式的赋值但会存储传入的值作为session的键值。
例题是javisoj的phpinfo,很经典
1 |
|
看上去没有传参的地方,但php页面是可以上传数据的,参考https://skysec.top/2018/04/04/amazing-phpinfo/#session-upload-progress
弄一个上传页面,name要和phpinfo中的session.upload_progress.name相同
1 | <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data"> |
抓包修改上传的filename或者UPLOAD_PROGRESS的内容为|O:5:"OowoO":1:{s:4:"mdzz";s:34:"var_dump(glob(dirname(FILE)));";}这种格式就可以执行代码。这题直接glob(‘*’)的话是在根目录,所以要利用dirname(FILE)找一下web目录的位置。
接下来就是最网红的phar反序列化,可能是最近两年最火的php漏洞。特点就是不依赖unserialize,通过上传phar文件通过某些函数触发反序列化。这种隐式方式和session反序列化有些类似。利用方法就是生成一个phar文件并通过某些方法触发反序列化:
1 |
|
大概列举下可以触发phar反序列化的函数
1 | fileatime / filectime / filemtime |
反正传个文件进去的都有可能
绕过过滤的方法:
1 | $z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt'; |
例题就是之前写过的bytecms的ezcms,利用Zip内置类的同名open方法。
说到内置类,还有一个很经典的就是Soap类的call魔法方法实现ssrf,
1 |
|
运行后本地5555端口收到请求
1 | POST /path HTTP/1.1 |
从数据包看到SOAPAction是可控的,存在CRLF注入,但是这里不能修改content-type,所以并不能利用。利用方式是修改user-agent,来自wupco
1 |
|
这样就有了一个post的ssrf
例题是N1CTF2018的easy_harder_php
第一步是注入,有几处sql都是直接拼接字符串的,虽然有个全局的addslashes转义了单引号,但是在insert中把重音符号转化成了单引号
1 | private function get_column($columns){ |
所以可以拼接绕过,一共是三列,可控点在第二列,所以使用aaa`,if(ascii(substr((select password from ctf_users where username=0x61646d696e),%d,1))=%d,sleep(3),0))#这种就可以时间盲注,得到密码nu1ladmin
然后才是反序列化部分,showmess类存在反序列化
1 | function showmess() |
所以注入一个Soap类调用其不存在的getcountry函数时就会触发call方法实现ssrf。就用上边的脚本最后bin2hex一下就行了。
还有种题型是反序列化逃逸,一般是通过preg_replace之类添加或去除了一些内容,导致反序列化结果发生变化。
1 |
|
问题出在filter(serialize($_SESSION))这里,修改了序列化后的值导致逃逸。$_SESSION可以利用变量覆盖控制,控制了img就可以读任意文件了。
1 |
|
这样就把s:59给划到前面的字符串里了,img属性就逃了出来,原本的img属性被挤到大括号后面不会执行。至于为什么加dd那个属性,因为原来数组有三项,改过还得是三个,不然会报错。
后记
写完了感觉好水,不水又没有东西写,只能水水维持生活。越来越感觉CTF是个无底洞,需要的广度大于深度,还是把精力放在挖洞吧,成果比较快。
参考链接
https://skysec.top/2019/07/18/Summary-of-serialization-attacks-Part-3/
https://blog.zsxsoft.com/post/38
https://xz.aliyun.com/t/6699