Windows Error Reporting 0day漏洞分析(CVE-2019-0863)

 

0x00 前言

2018年12月,名为“SandboxEscaper”的一名黑客公开了Windows Error Reporting(WER,Windows错误报告)组件中的一个0day漏洞。深入分析这个漏洞后,我发现了另一个0day漏洞,该漏洞可以用来提升至系统权限。根据微软的安全公告,攻击者一直在实际环境中利用该漏洞发起攻击,攻击活动直到2019年5月份微软推出安全补丁才暂告一段落。

本文将与大家分享这个漏洞的具体原理。

 

0x01 WER工作原理

Windows Error Reporting工具是基于事件的一种反馈架构,用来收集Windows能够探测到的硬件以及软件方面的问题,向微软提交相关信息,并且可以为用户提供可用的解决方案。

比如,如果Windows出现系统崩溃,那么就会生成错误报告,存放到WER报告队列目录中(C:\ProgramData\Microsoft\Windows\WER\ReportQueue),队列中每个报告都有自己的子目录以及唯一对应的Report.wer INI文件(其中带有相关元数据)。为了能让所有进程都能报告错误信息,所有用户都具备ReportQueue目录的写入权限,如下图所示:

图1. Windows Error Reporting队列目录

报告生成后,会被发送到微软以便后续分析。这种交互行为可以通过多种方式来触发,其中一种方式就是利用名为Windows Error Reporting\QueueReporting的计划任务。从安全角度来看,这个任务比较有趣,主要有以下几个因素:

  • 该任务以SYSTEM权限运行,可参考任务对应的Security Options选项
  • 该任务可按需触发
  • 该任务会以固定的命令行参数来运行一个专用的程序:wermgr.exe -upload

图2. Windows Error Reporting计划任务

wermgr.exe在执行后,会与挂起的报告文件以及目录交互。程序会读取并解析相关文件,将这些文件拷贝到其他目录,有时候甚至会删除文件。现在我们已经具有一个较高权限的组件,该组件能够访问任何用户都具备写入权限的文件。如果程序在逻辑实现过程中不够严谨,那么这种操作将引入一些非常严重的安全问题。

 

0x02 滥用文件系统链接

Windows支持不同类型的文件系统链接,我们可以利用这些链接将文件以及目录指向其他文件及目录。一旦链接被系统扫描以及重新解析,就会将用户重定向到目标路径。从安全角度来看,这里最大的风险在于滥用硬链接(hard links)以及挂载点(mount point),因为用户可以将这些组件链接到本来不具备写入权限的目标文件或者目录。

举个例子,比如不具备kernel32.dll写入权限的用户可以创建c:\temp\Dir\x.dll以及C:\Windows\System32\kernel32.dll之间的硬链接。具备重定向到较高权限组件的能力一直都是黑客追求的目标,这样就能读取、写入甚至删除敏感的关键文件。

图3. 硬链接至用户不具备写入权限的文件

 

0x03 漏洞分析

简而言之,攻击者可以利用WER来修改文件权限,利用前文提到的文件系统链接方式,将报告目录中的文件链接至主机上的其他目标文件,以获取对其他任意文件的读取、写入、编辑以及删除权限。

更具体一点,整体漏洞利用场景如下:

  • wermger.exe逐一解析报告目录中的所有文件,将这些文件提交给微软
  • wermger.exe检测到损坏的Report.wer INI文件,就会删除该文件。然而wermger.exe首先会修改该文件的DACL属性,以便删除该文件
  • wermger.exe在读取文件DACL属性以及添加删除权限之间存在较小的一个时间窗口,攻击者利用的正是这个时间窗口。如果攻击者创建这类文件与系统上其他任意文件的一个链接,那么在读取DACL之后,wermgr.exe会错误地修改其他文件的安全描述符。这是非常糟糕的场景。

具体利用原理可分为以下几步。

步骤1:

wermgr.exe -upload执行的第一个操作就是调用wermgr!DoCoreUpload函数,该函数会列出ReportQueue下的所有子目录,读取错误报告并将报告提交给微软:

int64 DoCoreUpload(/* ... */) {
    /* ... */
    Ret = WerpSubmitReportFromStore(ReportPath, /* ... */);
    if (Ret >= 0) {
        /* Report successfully uploaded */
    } else {
        if (Ret == ERROR_FILE_CORRUPT) {
            DeleteCorruptedReportFromStore(ReportPath);
        }
    }
}

步骤2:

wermgr.exe遇到损坏的Report.wer INI文件时,会修改文件的DACL,以便后续执行删除操作。更具体的细节为:

首先,wermgr!DeleteCorruptedReportFromStore会列出报告子目录下的所有文件;

其次,wermgr!PreparePathForDeletion会修改每个文件的权限。这里正是核心bug所在的位置,因为该函数会使用kernel32!GetFileSecurity来读取文件的安全描述符,然后调用kernel32!SetFileSecurity将删除描述符应用于该文件。

int64 PreparePathForDeletion(wchar_t* FileName) {
    PSECURITY_DESCRIPTOR SecurityDescriptor = NULL;
    DWORD BytesRead = 0;
    PDACL Dacl = NULL;
    /* ... */

    if ( !GetFileSecurity(FileName, 
                    DACL_SECURITY_INFORMATION, 
                    NULL, 0, &BytesRead) ) {
        /* ... */
        return;
    }

    SecurityDescriptor = new BYTE[BytesRead];

    if ( !GetFileSecurity(FileName, 
                    DACL_SECURITY_INFORMATION, 
                    SecurityDescriptor, 
                    BytesRead, &BytesRead) ) { 
        /* ... */
        return;
    }

    if ( GetSecurityDescriptorDacl(SecurityDescriptor, 
                         &DaclPresent, 
                         &Dacl, &DaclDefaulted) )
    {
        /* ... */
        HANDLE TokenHandle = NULL;
        PACL NewAcl = NULL;
        EXPLICIT_ACCESS ExplicitAccess = {0};

        /* ... */
        LPVOID UserName = new BYTE[/* ... */];
        GetTokenInformation(TokenHandle, TokenUser, 
                      UserName, &BytesRead);

        ExplicitAccess.Trustee.ptstrName = UserName;
        ExplicitAccess.Trustee.TrusteeType = TRUSTEE_IS_NAME;
        ExplicitAccess.grfAccessMode = GRANT_ACCESS;
        ExplicitAccess.grfAccessPermissions = DELETE | /* ... */;
        /* ... */

        SetEntriesInAcl(1, &ExplicitAccess, Dacl, &NewAcl);
        InitializeSecurityDescriptor(&SecurityDescriptor, 1);
        SetSecurityDescriptorDacl(&SecurityDescriptor, 1, NewAcl, 0);
        SetFileSecurity(FilePath, DACL_SECURITY_INFORMATION, 
                    &SecurityDescriptor);    
    }
}

能够在合适的时机成功创建文件系统链接是非常困难的一个操作,但如果攻击者非常有耐心,可以不断重试,直到成功完成该任务。攻击者很可能以可执行文件(DLL、EXE或者脚本)作为目标,然后以恶意payload覆盖这些文件,最后以SYSTEM权限来执行。

(完)