刷题记录:[LCTF]bestphp's revenge
题目复现链接:https://buuoj.cn/challenges
参考链接:https://xz.aliyun.com/t/3341#toc-22
从LCTF WEB签到题看PHP反序列化
LCTF2018-bestphp's revenge 详细题解
这是LCTF的web签到题??打扰了。现在一天一题已经有点跟不上了。。。。
一、知识点
这几个知识点环环相扣形成利用链,所以我一起讲了
session反序列化->soap(ssrf+crlf)->call_user_func激活soap类
1、SoapClient触发反序列化导致ssrf
2、serialize_hander处理session方式不同导致session注入
3、crlf漏洞
二、解题思路
首先,php反序列化没有可利用的类时,可以调用php原生类,参考
反序列化之PHP原生类的利用,
贴上源码和poc讲
//index.php
<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET[f],$_POST);
session_start();
if(isset($_GET[name])){
$_SESSION[name] = $_GET[name];
}
var_dump($_SESSION);
$a = array(reset($_SESSION),'welcome_to_the_lctf2018');
call_user_func($b,$a);
?>
//flag.php
session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
$_SESSION['flag'] = $flag;
}
only localhost can get flag!
很容易想到f
传入extract
覆盖b为我们想要的函数,问题是后面session的利用。
先说SoapClient
,参考从几道CTF题看SOAP安全问题
SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。
其采用HTTP作为底层通讯协议,XML作为数据传送的格式
SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。
那么如果我们能通过反序列化调用SoapClient
向flag.php
发送请求,那么就可以实现ssrf
接下要解决的问题是:
- 在哪触发反序列化
- 如何控制反序列化的内容
这里要知道call_user_func()
函数如果传入的参数是array
类型的话,会将数组的成员当做类名和方法,例如本题中可以先用extract()
将b覆盖成call_user_func()
,reset($_SESSION)
就是$_SESSION['name']
,我们可以传入name=SoapClient
,那么最后call_user_func($b, $a)
就变成call_user_func(array('SoapClient','welcome_to_the_lctf2018'))
,即call_user_func(SoapClient->welcome_to_the_lctf2018)
,由于SoapClient
类中没有welcome_to_the_lctf2018
这个方法,就会调用魔术方法__call()
从而发送请求
那SoapClient
的内容怎么控制呢,贴上大佬的poc
<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
'user_agent' => "N0rth3ty
Cookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4
",
'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;
这里又涉及到crlf,参考[转载]CRLF Injection漏洞的利用与实例分析,我的理解是因为http请求遇到两个
即%0d%0a
,会将前半部分当做头部解析,而将剩下的部分当做体,那么如果头部可控,就可以注入crlf实现修改http请求包。如果我的理解有错,请大佬指正。
这个poc就是利用crlf伪造请求去访问flag.php并将结果保存在cookie为PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4
的session中。
最后一点,就是如何让php反序列化结果可控。这里涉及到php反序列的机制。
php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。
存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。
在php.ini中存在三项配置项:
session.save_path="" --设置session的存储路径
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认是php(5.5.4后改为php_serialize)
PHP内置了多种处理器用于存储$_SESSION数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式:
处理器 | 对应的存储格式 |
---|---|
php | 键名 + 竖线 + 经过serialize()函数反序列化处理的值 |
php_binary | 键名的长度对应的ASCII字符 + 键名 + 经过serialize()函数反序列化处理的值 |
php_serialize(php>=5.5.4) | 经过serialize()函数反序列处理的数组 |
配置选项 session.serialize_handler,通过该选项可以设置序列化及反序列化时使用的处理器。
如果PHP在反序列化存储的$_SEESION数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的伪造,甚至可以伪造任意数据。
当存储是php_serialize处理,然后调用时php去处理,如果这时注入的数据时a=|O:4:"test":0:{}
,那么session中的内容是a:1:{s:1:"a";s:16:"|O:4:"test":0:{}";}
,那么a:1:{s:1:"a";s:16:"
会被php解析成键名,后面就是一个test对象的注入。
正好我们一开始的call_user_func
还没用,可以构造session_start(['serialize_handler'=>'php_serialize'])
达到注入的效果。
三、解题步骤
先注入poc得到的session
触发反序列化使SoapClient发送请求
携带poc中的cookie访问即可得到flag