0%

CVE-2019-9081再战

CVE-2019-9081学习&复现的再次学习

上一篇只是简单理解了poc的思路然后进行了复现,但对其中有些地方的原理完全不懂。今天争取在互联网的帮助下可以大概理解。

0x00 遇到的一个有关phpstorm下php脚本调试的问题

  • 问题描述

    我在未配置xdebug之前可以在phpstorm通过点击小甲虫图标对php脚本进行调试,但无法调试运行在服务器上的php项目。在配置xdebug后便可以对项目进行调试,但对脚本调试便总会报错(未检测到调试器扩展)。

  • 问题的解决

    其实最后我感觉可能这是个bug吧。在我为项目设置了默认项目解释器后,就可以像以前一样对脚本进行调试了。

0x01 对$exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);的再次理解

  • $this->app[Kernel::class]

    Kernel::class比较简单,是个固定值:Illuminate\Contracts\Console\Kernel

    对PendingCommand类的app属性的调用,但$this->app中为一个实例化的类的对象,将它按照数组的方式进行调用说明该类实现了ArrayAccess接口,在poc中,反序列化传入的的app的值为实例化的Illuminate\Foundation\Application类简单看一下这个类可以看到Application类所继承的Container类确实实现了ArrayAccess接口。由于app本身没有Kernel::class键所以会调用到Container的offsetget方法,然后依次调用如下方法(offsetGet->make->resolve)。然后需要通过resolve方法进入build方法,build方法可以将传入的类实例化(通过php的反射机制,目前还是不太懂)。但想进入build需要满足$this->isBuildable($concrete, $abstract)即要满足$concrete === $abstract,而$concrete = $this->getConcrete($abstract);。在getConcrete中,

    其判断逻辑(

    if (isset($this->bindings[$abstract])) {

            return $this->bindings\[$abstract]['concrete'];
        }
    

    )可以通过反序列化传入bindings属性中一个键为$abstract(Kernel::class)的一个二维数组,其值便是我们想实例化的类。但这样无法满足$concrete === $abstract所以再次进入make方法。然后再次进入resolve但这时$abstract参数已经是我们像实例化的类。再走一遍上面的流程,便可以成功实例化我们传入的类并最后返回,然后调用其call方法,进入->call($this->command, $this->parameters)后半句。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
     public function offsetGet($key)
    {
    return $this->make($key);
    }
    public function make($abstract, array $parameters = [])
    {
    return $this->resolve($abstract, $parameters);
    }
    protected function resolve($abstract, $parameters = [])
    {
    $abstract = $this->getAlias($abstract);

    $needsContextualBuild = ! empty($parameters) || ! is_null(
    $this->getContextualConcrete($abstract)
    );

    if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
    return $this->instances[$abstract];
    }

    $this->with[] = $parameters;

    $concrete = $this->getConcrete($abstract);

    if ($this->isBuildable($concrete, $abstract)) {
    $object = $this->build($concrete);
    } else {
    $object = $this->make($concrete);
    }

    foreach ($this->getExtenders($abstract) as $extender) {
    $object = $extender($object, $this);
    }

    if ($this->isShared($abstract) && ! $needsContextualBuild) {
    $this->instances[$abstract] = $object;
    }

    $this->fireResolvingCallbacks($abstract, $object);

    $this->resolved[$abstract] = true;

    array_pop($this->with);

    return $object;
    }
    protected function getConcrete($abstract)
    {
    if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
    return $concrete;
    }

    if (isset($this->bindings[$abstract])) {
    return $this->bindings[$abstract]['concrete'];
    }

    return $abstract;
    }
    protected function isBuildable($concrete, $abstract)
    {
    return $concrete === $abstract || $concrete instanceof Closure;
    }
    public function build($concrete)
    {
    if ($concrete instanceof Closure) {
    return $concrete($this, $this->getLastParameterOverride());
    }

    $reflector = new ReflectionClass($concrete);

    if (! $reflector->isInstantiable()) {
    return $this->notInstantiable($concrete);
    }

    $this->buildStack[] = $concrete;

    $constructor = $reflector->getConstructor();

    if (is_null($constructor)) {
    array_pop($this->buildStack);

    return new $concrete;
    }

    $dependencies = $constructor->getParameters();

    $instances = $this->resolveDependencies(
    $dependencies
    );

    array_pop($this->buildStack);

    return $reflector->newInstanceArgs($instances);
    }
  • ->call($this->command, $this->parameters)

这个比上面的好理解,时间不够了,下次一起补上。

7月7日补充:app中的实例化对象(Illuminate\Foundation\Application)无call方法,但其父类Container存在call方法。

第一个分支判断失败,直接进入下面的返回语句,仔细看发现了其中包含敏感函数call_user_func_array$callback可控。再看其具体逻辑,$callback即为函数名,static::getMethodDependencies($container, $callback, $parameters)即为参数列表。进入static::getMethodDependencies方法看其具体逻辑。其利用反射机制获取$callback的对象(目前还是不太懂)。最终将$dependencies和$parameters合并返回,而前者为空,所以函数参数可控。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
{
if (static::isCallableWithAtSign($callback) || $defaultMethod) {
return static::callClass($container, $callback, $parameters, $defaultMethod);
}

return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
return call_user_func_array(
$callback, static::getMethodDependencies($container, $callback, $parameters)
);
});
}
protected static function getMethodDependencies($container, $callback, array $parameters = [])
{
$dependencies = [];

foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
}

return array_merge($dependencies, $parameters);
}

0x02小结

呜呜呜,军训好累。

昨天调phpstorm调试脚本花了好多时间。

今天再看$this->app[Kernel::class]发现懂了很多。但再回看wiki那篇文章其实已经说的很清楚了,但为什么前几天就是看不懂呢^_^