2010-01-13 21:46:48 +00:00
|
|
|
module Msf
|
|
|
|
|
|
|
|
require 'msf/core/exploit/tcp'
|
|
|
|
|
|
|
|
###
|
|
|
|
#
|
|
|
|
# This module exposes methods that may be useful to exploits that deal with
|
|
|
|
# servers that speak the telnet protocol.
|
|
|
|
#
|
|
|
|
###
|
|
|
|
module Exploit::Remote::Telnet
|
|
|
|
|
|
|
|
include Exploit::Remote::Tcp
|
|
|
|
|
|
|
|
# Borrowing constants from Ruby's Net::Telnet class (ruby license)
|
|
|
|
IAC = 255.chr # "\377" # "\xff" # interpret as command
|
|
|
|
DONT = 254.chr # "\376" # "\xfe" # you are not to use option
|
|
|
|
DO = 253.chr # "\375" # "\xfd" # please, you use option
|
|
|
|
WONT = 252.chr # "\374" # "\xfc" # I won't use option
|
|
|
|
WILL = 251.chr # "\373" # "\xfb" # I will use option
|
|
|
|
SB = 250.chr # "\372" # "\xfa" # interpret as subnegotiation
|
|
|
|
GA = 249.chr # "\371" # "\xf9" # you may reverse the line
|
|
|
|
EL = 248.chr # "\370" # "\xf8" # erase the current line
|
|
|
|
EC = 247.chr # "\367" # "\xf7" # erase the current character
|
|
|
|
AYT = 246.chr # "\366" # "\xf6" # are you there
|
|
|
|
AO = 245.chr # "\365" # "\xf5" # abort output--but let prog finish
|
|
|
|
IP = 244.chr # "\364" # "\xf4" # interrupt process--permanently
|
|
|
|
BREAK = 243.chr # "\363" # "\xf3" # break
|
|
|
|
DM = 242.chr # "\362" # "\xf2" # data mark--for connect. cleaning
|
|
|
|
NOP = 241.chr # "\361" # "\xf1" # nop
|
|
|
|
SE = 240.chr # "\360" # "\xf0" # end sub negotiation
|
|
|
|
EOR = 239.chr # "\357" # "\xef" # end of record (transparent mode)
|
|
|
|
ABORT = 238.chr # "\356" # "\xee" # Abort process
|
|
|
|
SUSP = 237.chr # "\355" # "\xed" # Suspend process
|
|
|
|
EOF = 236.chr # "\354" # "\xec" # End of file
|
|
|
|
SYNCH = 242.chr # "\362" # "\xf2" # for telfunc calls
|
|
|
|
|
|
|
|
OPT_BINARY = 0.chr # "\000" # "\x00" # Binary Transmission
|
|
|
|
OPT_ECHO = 1.chr # "\001" # "\x01" # Echo
|
|
|
|
OPT_RCP = 2.chr # "\002" # "\x02" # Reconnection
|
|
|
|
OPT_SGA = 3.chr # "\003" # "\x03" # Suppress Go Ahead
|
|
|
|
OPT_NAMS = 4.chr # "\004" # "\x04" # Approx Message Size Negotiation
|
|
|
|
OPT_STATUS = 5.chr # "\005" # "\x05" # Status
|
|
|
|
OPT_TM = 6.chr # "\006" # "\x06" # Timing Mark
|
|
|
|
OPT_RCTE = 7.chr # "\a" # "\x07" # Remote Controlled Trans and Echo
|
|
|
|
OPT_NAOL = 8.chr # "\010" # "\x08" # Output Line Width
|
|
|
|
OPT_NAOP = 9.chr # "\t" # "\x09" # Output Page Size
|
|
|
|
OPT_NAOCRD = 10.chr # "\n" # "\x0a" # Output Carriage-Return Disposition
|
|
|
|
OPT_NAOHTS = 11.chr # "\v" # "\x0b" # Output Horizontal Tab Stops
|
|
|
|
OPT_NAOHTD = 12.chr # "\f" # "\x0c" # Output Horizontal Tab Disposition
|
|
|
|
OPT_NAOFFD = 13.chr # "\r" # "\x0d" # Output Formfeed Disposition
|
|
|
|
OPT_NAOVTS = 14.chr # "\016" # "\x0e" # Output Vertical Tabstops
|
|
|
|
OPT_NAOVTD = 15.chr # "\017" # "\x0f" # Output Vertical Tab Disposition
|
|
|
|
OPT_NAOLFD = 16.chr # "\020" # "\x10" # Output Linefeed Disposition
|
|
|
|
OPT_XASCII = 17.chr # "\021" # "\x11" # Extended ASCII
|
|
|
|
OPT_LOGOUT = 18.chr # "\022" # "\x12" # Logout
|
|
|
|
OPT_BM = 19.chr # "\023" # "\x13" # Byte Macro
|
|
|
|
OPT_DET = 20.chr # "\024" # "\x14" # Data Entry Terminal
|
|
|
|
OPT_SUPDUP = 21.chr # "\025" # "\x15" # SUPDUP
|
|
|
|
OPT_SUPDUPOUTPUT = 22.chr # "\026" # "\x16" # SUPDUP Output
|
|
|
|
OPT_SNDLOC = 23.chr # "\027" # "\x17" # Send Location
|
|
|
|
OPT_TTYPE = 24.chr # "\030" # "\x18" # Terminal Type
|
|
|
|
OPT_EOR = 25.chr # "\031" # "\x19" # End of Record
|
|
|
|
OPT_TUID = 26.chr # "\032" # "\x1a" # TACACS User Identification
|
|
|
|
OPT_OUTMRK = 27.chr # "\e" # "\x1b" # Output Marking
|
|
|
|
OPT_TTYLOC = 28.chr # "\034" # "\x1c" # Terminal Location Number
|
|
|
|
OPT_3270REGIME = 29.chr # "\035" # "\x1d" # Telnet 3270 Regime
|
|
|
|
OPT_X3PAD = 30.chr # "\036" # "\x1e" # X.3 PAD
|
|
|
|
OPT_NAWS = 31.chr # "\037" # "\x1f" # Negotiate About Window Size
|
|
|
|
OPT_TSPEED = 32.chr # " " # "\x20" # Terminal Speed
|
|
|
|
OPT_LFLOW = 33.chr # "!" # "\x21" # Remote Flow Control
|
|
|
|
OPT_LINEMODE = 34.chr # "\"" # "\x22" # Linemode
|
|
|
|
OPT_XDISPLOC = 35.chr # "#" # "\x23" # X Display Location
|
|
|
|
OPT_OLD_ENVIRON = 36.chr # "$" # "\x24" # Environment Option
|
|
|
|
OPT_AUTHENTICATION = 37.chr # "%" # "\x25" # Authentication Option
|
|
|
|
OPT_ENCRYPT = 38.chr # "&" # "\x26" # Encryption Option
|
|
|
|
OPT_NEW_ENVIRON = 39.chr # "'" # "\x27" # New Environment Option
|
|
|
|
OPT_EXOPL = 255.chr # "\377" # "\xff" # Extended-Options-List
|
|
|
|
|
|
|
|
NULL = "\000"
|
|
|
|
CR = "\015"
|
|
|
|
LF = "\012"
|
|
|
|
EOL = CR + LF
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Creates an instance of a Telnet exploit module.
|
|
|
|
#
|
|
|
|
def initialize(info = {})
|
|
|
|
super
|
|
|
|
|
|
|
|
# Register the options that all Telnet exploits may make use of.
|
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
Opt::RHOST,
|
2010-01-20 03:25:34 +00:00
|
|
|
Opt::RPORT(23),
|
|
|
|
OptString.new('USERNAME', [ false, 'The username to authenticate as' ]),
|
|
|
|
OptString.new('PASSWORD', [ false, 'The password for the specified username' ])
|
2010-01-13 21:46:48 +00:00
|
|
|
], Msf::Exploit::Remote::Telnet)
|
|
|
|
|
|
|
|
register_advanced_options(
|
|
|
|
[
|
|
|
|
OptInt.new('TelnetTimeout', [ true, 'The number of seconds to wait for a reply from a Telnet command', 10]),
|
|
|
|
OptInt.new('TelnetBannerTimeout', [ true, 'The number of seconds to wait for the initial banner', 25])
|
|
|
|
], Msf::Exploit::Remote::Telnet)
|
|
|
|
|
|
|
|
register_autofilter_ports([ 23 ])
|
|
|
|
register_autofilter_services(%W{ telnet })
|
2010-02-09 19:07:02 +00:00
|
|
|
|
|
|
|
# Appended to by each read and gets reset after each send. Doing it
|
|
|
|
# this way lets us deal with partial reads in the middle of expect
|
|
|
|
# strings, e.g., the first recv returns "Pa" and the second returns
|
|
|
|
# "ssword: "
|
|
|
|
@recvd = ''
|
|
|
|
|
|
|
|
#
|
2010-02-20 05:49:40 +00:00
|
|
|
# Some of these regexes borrowed from NeXpose, others added from datasets
|
2010-02-09 19:07:02 +00:00
|
|
|
#
|
2010-02-20 05:49:40 +00:00
|
|
|
@login_regex = /(?:log[io]n( name|)|user(name|id|))\s*\:/i
|
|
|
|
@password_regex = /(?:password|passwd)\s*\:/i
|
|
|
|
@false_failure_regex = /(?:(^\s*last)\ login *\:|allows only\ .*\ Telnet\ Client\ License)/i
|
2010-02-09 19:07:02 +00:00
|
|
|
@failure_regex = /(?:
|
2010-02-20 05:49:40 +00:00
|
|
|
Incorrect | Unknown | Fail | Invalid |
|
|
|
|
Login | Password | Passwd | Username |
|
|
|
|
Unable | Error | Denied | Reject |
|
|
|
|
Refuse | Close | Closing | %\ Bad |
|
|
|
|
Sorry |
|
|
|
|
Not\ on\ system\ console |
|
|
|
|
Enter\ username\ and\ password |
|
|
|
|
Auto\ Apply\ On |
|
|
|
|
\n\*$ |
|
|
|
|
(Login ?|User ?)(name|): |
|
|
|
|
^\s*\<[a-f0-9]+\>\s*$ |
|
|
|
|
^\s*220.*FTP
|
|
|
|
)/mix
|
|
|
|
|
|
|
|
@waiting_regex = /(?:
|
|
|
|
.*please\ wait.* |
|
|
|
|
.*one\ minute.*
|
|
|
|
)/mix
|
|
|
|
|
|
|
|
@success_regex = /(?:
|
|
|
|
list\ of\ built-in |
|
|
|
|
sh.*[\#\$]\s*$ |
|
|
|
|
\[\/\]\s*$ |
|
|
|
|
or\ the\ MENU\ system |
|
|
|
|
Password\ is\ not\ set |
|
|
|
|
logging\ in\ as\ visitor |
|
|
|
|
Login\ successful
|
|
|
|
)/mix
|
2010-01-13 21:46:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
#
|
|
|
|
# This method establishes an Telnet connection to host and port specified by
|
|
|
|
# the RHOST and RPORT options, respectively. After connecting, the banner
|
|
|
|
# message is read in and stored in the 'banner' attribute. This method has the
|
|
|
|
# benefit of handling telnet option negotiation.
|
|
|
|
#
|
|
|
|
def connect(global = true, verbose = true)
|
2010-02-20 05:49:40 +00:00
|
|
|
@trace = ''
|
2010-02-09 19:07:02 +00:00
|
|
|
@recvd = ''
|
2010-01-13 21:46:48 +00:00
|
|
|
fd = super(global)
|
|
|
|
|
|
|
|
self.banner = ''
|
|
|
|
# Wait for a banner to arrive...
|
|
|
|
begin
|
|
|
|
Timeout.timeout(datastore['TelnetBannerTimeout']) do
|
|
|
|
while(true)
|
|
|
|
buff = recv_telnet(fd)
|
|
|
|
self.banner << buff if buff
|
2010-02-09 19:07:02 +00:00
|
|
|
if(self.banner =~ @login_regex or self.banner =~ @password_regex)
|
2010-01-13 21:46:48 +00:00
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
rescue ::Timeout::Error
|
|
|
|
end
|
|
|
|
|
|
|
|
self.banner.strip!
|
|
|
|
|
|
|
|
# Return the file descriptor to the caller
|
|
|
|
fd
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Handle telnet option negotiation
|
|
|
|
#
|
2010-02-09 19:07:02 +00:00
|
|
|
# Appends to the @recvd buffer which is used to tell us whether we're at a
|
|
|
|
# login prompt, a password prompt, or a working shell.
|
|
|
|
#
|
2010-01-20 03:25:34 +00:00
|
|
|
def recv_telnet(fd=self.sock, timeout=datastore['TelnetTimeout'])
|
2010-01-13 21:46:48 +00:00
|
|
|
|
2010-02-22 23:45:43 +00:00
|
|
|
data = ''
|
|
|
|
|
|
|
|
begin
|
2010-01-13 21:46:48 +00:00
|
|
|
data = fd.get_once(-1, timeout.to_i)
|
2010-02-09 19:07:02 +00:00
|
|
|
return nil if not data or data.length == 0
|
2010-01-13 21:46:48 +00:00
|
|
|
|
|
|
|
# combine CR+NULL into CR
|
|
|
|
data.gsub!(/#{CR}#{NULL}/no, CR)
|
|
|
|
|
|
|
|
# combine EOL into "\n"
|
|
|
|
data.gsub!(/#{EOL}/no, "\n")
|
|
|
|
|
2010-02-09 19:07:02 +00:00
|
|
|
data.gsub!(/#{IAC}(
|
2010-01-13 21:46:48 +00:00
|
|
|
[#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|
|
|
|
|
[#{DO}#{DONT}#{WILL}#{WONT}]
|
|
|
|
[#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|
|
|
|
|
#{SB}[^#{IAC}]*#{IAC}#{SE}
|
|
|
|
)/xno) do
|
|
|
|
m = $1
|
|
|
|
|
|
|
|
if m == IAC
|
|
|
|
IAC
|
|
|
|
elsif m == AYT
|
|
|
|
fd.write("YES" + EOL)
|
2010-02-09 19:07:02 +00:00
|
|
|
''
|
2010-01-13 21:46:48 +00:00
|
|
|
elsif m[0,1] == DO
|
|
|
|
if(m[1,1] == OPT_BINARY)
|
|
|
|
fd.write(IAC + WILL + OPT_BINARY)
|
|
|
|
else
|
|
|
|
fd.write(IAC + WONT + m[1,1])
|
|
|
|
end
|
|
|
|
''
|
|
|
|
elsif m[0,1] == DONT
|
|
|
|
fd.write(IAC + WONT + m[1,1])
|
|
|
|
''
|
|
|
|
elsif m[0,1] == WILL
|
|
|
|
if m[1,1] == OPT_BINARY
|
|
|
|
fd.write(IAC + DO + OPT_BINARY)
|
2010-02-20 05:49:40 +00:00
|
|
|
# Disable Echo
|
2010-01-13 21:46:48 +00:00
|
|
|
elsif m[1,1] == OPT_ECHO
|
2010-02-20 05:49:40 +00:00
|
|
|
fd.write(IAC + DONT + OPT_ECHO)
|
2010-01-13 21:46:48 +00:00
|
|
|
elsif m[1,1] == OPT_SGA
|
|
|
|
fd.write(IAC + DO + OPT_SGA)
|
|
|
|
else
|
|
|
|
fd.write(IAC + DONT + m[1,1])
|
|
|
|
end
|
|
|
|
''
|
|
|
|
elsif m[0,1] == WONT
|
|
|
|
fd.write(IAC + DONT + m[1,1])
|
|
|
|
''
|
|
|
|
else
|
|
|
|
''
|
|
|
|
end
|
|
|
|
end
|
2010-02-20 05:49:40 +00:00
|
|
|
@trace << data
|
2010-02-09 19:07:02 +00:00
|
|
|
@recvd << data
|
|
|
|
fd.flush
|
2010-02-22 23:45:43 +00:00
|
|
|
|
|
|
|
rescue ::EOFError, ::Errno::EPIPE
|
|
|
|
end
|
|
|
|
|
2010-02-09 19:07:02 +00:00
|
|
|
data
|
2010-01-13 21:46:48 +00:00
|
|
|
end
|
|
|
|
|
2010-02-09 19:07:02 +00:00
|
|
|
def login_prompt?
|
|
|
|
return true if @recvd =~ @login_regex
|
|
|
|
return false
|
2010-01-20 03:25:34 +00:00
|
|
|
end
|
2010-02-20 05:49:40 +00:00
|
|
|
|
|
|
|
def command_echo?(cmd)
|
|
|
|
recvn = @recvd.gsub(/^(\s*#{cmd}\r?\n\s*|\s*\*+\s*)/, '')
|
|
|
|
if(recvn != @recvd)
|
|
|
|
@recvd = recvn
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
def waiting_message?
|
|
|
|
recvn = @recvd.gsub(@waiting_regex, '')
|
|
|
|
if(recvn != @recvd)
|
|
|
|
@recvd = recvn.strip
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2010-02-09 19:07:02 +00:00
|
|
|
def password_prompt?
|
2010-02-20 05:49:40 +00:00
|
|
|
return true if @recvd =~ @password_regex
|
2010-02-09 19:07:02 +00:00
|
|
|
return false
|
2010-01-20 03:25:34 +00:00
|
|
|
end
|
2010-02-20 05:49:40 +00:00
|
|
|
|
2010-02-09 19:07:02 +00:00
|
|
|
def login_failed?
|
|
|
|
# Naively, failure means matching the failure regex.
|
|
|
|
#
|
|
|
|
# However, this leads to problems with false positives in the case of
|
|
|
|
# "login:" because unix systems commonly show "Last login: Sat Jan 3
|
|
|
|
# 20:22:52" upon successful login, so check against a false-positive
|
|
|
|
# regex, also.
|
|
|
|
#
|
2010-02-20 05:49:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Empty strings should not count
|
|
|
|
if @recvd.strip.length == 0
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
# If we have not seen a newline, this is likely an echo'd prompt
|
|
|
|
if ! @recvd.index("\n")
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
# We do have a set of highly-accurate success patterns
|
|
|
|
if (@recvd =~ @success_regex)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2010-02-09 19:07:02 +00:00
|
|
|
if @recvd =~ @failure_regex
|
|
|
|
if @recvd !~ @false_failure_regex
|
2010-02-20 05:49:40 +00:00
|
|
|
return true
|
2010-01-13 21:46:48 +00:00
|
|
|
end
|
|
|
|
end
|
2010-02-09 19:07:02 +00:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
def login_succeeded?
|
|
|
|
# Much easier to test for failure than success because a few key words
|
|
|
|
# mean failure whereas all kinds of crap is used for success, much of
|
|
|
|
# which also shows up in failure messages.
|
|
|
|
return (not login_failed?)
|
|
|
|
end
|
2010-01-13 21:46:48 +00:00
|
|
|
|
2010-02-09 19:07:02 +00:00
|
|
|
def user
|
|
|
|
datastore["USERNAME"]
|
|
|
|
end
|
|
|
|
def pass
|
|
|
|
datastore["PASSWORD"]
|
2010-01-13 21:46:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
#
|
|
|
|
# This method logs in as the supplied user by transmitting the username
|
|
|
|
#
|
|
|
|
def send_user(user, nsock = self.sock)
|
2010-02-09 19:07:02 +00:00
|
|
|
got_prompt = wait_for(@login_regex)
|
|
|
|
if not got_prompt
|
|
|
|
print_error("#{rhost} - Something is wrong, didn't get a login prompt")
|
|
|
|
end
|
|
|
|
return send_recv("#{user}\r\n")
|
2010-01-13 21:46:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
#
|
|
|
|
# This method completes user authentication by sending the supplied password
|
|
|
|
#
|
|
|
|
def send_pass(pass, nsock = self.sock)
|
2010-02-09 19:07:02 +00:00
|
|
|
got_prompt = wait_for(@password_regex)
|
|
|
|
if not got_prompt
|
|
|
|
print_error("#{rhost} - Something is wrong, didn't get a password prompt")
|
2010-02-07 00:14:29 +00:00
|
|
|
end
|
2010-02-09 19:07:02 +00:00
|
|
|
return send_recv("#{pass}\r\n")
|
2010-01-13 21:46:48 +00:00
|
|
|
end
|
|
|
|
|
2010-02-09 19:07:02 +00:00
|
|
|
def send_recv(msg, nsock = self.sock)
|
|
|
|
raw_send(msg, nsock)
|
|
|
|
recv_all(nsock)
|
|
|
|
return @recvd
|
|
|
|
end
|
|
|
|
|
|
|
|
def recv_all(nsock = self.sock, timeout = tel_timeout)
|
|
|
|
# Make sure we read something in
|
|
|
|
wait_for(/./)
|
2010-01-13 21:46:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
#
|
|
|
|
# This method transmits a telnet command and does not wait for a response
|
|
|
|
#
|
2010-02-09 19:07:02 +00:00
|
|
|
# Resets the @recvd buffer
|
|
|
|
#
|
2010-01-13 21:46:48 +00:00
|
|
|
def raw_send(cmd, nsock = self.sock)
|
2010-02-09 19:07:02 +00:00
|
|
|
@recvd = ''
|
2010-02-20 05:49:40 +00:00
|
|
|
@trace << cmd
|
2010-01-13 21:46:48 +00:00
|
|
|
nsock.put(cmd)
|
|
|
|
end
|
|
|
|
|
2010-02-20 05:49:40 +00:00
|
|
|
#
|
2010-02-09 19:07:02 +00:00
|
|
|
# Wait for the supplied string (or Regexp) to show up on the socket, or a
|
|
|
|
# timeout
|
|
|
|
#
|
|
|
|
def wait_for(expect, nsock = self.sock)
|
|
|
|
if expect.kind_of? Regexp
|
|
|
|
regx = expect
|
|
|
|
else
|
|
|
|
regx = /#{Regexp.quote(expect)}/i
|
|
|
|
end
|
|
|
|
return true if @recvd =~ regx
|
|
|
|
|
|
|
|
resp = ''
|
|
|
|
while (resp and not @recvd =~ regx)
|
|
|
|
resp = recv_telnet(nsock)
|
|
|
|
end
|
|
|
|
|
|
|
|
return (@recvd =~ regx)
|
|
|
|
end
|
|
|
|
|
2010-01-13 21:46:48 +00:00
|
|
|
##
|
|
|
|
#
|
|
|
|
# Wrappers for getters
|
|
|
|
#
|
|
|
|
##
|
|
|
|
|
|
|
|
#
|
|
|
|
# Returns the number of seconds to wait for a telnet reply
|
|
|
|
#
|
|
|
|
def tel_timeout
|
|
|
|
(datastore['TelnetTimeout'] || 10).to_i
|
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
|
|
|
#
|
|
|
|
# This attribute holds the banner that was read in after a successful call
|
|
|
|
# to connect or connect_login.
|
|
|
|
#
|
|
|
|
attr_accessor :banner
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|