abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · moodle-grades-gradeitems
技术分析文章:https://abraxaslabs.tech/research/moodle-grades-gradeitems
Moodle LMS 5.2.3 - Moodle
类别: IDOR 可达性: 远程
core_grades_get_gradeitems 属于 ajax 以及官方移动服务,在 services.php 中没有 capabilities 键。validate_context 是唯一的校验关卡。随后 grade_item::fetch_all 会导出课程中的每一个成绩项。array_filter 会过滤掉 GRADE_TYPE_NONE、分类项以及课程项。隐藏项不在该过滤条件中。同级的 core_grades_get_grade_tree 已经要求 moodle/grade:viewall 权限。
已选课的学生 POST {courseid} 即可获取学生成绩册 UI 仍然隐藏的隐藏成绩项名称。
| ID | 暂无 CVE |
| CWE | CWE-862 / CWE-200 |
| CVSS | 中危:4.3 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N |
| 产品 | Moodle |
| 受影响版本 | 5.2.3 标签 v5.2.3(344232c15336c71b80f9aca8359ce0e0a9f3d116) |
| 认证 | 已选课学生(moodle/grade:view;缺少 viewall / viewhidden) |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1 |
写入者:课程中已选课的学生。他们向 AJAX 服务 POST {courseid}。响应包含名称、ID 和分类。返回的 schema 中没有分数。不是 RCE。不是 LPE。也不是分数写入。
| 权限 | 方法 / 汇聚点 | 侦察 / 完整性 / 可用性 | 实验验证或推断 |
|---|---|---|---|
| 读取隐藏成绩项的名称、id 和分类 | POST /lib/ajax/service.php?sesskey= core_grades_get_gradeitems {courseid} | 侦察 | 实验验证。HTTP 200,名称包含 MOODLE-GRADES-GRADEITEMS-WITNESS |
| 读取可见成绩项的名称、id 和分类 | 同一响应 | 侦察(本应仅限教师) | 实验验证。名称包含 Visible Lab Assignment |
| 通过官方移动服务获取同一目录 | 同一类,services 包含 MOODLE_OFFICIAL_MOBILE_SERVICE | 侦察 | 根据 services.php 推断。实验使用 AJAX。 |
遍历连续的 courseid 值以获取学生可进入的课程 | 同一方法,猜测 id | 侦察 | 推断。实验使用预置课程 id 2 |
| 在学生成绩册 UI 中看到隐藏项 | GET /grade/report/user/index.php?id= | 不适用 | 实验验证 已关闭。hidden-in-ui=False,visible-in-ui=True |
| 无会话调用 | 使用伪造 sesskey 的 AJAX | 不适用 | 实验验证 已关闭。servicerequireslogin |
| 以已登录但未选课的用户身份调用 | 外部人员 AJAX,同一 courseid | 不适用 | 实验验证 已关闭。requireloginerror |
| 读取其他学生的分数或写入成绩 | 不适用 | 不适用 | 已关闭。返回键为 id、itemname、category |
| PHP RCE / OS LPE | 不适用 | 不适用 | 已关闭。此遗留问题仅为目录读取 |
已经拥有 moodle/grade:viewall 权限的教师或管理者本就能看到该目录。访客 / 未认证调用者仍然需要课程上下文。
同样的遗留问题:Blackboard 图片文件读取、enrol_database SQLi、成绩反馈 IDOR。
在 MSA-26-0042/0043 之后,我锁定了 moodle/moodle v5.2.3。成绩相关的 Web 服务就在该 MSA 窗口内。get_gradeitems.php 仍然没有 require_capability。学生界面已经隐藏了该项。但 AJAX 目录没有。
已经记录下来的错误方向:把未认证 AJAX 200 当作泄露(响应体是 servicerequireslogin);因为方法名里有 grade 就称其为分数 IDOR(schema 只是名称);以为 get_grade_tree 是同一个遗留问题(它已经要求 viewall)。一个反弹 shell。纯属演戏。
在 php:8.3-apache + MariaDB 11.4 上运行实时 Moodle v5.2.3。种子数据将 labstudent 选入课程,将 laboutsider 排除在课程之外,并创建一个可见的手动评分项以及一个隐藏评分项,其名称即为见证。run.sh 会复制本地锁定版本,或执行 git clone --depth 1 --branch v5.2.3。Moodle 源码树不包含在本包中。
python3 moodle-grades-gradeitems-Abraxas-Labs.py
或
cd lab
./run.sh
仅针对 http://127.0.0.1:19590。组合项目 moodle-grades-gradeitems。运行结束后技术栈会被拆除。
SUCCESS moodle-grades-gradeitems student-ajax=200 hidden-in-ajax=yes hidden-in-ui=no visible-in-ajax=yes visible-in-ui=yes outsider-error=yes unauth-error=yes MOODLE-GRADES-GRADEITEMS-WITNESS
在 validate_context 之后,要求与 get_grade_tree 相同的权限上限:require_capability('moodle/grade:viewall', $context)。同时,除非调用者拥有 moodle/grade:viewhidden 权限,否则丢弃隐藏项。添加一个 Behat/PHPUnit 测试用例:已选课学生、隐藏的手动评分项,core_grades_get_gradeitems 必须省略该项(或返回 403)。学生成绩册界面已经隐藏了该项。
get_gradeitems.phpget_grade_tree.phpservices.php(core_grades_get_gradeitems,无 capabilities)GNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。不提供任何担保。