CVE-2020-0787 BITS任意文件移动漏洞

漏洞组件:后台智能传输服务模块(BITS)

组件特点:

  1. 从HTTP Web服务器和SMB文件共享下载文件或将文件上传到HTTP Web服务器和SMB文件共享。
  2. BITS将考虑传输成本以及网络使用情况,以便用户的前台工作影响尽可能小
  3. 即使重新启动后,BITS也可以处理网络中断,暂停并自动恢复传输。

RPC服务模式#

img

发现过程#

BITS服务接口#

BITS服务存在多个不同版本的类,是“控件类”的不同迭代,且存在一个旧版“控件类”
img

可以看到存在一个1.0版本的COM类和一个旧版本的类,暂且用新类和旧类指代
新类中存在一个IBackgroundCopyManager接口,旧类存在一个IBackgroundCopyQMgr接口

img

可以看到BITS服务是以System权限执行的

新类分析#

新类调用流程

  1. 创建BIT控制类(CLSID:4991D34B-80A1-4291-83B6-3328366B9097)的实例,通过CoCreateInstance()接口获得一个指向IBackgroundCopyManager的指针
  2. IBackgroundCopyManager,调用IBackgroundCopyManager::CreateJob()获取指向该IBackgroundCopyJob接口的指针。
  3. IBackgroundCopyJob调用IBackgroundCopyJob::AddFile(URL, LOCAL_FILE)将文件添加到下载任务中,这需要两个参数:URL和本地文件路径。
  4. 调用IBackgroundCopyJob::Resume()设置为SUSPENDED状态,并在工作的状态为TRANSFERRED时调用IBackgroundCopyJob::Complete()
1
2
3
4
5
CoCreateInstance(CLSID_4991D34B-80A1-4291-83B6-3328366B9097)   -> IBackgroundCopyManager*
|__ IBackgroundCopyManager::CreateJob() -> IBackgroundCopyJob*
|__ IBackgroundCopyJob::AddFile(URL, LOCAL_FILE)
|__ IBackgroundCopyJob::Resume()
|__ IBackgroundCopyJob::Complete()

旧类分析#

旧类调用流程

  1. 创建传统BIT控制类(CLSID:69AD4AEE-51BE-439B-A92C-86AE490E8B30)的实例,调用CoCreateInstance()获得一个指向IBackgroundCopyQMgr的指针。
  2. IBackgroundCopyQMgr调用IBackgroundCopyQMgr::CreateGroup()获取指向IBackgroundCopyGroup的指针
  3. IBackgroundCopyGroup调用IBackgroundCopyGroup::CreateJob()获取指向IBackgroundCopyJob1指针。
  4. IBackgroundCopyJob1调用IBackgroundCopyJob1::AddFiles()将文件添加到任务中。
  5. 调用IBackgroundCopyJob1::Resume()设置为SUSPENDED状态,,并在工作的状态TRANSFERRED时调用IBackgroundCopyJob1::Complete()
1
2
3
4
5
6
CoCreateInstance(CLSID_69AD4AEE-51BE-439B-A92C-86AE490E8B30)   -> IBackgroundCopyQMgr*
|__ IBackgroundCopyQMgr::CreateGroup() -> IBackgroundCopyGroup*
|__ IBackgroundCopyGroup::CreateJob() -> IBackgroundCopyJob1*
|__ IBackgroundCopyJob1::AddFiles(FILESETINFO)
|__ IBackgroundCopyJob1::Resume()
|__ IBackgroundCopyJob1::Complete()

发现问题 QueryNewJobInterface#

IBackgroundCopyGroup接口的文档中,该接口有13种方法
img

但是OleView显示有15个接口
img

从头文件中qmgr.中找到proc16定义如下:

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

virtual HRESULT QueryNewJobInterface(REFIID iid, IUnknown **pUnk);

img
先与GUID 37668d37-507e-4160-9316-26306d150b12比较,成功后调用CJob::GetJobExternal函数

而GUID 37668d37-507e-4160-9316-26306d150b12IBackgroundCopyJob的GUID,也就是说旧版本的代码可以通过未在文档中列出的QueryNewJobInterface接口,获取新版本中的IBackgroundCopyJob方法指针,也就是说可能会出现跨版本的代码调用

