Mirror of metasploit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ftp.rb 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. # -*- coding: binary -*-
  2. module Msf
  3. require 'msf/core/exploit/tcp'
  4. ###
  5. #
  6. # This module exposes methods that may be useful to exploits that deal with
  7. # servers that speak the File Transfer Protocol (FTP).
  8. #
  9. ###
  10. module Exploit::Remote::Ftp
  11. include Exploit::Remote::Tcp
  12. #
  13. # Creates an instance of an FTP exploit module.
  14. #
  15. def initialize(info = {})
  16. super
  17. # Register the options that all FTP exploits may make use of.
  18. register_options(
  19. [
  20. Opt::RHOST,
  21. Opt::RPORT(21),
  22. OptString.new('FTPUSER', [ false, 'The username to authenticate as', 'anonymous']),
  23. OptString.new('FTPPASS', [ false, 'The password for the specified username', 'mozilla@example.com'])
  24. ], Msf::Exploit::Remote::Ftp)
  25. register_advanced_options(
  26. [
  27. OptInt.new('FTPTimeout', [ true, 'The number of seconds to wait for a reply from an FTP command', 16]),
  28. OptBool.new('FTPDEBUG', [ false, 'Whether or not to print verbose debug statements', false ])
  29. ], Msf::Exploit::Remote::Ftp)
  30. register_autofilter_ports([ 21, 2121])
  31. register_autofilter_services(%W{ ftp })
  32. @ftpbuff = ""
  33. end
  34. #
  35. # This method establishes an FTP connection to host and port specified by
  36. # the 'rhost' and 'rport' methods. After connecting, the banner
  37. # message is read in and stored in the 'banner' attribute.
  38. #
  39. def connect(global = true, verbose = nil)
  40. verbose ||= datastore['FTPDEBUG']
  41. verbose ||= datastore['VERBOSE']
  42. print_status("Connecting to FTP server #{rhost}:#{rport}...") if verbose
  43. fd = super(global)
  44. # Wait for a banner to arrive...
  45. self.banner = recv_ftp_resp(fd)
  46. print_status("Connected to target FTP server.") if verbose
  47. # Return the file descriptor to the caller
  48. fd
  49. end
  50. #
  51. # This method handles establishing datasocket for data channel
  52. #
  53. def data_connect(mode = nil, nsock = self.sock)
  54. if mode
  55. res = send_cmd([ 'TYPE' , mode ], true, nsock)
  56. return nil if not res =~ /^200/
  57. end
  58. # force datasocket to renegotiate
  59. self.datasocket.shutdown if self.datasocket != nil
  60. res = send_cmd(['PASV'], true, nsock)
  61. return nil if not res =~ /^227/
  62. # 227 Entering Passive Mode (127,0,0,1,196,5)
  63. if res =~ /\((\d+)\,(\d+),(\d+),(\d+),(\d+),(\d+)/
  64. # convert port to FTP syntax
  65. datahost = "#{$1}.#{$2}.#{$3}.#{$4}"
  66. dataport = ($5.to_i * 256) + $6.to_i
  67. self.datasocket = Rex::Socket::Tcp.create(
  68. 'PeerHost' => datahost,
  69. 'PeerPort' => dataport,
  70. 'Context' => { 'Msf' => framework, 'MsfExploit' => self }
  71. )
  72. end
  73. self.datasocket
  74. end
  75. #
  76. # This method handles disconnecting our data channel
  77. #
  78. def data_disconnect
  79. self.datasocket.shutdown
  80. self.datasocket = nil
  81. end
  82. #
  83. # Connect and login to the remote FTP server using the credentials
  84. # that have been supplied in the exploit options.
  85. #
  86. def connect_login(global = true, verbose = nil)
  87. verbose ||= datastore['FTPDEBUG']
  88. verbose ||= datastore['VERBOSE']
  89. ftpsock = connect(global, verbose)
  90. if !(user and pass)
  91. print_error("No username and password were supplied, unable to login")
  92. return false
  93. end
  94. print_status("Authenticating as #{user} with password #{pass}...") if verbose
  95. res = send_user(user, ftpsock)
  96. if (res !~ /^(331|2)/)
  97. print_error("The server rejected our username") if verbose
  98. return false
  99. end
  100. if (pass)
  101. print_status("Sending password...") if verbose
  102. res = send_pass(pass, ftpsock)
  103. if (res !~ /^2/)
  104. print_error("The server rejected our password") if verbose
  105. return false
  106. end
  107. end
  108. return true
  109. end
  110. #
  111. # This method logs in as the supplied user by transmitting the FTP
  112. # 'USER <user>' command.
  113. #
  114. def send_user(user, nsock = self.sock)
  115. raw_send("USER #{user}\r\n", nsock)
  116. recv_ftp_resp(nsock)
  117. end
  118. #
  119. # This method completes user authentication by sending the supplied
  120. # password using the FTP 'PASS <pass>' command.
  121. #
  122. def send_pass(pass, nsock = self.sock)
  123. raw_send("PASS #{pass}\r\n", nsock)
  124. recv_ftp_resp(nsock)
  125. end
  126. #
  127. # This method sends a QUIT command.
  128. #
  129. def send_quit(nsock = self.sock)
  130. raw_send("QUIT\r\n", nsock)
  131. recv_ftp_resp(nsock)
  132. end
  133. #
  134. # This method sends one command with zero or more parameters
  135. #
  136. def send_cmd(args, recv = true, nsock = self.sock)
  137. cmd = args.join(" ") + "\r\n"
  138. ret = raw_send(cmd, nsock)
  139. if (recv)
  140. return recv_ftp_resp(nsock)
  141. end
  142. return ret
  143. end
  144. #
  145. # This method transmits the command in args and receives / uploads DATA via data channel
  146. # For commands not needing data, it will fall through to the original send_cmd
  147. #
  148. # For commands that send data only, the return will be the server response.
  149. # For commands returning both data and a server response, an array will be returned.
  150. #
  151. # NOTE: This function always waits for a response from the server.
  152. #
  153. def send_cmd_data(args, data, mode = 'a', nsock = self.sock)
  154. type = nil
  155. # implement some aliases for various commands
  156. if (args[0] =~ /^DIR$/i || args[0] =~ /^LS$/i)
  157. # TODO || args[0] =~ /^MDIR$/i || args[0] =~ /^MLS$/i
  158. args[0] = "LIST"
  159. type = "get"
  160. elsif (args[0] =~ /^GET$/i)
  161. args[0] = "RETR"
  162. type = "get"
  163. elsif (args[0] =~ /^PUT$/i)
  164. args[0] = "STOR"
  165. type = "put"
  166. end
  167. # fall back if it's not a supported data command
  168. if not type
  169. return send_cmd(args, true, nsock)
  170. end
  171. # Set the transfer mode and connect to the remove server
  172. return nil if not data_connect(mode)
  173. # Our pending command should have got a connection now.
  174. res = send_cmd(args, true, nsock)
  175. # make sure could open port
  176. return nil unless res =~ /^(150|125) /
  177. # dispatch to the proper method
  178. if (type == "get")
  179. # failed listings just disconnect..
  180. begin
  181. data = self.datasocket.get_once(-1, ftp_timeout)
  182. rescue ::EOFError
  183. data = nil
  184. end
  185. else
  186. sent = self.datasocket.put(data)
  187. end
  188. # close data channel so command channel updates
  189. data_disconnect
  190. # get status of transfer
  191. ret = nil
  192. if (type == "get")
  193. ret = recv_ftp_resp(nsock)
  194. ret = [ ret, data ]
  195. else
  196. ret = recv_ftp_resp(nsock)
  197. end
  198. ret
  199. end
  200. #
  201. # This method transmits a FTP command and waits for a response. If one is
  202. # received, it is returned to the caller.
  203. #
  204. def raw_send_recv(cmd, nsock = self.sock)
  205. nsock.put(cmd)
  206. nsock.get_once(-1, ftp_timeout)
  207. end
  208. #
  209. # This method reads an FTP response based on FTP continuation stuff
  210. #
  211. def recv_ftp_resp(nsock = self.sock)
  212. found_end = false
  213. resp = ""
  214. left = ""
  215. if !@ftpbuff.empty?
  216. left << @ftpbuff
  217. @ftpbuff = ""
  218. end
  219. while true
  220. data = nsock.get_once(-1, ftp_timeout)
  221. if not data
  222. @ftpbuff << resp
  223. @ftpbuff << left
  224. return data
  225. end
  226. got = left + data
  227. left = ""
  228. # handle the end w/o newline case
  229. enlidx = got.rindex(0x0a.chr)
  230. if enlidx != (got.length-1)
  231. if not enlidx
  232. left << got
  233. next
  234. else
  235. left << got.slice!((enlidx+1)..got.length)
  236. end
  237. end
  238. # split into lines
  239. rarr = got.split(/\r?\n/)
  240. rarr.each do |ln|
  241. if not found_end
  242. resp << ln
  243. resp << "\r\n"
  244. if ln.length > 3 and ln[3,1] == ' '
  245. found_end = true
  246. end
  247. else
  248. left << ln
  249. left << "\r\n"
  250. end
  251. end
  252. if found_end
  253. @ftpbuff << left
  254. print_status("FTP recv: #{resp.inspect}") if datastore['FTPDEBUG']
  255. return resp
  256. end
  257. end
  258. end
  259. #
  260. # This method transmits a FTP command and does not wait for a response
  261. #
  262. def raw_send(cmd, nsock = self.sock)
  263. print_status("FTP send: #{cmd.inspect}") if datastore['FTPDEBUG']
  264. nsock.put(cmd)
  265. end
  266. ##
  267. #
  268. # Wrappers for getters
  269. #
  270. ##
  271. #
  272. # Returns the user string from the 'FTPUSER' option.
  273. #
  274. def user
  275. datastore['FTPUSER']
  276. end
  277. #
  278. # Returns the user string from the 'FTPPASS' option.
  279. #
  280. def pass
  281. datastore['FTPPASS']
  282. end
  283. #
  284. # Returns the number of seconds to wait for a FTP reply
  285. #
  286. def ftp_timeout
  287. (datastore['FTPTimeout'] || 10).to_i
  288. end
  289. protected
  290. #
  291. # This attribute holds the banner that was read in after a successful call
  292. # to connect or connect_login.
  293. #
  294. attr_accessor :banner, :datasocket
  295. end
  296. end