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 @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 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 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