Files
metasploit-gs/lib/msf/core/exploit/ftpserver.rb
T
sinn3r d45cdd61aa Resolve #4507 - respond_to? + send = evil
Since Ruby 2.1, the respond_to? method is more strict because it does
not check protected methods. So when you use send(), clearly you're
ignoring this type of access control. The patch is meant to preserve
this behavior to avoid potential breakage.

Resolve #4507
2015-01-02 13:29:17 -06:00

241 lines
5.7 KiB
Ruby

# -*- coding: binary -*-
module Msf
require 'msf/core/exploit/tcp'
###
#
# This module exposes methods that may be useful to exploits that deal with
# clients that speak the File Transfer Protocol (FTP).
#
###
module Exploit::Remote::FtpServer
include Exploit::Remote::TcpServer
#
# Creates an instance of an FTP exploit module.
#
def initialize(info = {})
super
# Register the options that all FTP exploits may make use of.
register_options(
[
OptPort.new('SRVPORT', [ true, "The local port to listen on.", 21 ]),
OptPort.new('PASVPORT', [ false, "The local PASV data port to listen on (0 is random)", 0 ])
], Msf::Exploit::Remote::FtpServer)
end
# (see Msf::Exploit#setup)
def setup
super
@state = {}
end
# (see TcpServer#on_client_connect)
def on_client_connect(c)
@state[c] = {
:name => "#{c.peerhost}:#{c.peerport}",
:ip => c.peerhost,
:port => c.peerport,
:user => nil,
:pass => nil,
:cwd => '/'
}
active_data_port_for_client(c, 20)
c.put "220 FTP Server Ready\r\n"
end
# Dispatches client requests to command handlers.
#
# Handlers should be named +on_client_command_*+, ending with a
# downcased FTP verb, e.g. +on_client_command_user+. If no handler
# exists for the given command, returns a generic default response.
#
# @example Handle SYST requests
# class Metasploit4 < Msf::Exploit
# include Msf::Exploit::Remote::FtpServer
# ...
# def on_client_command_syst(cmd_conn, arg)
# print_status("Responding to SYST request")
# buf = build_exploit_buffer(cmd_conn)
# cmd_conn.put("215 Unix Type: #{buf}\r\n")
# end
# end
#
# @param (see TcpServer#on_client_data)
# @return (see TcpServer#on_client_data)
def on_client_data(c)
data = c.get_once
return if not data
cmd,arg = data.strip.split(/\s+/, 2)
arg ||= ""
return if not cmd
# Allow per-command overrides
if self.respond_to?("on_client_command_#{cmd.downcase}", true)
return self.send("on_client_command_#{cmd.downcase}", c, arg)
end
case cmd.upcase
when 'USER'
@state[c][:user] = arg
c.put "331 User name okay, need password...\r\n"
return
when 'PASS'
@state[c][:pass] = arg
print_status("#{@state[c][:name]} LOGIN #{@state[c][:user]} / #{@state[c][:pass]}")
c.put "230 Login OK\r\n"
return
when 'QUIT'
c.put "221 Logout\r\n"
return
when 'SYST'
c.put "215 UNIX Type: L8\r\n"
return
when 'TYPE'
c.put "200 Type is set\r\n"
return
when 'CWD'
c.put "250 CWD command successful.\r\n"
return
when 'PWD'
c.put "257 \"#{@state[c][:cwd]}\" is current directory.\r\n"
return
when 'SIZE'
c.put "213 1\r\n"
return
when 'MDTM'
c.put "213 #{Time.now.strftime("%Y%m%d%H%M%S")}\r\n"
return
when 'PORT'
port = arg.split(',')[4,2]
if(not port and port.length == 2)
c.put("500 Illegal PORT command.\r\n")
return
end
port = port.map{|x| x.to_i}.pack('C*').unpack('n')[0]
active_data_port_for_client(c, port)
c.put "200 PORT command successful.\r\n"
return
when 'PASV'
daddr = Rex::Socket.source_address(c.peerhost)
dport = passive_data_port_for_client(c)
@state[c][:daddr] = daddr
@state[c][:dport] = dport
pasv = (daddr.split('.') + [dport].pack('n').unpack('CC')).join(',')
c.put "227 Entering Passive Mode (#{pasv})\r\n"
return
when /^(STOR|MKD|REM|DEL|RMD)$/
c.put "500 Access denied\r\n"
return
else
# Allow per-command overrides
if(self.respond_to?("on_client_unknown_command"))
return self.send("on_client_unknown_command", c, cmd.upcase, arg)
end
print_status("#{@state[c][:name]} UNKNOWN '#{cmd} #{arg}'")
c.put("500 '#{cmd} #{arg}': command not understood.\r\n")
return
end
return
end
def on_client_close(c)
@state.delete(c)
end
def passive_data_port_for_client(c)
@state[c][:mode] = :passive
if(not @state[c][:passive_sock])
s = Rex::Socket::TcpServer.create(
'LocalHost' => '0.0.0.0',
'LocalPort' => datastore['PASVPORT'].to_i,
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
)
dport = s.getsockname[2]
@state[c][:passive_sock] = s
@state[c][:passive_port] = dport
add_socket(s)
end
@state[c][:passive_port]
end
def active_data_port_for_client(c,port)
@state[c][:mode] = :active
connector = Proc.new {
host = c.peerhost.dup
sock = Rex::Socket::Tcp.create(
'PeerHost' => host,
'PeerPort' => port,
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
)
add_socket(sock)
sock
}
@state[c][:active_connector] = connector
@state[c][:active_port] = port
end
# Create a socket for the protocol data, either PASV or PORT,
# depending on the client.
#
# @see http://tools.ietf.org/html/rfc3659 RFC 3659
# @see http://tools.ietf.org/html/rfc959 RFC 959
# @param c [Socket] Control connection socket
#
# @return [Socket] A connected socket for the data connection
# @return [nil] on failure
def establish_data_connection(c)
begin
Timeout.timeout(20) do
if(@state[c][:mode] == :active)
return @state[c][:active_connector].call()
end
if(@state[c][:mode] == :passive)
c = @state[c][:passive_sock].accept
add_socket(c)
return c
end
end
rescue ::Exception => e
print_error("Failed to establish data connection: #{e.class} #{e}")
end
nil
end
end
end