被LCTF虐自闭了,但是也学到了不少东西。题目质量和运维都很赞。
题目
题目给了源码
1 |
|
这里只需要关注call_user_func
这个回调函数。 call_user_func
— 把第一个参数作为回调函数调用,第一个参数是被调用的回调函数,其余参数是回调函数的参数。 这里调用的回调函数不仅仅是我们自定义的函数,还可以是php的内置函数。比如下面我们会用到的extract。 这里需要注意当我们的第一个参数为数组时,会把第一个值当作类名,第二个值当作方法进行回调。 例如
1 |
|
结果就会调用类myclass
中的say_hello
方法,输出hello!。 还有一个flag.php
1 | session_start(); |
首先想到的是需要构造ssrf去访问flag.php,然后获取flag。再利用变量覆盖把SESSION中的flag打印出来。
PHP中SESSION反序列化机制
可以参考乘物游心师傅的文章:https://blog.spoock.com/2016/10/16/php-serialize-problem/ 在寻找可以接收数组并且能够SSRF的函数时,题目给了hint:反序列化。 题目中并没有反序列化函数,由于session文件内容的格式不好控制,也无法利用phar://进行反序列化,那么基本就可以确定题目与PHP中SESSION的反序列化机制有关。 我们可以利用回调函数来覆盖session默认的序列化引擎。 阿桦师傅的XCTF Final Web1 Writeup:https://www.jianshu.com/p/7d63eca80686中有类似的方法,利用回调函数调用session\_start函数,修改session的位置,再配合LFI进行getshell。 php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。 存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。 在php.ini中存在三项配置项:
1 | session.save_path="" --设置session的存储路径 |
session.serialize_handler存在以下几种
1 | php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值 |
在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set(‘session.serialize_handler’, ‘需要设置的引擎’);。 php_binary引擎格式
1 | <0x04>names:5:"Smi1e"; |
php引擎格式
1 | names:5:"Smi1e"; |
php_searialize引擎格式
1 | a:1:{s:4:"name";s:5:"Smi1e";} |
当序列化的引擎和反序列化的引擎不一致时,就可以利用引擎之间的差异产生序列化注入漏洞。 例如传入
$_SESSION['name']='O:5:"Smi1e":1:{s:4:"test";s:3:"AAA";}';
序列化引擎使用的是php_serialize,那么储存的session文件为
1 | a:1:{s:4:"name";s:5:"O:5:"Smi1e":1:{s:4:"test";s:3:"AAA";}";} |
而反序列化引擎如果使用的是php,就会把作为作为key和value的分隔符。把a:1:{s:4:"name";s:5:"
当作键名,而把O:5:"Smi1e":1:{s:4:"test";s:3:"AAA";}
当作经过serialize()函数处理过的值,最后会把它进行unserialize处理,此时就构成了一次反序列化注入攻击。
寻找可以SSRF的类
题目中的源码并没有类,因此只能去利用php的原生类。 在l3m0n师傅的文章中找到可以利用php原生类SoapClient中的__call方法进行SSRF。 原文地址:https://www.cnblogs.com/iamstudy/articles/unserialize\_in\_php\_inner\_class.html#\_label1\_0 那么本题的思路就清晰了。 **利用回调函数覆盖session序列化引擎为php_serilaze,构造SSRF的Soap类的序列化字符串配合序列化注入写入session文件,然后利用变量覆盖漏洞,覆盖掉变量b为回调函数call_user_func,此时结合我刚开始所说的回调函数调用Soap类的未知方法,触发__call方法进行SSRF访问flag.php。把flag写入session,再把session打印出来即可。**
解题
构造SSRF的Soap类的序列化字符串
1 |
|
本地生成payload:O%3A10%3A%22SoapClient%22%3A3%3A%7Bs%3A3%3A%22uri%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
覆盖序列化引擎并将构造的Soap类序列化字符串写入session文件。 此时session_start()序列化使用的是php引擎。接下里我们覆盖变量b,利用call_user_func调用SoapClient类中的不存在方法,触发__call方法,执行ssrf。并获得访问flag.php的PHPSESSID。
获得的PHPSESSID的SESSION中的flag。
很nice的题目,再次给西电的师傅们点个赞。希望这种比赛以后多一点,少一点辣鸡比赛。