猜测可以完成的流程#

  1. 创建指向旧类的实例,获取指向IBackgroundCopyQMgr接口的指针
  2. 创建一个新组调用IBackgroundCopyQMgr::CreateGroup(),获取一个指向IBackgroundCopyGroup接口的指针
  3. 创建一个job,调用IBackgroundCopyGroup::CreateJob(),获取一个指向IBackgroundCopyJob1接口的指针
  4. 通过IBackgroundCopyJob1::AddFiles()添加文件
  5. 调用IBackgroundCopyGroup::QueryNewJobInterface()方法并获得指向未知接口的指针,假定它是一个IBackgroundCopyJob接口
  6. 通过调用IBackgroundCopyJob接口的Resume() 和 Complete()来恢复和完成job而不是IBackgroundCopyJob1接口
1
2
3
4
5
6
7
CoCreateInstance(CLSID_69AD4AEE-51BE-439B-A92C-86AE490E8B30)   -> IBackgroundCopyQMgr*
|__ IBackgroundCopyQMgr::CreateGroup() -> IBackgroundCopyGroup*
|__ IBackgroundCopyGroup::CreateJob() -> IBackgroundCopyJob1*
|__ IBackgroundCopyJob1::AddFiles(FILESETINFO)
|__ IBackgroundCopyGroup::QueryNewJobInterface() -> IBackgroundCopyJob
|__ IBackgroundCopyJob1::Resume()
|__ IBackgroundCopyJob1::Complete()

poc演示#

\\127.0.0.1\C$\Windows\System32\drivers\etc\hosts下载到本地C:\Temp\test.txt
调用AddFiles创建tmp文件,当前用户
img

调用Resume写入文件,当前用户
img

最后重命名文件
imgimg

此时为System权限

漏洞分析#

客户端调用Resume过程

1
2
3
4
5
6
7
8
9
(CLIENT) IBackgroundCopyJob::Resume()
|
V
(SERVER) CJobExternal::Resume()
|__ CJobExternal::ResumeInternal()
|__ ...
|__ CJob::CheckClientAccess() // Client impersonation
|__ CJob::Resume()
|__ ...

客户端调用Complete过程

1
2
3
4
5
6
7
8
9
(CLIENT) IBackgroundCopyJob::Complete()
|
V
(SERVER) CJobExternal::Complete()
|__ CJobExternal::CompleteInternal()
|__ ...
|__ CJob::CheckClientAccess() // Client impersonation
|__ CJob::Complete()
|__ ...

由于都会读取客户端token权限并进行检查,因此不能提权

正常情况#

1
2
3
4
5
6
7
8
(CLIENT) IBackgroundCopyGroup::CreateJob()
|
V
(SERVER) COldGroupInterface::CreateJob()
|__ COldGroupInterface::CreateJobInternal()
|__ CLockedJobWritePointer::ValidateAccess()
| |__ CJob::CheckClientAccess() // Client impersonation
|__ CJob::GetOldJobExternal() // IBackgroundCopyJob1* returned

返回的是代表客户端的token权限的接口

提权情况#

1
2
3
4
5
(CLIENT) IBackgroundCopyGroup::QueryNewJobInterface()
|
V
(SERVER) COldGroupInterface::QueryNewJobInterface()
|__ CJob::GetJobExternal() // IBackgroundCopyJob* returned

由于在这里未检查token,因此返回了代表服务端System权限的接口给客户端,后续客户端都是以System权限进行Resume和Complete等操作。

漏洞利用#

通过符号链接的方式写入dll进行提权。

  1. 本地创建job进行下载,在tmp文件上设置Oplock
  2. 恢复执行后该服务会写入TMP文件触发Oplock
  3. 此时切换挂载点到对象目录,创建符号链接,tmp文件指向fake.dll,本地文件指向system32文件夹中的dll
  4. 释放Oplock,由于是以System权限进行文件移动,因此会将fake.dll移动到system32文件夹下,实现提权

修补方式#

修改Windows BITS 处理符号链接的方式来修复此漏洞

参考资料#

评论