240 lines
5.7 KiB
Ruby
240 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 MetasploitModule < 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
|