Files
metasploit-gs/lib/msf/core/exploit/telnet.rb
T
Tod Beardsley 3f69bb8053 Fixes the handling for telnet services when the server is "busy" -- this is common wit HP JetDirect servers, where the server will respond with a busy message up to several seconds after the last connection logged off. While this does mean that credential tests will be skipped, they will at least not be scored incorrectly as false postives.
Also, this removes the disconnect() method in favor of self.sock.close(). Disconnect seems to have a tendency to leave sessions half-closed, which will cause a busy state to never clear. self.sock.close doesn't appear to have this effect if you use a slower bruteforce_speed option (3 seems to work all right).



git-svn-id: file:///home/svn/framework3/trunk@8835 4d416f70-5f16-0410-b530-b9f4589650da
2010-03-16 18:45:50 +00:00

438 lines
12 KiB
Ruby

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,
Opt::RPORT(23),
OptString.new('USERNAME', [ false, 'The username to authenticate as' ]),
OptString.new('PASSWORD', [ false, 'The password for the specified username' ])
], 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 })
# 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 = ''
#
# Some of these regexes borrowed from NeXpose, others added from datasets
#
@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
@failure_regex = /(?:
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
@busy_regex = /(?:
Another\ telnet\ session\ is\ in\ progress | Disconnecting\.\.\.
)/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
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)
@trace = ''
@recvd = ''
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
if(self.banner =~ @login_regex or self.banner =~ @password_regex)
break
elsif self.banner =~ @busy_regex
# It's about to drop connection anyway -- seen on HP JetDirect telnet server
break
end
end
end
rescue ::Timeout::Error
end
self.banner.strip!
# Return the file descriptor to the caller
fd
end
#
# Handle telnet option negotiation
#
# 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.
#
def recv_telnet(fd=self.sock, timeout=datastore['TelnetTimeout'])
data = ''
begin
data = fd.get_once(-1, timeout.to_i)
return nil if not data or data.length == 0
# combine CR+NULL into CR
data.gsub!(/#{CR}#{NULL}/no, CR)
# combine EOL into "\n"
data.gsub!(/#{EOL}/no, "\n")
data.gsub!(/#{IAC}(
[#{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)
''
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)
# Disable Echo
elsif m[1,1] == OPT_ECHO
fd.write(IAC + DONT + OPT_ECHO)
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
@trace << data
@recvd << data
fd.flush
rescue ::EOFError, ::Errno::EPIPE
end
data
end
def login_prompt?
return true if @recvd =~ @login_regex
return false
end
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
def busy_message?
recvn = @recvd.gsub(@busy_regex, '')
if(recvn != @recvd)
@recvd = recvn.strip
return true
end
false
end
def password_prompt?
return true if @recvd =~ @password_regex
return false
end
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.
#
# 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
if @recvd =~ @failure_regex
if @recvd !~ @false_failure_regex
return true
end
end
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
def user
datastore["USERNAME"]
end
def pass
datastore["PASSWORD"]
end
#
# This method logs in as the supplied user by transmitting the username
#
def send_user(user, nsock = self.sock)
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")
end
#
# This method completes user authentication by sending the supplied password
#
def send_pass(pass, nsock = self.sock)
got_prompt = wait_for(@password_regex)
if not got_prompt
print_error("#{rhost} - Something is wrong, didn't get a password prompt")
end
return send_recv("#{pass}\r\n")
end
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(/./)
end
#
# This method transmits a telnet command and does not wait for a response
#
# Resets the @recvd buffer
#
def raw_send(cmd, nsock = self.sock)
@recvd = ''
@trace << cmd
nsock.put(cmd)
end
#
# 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
##
#
# 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