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

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}"))
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