metasploit-framework/lib/msf/core/exploit/ftpserver.rb

198 lines
3.8 KiB
Ruby

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 ])
], Msf::Exploit::Remote::FtpServer)
end
def setup
super
@state = {}
end
def on_client_connect(c)
@state[c] = {
:name => "#{c.peerhost}:#{c.peerport}",
:ip => c.peerhost,
:port => c.peerport,
:user => nil,
:pass => nil
}
active_data_port_for_client(c, 20)
c.put "220 FTP Server Ready\r\n"
end
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}"))
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 \"/\" 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
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' => 0,
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
)
dport = s.getsockname[2]
@state[c][:passive_sock] = s
@state[c][:passive_port] = dport
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 }
)
}
@state[c][:active_connector] = connector
@state[c][:active_port] = port
end
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)
return @state[c][:passive_sock].accept
end
end
rescue ::Exception => e
print_error("Failed to establish data connection: #{e.class} #{e}")
end
nil
end
end
end