This patch introduces a really basic RPC service. It is still a long way from its final version
git-svn-id: file:///home/svn/framework3/trunk@5991 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
8eda1cccbf
commit
71c5175a85
|
@ -0,0 +1,166 @@
|
|||
[ INTRODUCTION ]
|
||||
|
||||
The msfrpcd daemon uses the xmlrpc plugin to provide a remote
|
||||
interface to the Metasploit Framework. By default, This service
|
||||
listens on port 55553, uses SSL, and is password protected.
|
||||
|
||||
The RPC interface allows access to a minimal set of framework
|
||||
APIs, covering the core framework, the module set, the job list,
|
||||
and the session table. These APIs can be used to enumerate
|
||||
modules, execute them, and interact with the resulting sessions
|
||||
and jobs.
|
||||
|
||||
|
||||
[ USAGE ]
|
||||
|
||||
To activate the RPC interface, launch msfrpcd, or load msfconsole
|
||||
and load the xmlrpc plugin.
|
||||
|
||||
$ ./msfrpcd -P s3cr3tp4ss
|
||||
- or -
|
||||
msf> load xmlrpc Pass=s3cr3tp4ss
|
||||
|
||||
Once the interface is started, any compatible RPC interface be used
|
||||
to interact with the service. The 'msfrpc' client provides a Ruby
|
||||
shell that can be used to talk to the service.
|
||||
|
||||
$ ./msfrpc -h server_name -P s3cr3tp4ss
|
||||
[*] The 'rpc' object holds the RPC client interface
|
||||
|
||||
>> rpc.call("core.version")
|
||||
=> {"version"=>"3.3-dev"}
|
||||
|
||||
|
||||
[ API - AUTH ]
|
||||
|
||||
Method: auth.login
|
||||
Expects: username, password
|
||||
Returns: { "result" => "success", "token" => "<token>" }
|
||||
Summary: This method is used by rpc.login() to obtain the session key
|
||||
(token) which is sent in subsequent requests. This token uniquely
|
||||
identifies a particular client and can be used by multiple clients,
|
||||
even after the originating TCP session is closed. The RPC client
|
||||
object automatically sends this token with all other method calls.
|
||||
Inactive tokens are destroyed after five minutes of non-use.
|
||||
|
||||
|
||||
[ API - CORE ]
|
||||
|
||||
Method: core.version
|
||||
Expects: none
|
||||
Returns: { "version" => "<framework-version>" }
|
||||
|
||||
|
||||
[ API - MODULE ]
|
||||
|
||||
Method: module.exploits
|
||||
Method: module.auxiliary
|
||||
Method: module.payloads
|
||||
Method. module.encoders
|
||||
Method: module.nops
|
||||
Expects: none
|
||||
Returns: { "modules" => ["module1", "module2", ...] }
|
||||
Summary: This method is used to obtain a list of available modules
|
||||
of the specified type. The resulting module namees can be used in
|
||||
other calls within the module service.
|
||||
|
||||
Method: module.info
|
||||
Expects: module_type, module_name
|
||||
Returns: { "name" => "<name>", ... }
|
||||
Summary: This method returns all shared module fields (name, authors,
|
||||
version, description, etc), but also the list of targets and actions
|
||||
when appropriate.
|
||||
|
||||
Method: module.options
|
||||
Expects: module_type, module_name
|
||||
Returns: { "<option_name>" => { "type" => "integer", ... } }
|
||||
Summary: This method returns a list of all options for a given module,
|
||||
including advanced and evasion options. The returned hash contains
|
||||
detailed information about each option, including its type, its
|
||||
default value, whether it is required, and so on.
|
||||
|
||||
Method: module.compatible_payloads
|
||||
Expects: module_name
|
||||
Returns: { "payloads" => [ "payload1", "payload2", ... ] }
|
||||
Summary: This method only works for exploit modules and returns a
|
||||
list of payloads that are compatible with the specified exploit.
|
||||
|
||||
Method: module.execute
|
||||
Expects: module_type, module_name, options_hash
|
||||
Returns: { "result" => "success" }
|
||||
Summary: This method only works for exploit and auxiliary modules
|
||||
and uses the simplified framework API to launch these modules
|
||||
with the specified options. Option values should be placed into
|
||||
the options_hash argument, including items such as PAYLOAD,
|
||||
TARGET, ACTION, and all required options.
|
||||
|
||||
|
||||
|
||||
[ API - JOB ]
|
||||
|
||||
Method: job.list
|
||||
Expects: none
|
||||
Returns: { "<job_id>" => "<job_name>" }
|
||||
Summary: This method returns a list of running jobs, along with
|
||||
the name of the job.
|
||||
|
||||
Method: job.stop
|
||||
Expects: job_id
|
||||
Returns: { "result" => "success" }
|
||||
Summary: This method kills a specific job by ID
|
||||
|
||||
|
||||
|
||||
[ API - SESSION ]
|
||||
|
||||
Method: session.list
|
||||
Expects: none
|
||||
Returns: { "<session_id>" => { "type" => "shell", ... }}
|
||||
Summary: This method returns a list of active sessions, including
|
||||
the fields type, tunnel_local, tunnel_peer, via_exploit,
|
||||
via_payload, and desc.
|
||||
|
||||
Method: session.stop
|
||||
Expects: session_id
|
||||
Returns: { "result" => "success" }
|
||||
Summary: This method kills a specific session by ID
|
||||
|
||||
Method: session.shell_read
|
||||
Expects: session_id
|
||||
Returns: { "data" => "<shell_data>" }
|
||||
Summary: This method reads any pending output from a session. This
|
||||
method only works for sessions of type "shell" and does not block.
|
||||
|
||||
Method: session.shell_write
|
||||
Expects: session_id, shell_data
|
||||
Returns: { "write_count" => "<number_of_bytes_written>" }
|
||||
Summary: This method writes the specified input into the session.
|
||||
This method only works for sessions of type "shell" and does not
|
||||
block.
|
||||
|
||||
|
||||
[ EXCEPTIONS ]
|
||||
|
||||
When an error occurs, an exception is thrown on the client side. This
|
||||
exception will be of class XMLRPC::FaultException and the faultCode
|
||||
and faultString methods of this exception will contain defailed
|
||||
information about the problem. Many API calls will raise faultCode
|
||||
of 404 when the specified item is not found. An unhandled, server
|
||||
exception will result in a faultCode of 500 on the client side.
|
||||
|
||||
|
||||
|
||||
[ SECURITY CONSIDERATIONS ]
|
||||
|
||||
At this time, the SSL certificate used by the service is
|
||||
dynamically allocated, making it vulnerable to a man-in-the-middle
|
||||
attack. Future versions will address this by allowing a certificate
|
||||
to be generated and verified.
|
||||
|
||||
The current implementation passes the username and password for the
|
||||
RPC service as parameters on the command line. This can lead to
|
||||
disclosure of the password to other local users on some Unix systems.
|
||||
The msfrpc and msfrpcd applications change the displayed arguments
|
||||
as soon as they are launched, but there is still a brief window of
|
||||
time where another local user may snoop the msfrpcd password. In the
|
||||
future, the password will be specified via TTY or file.
|
|
@ -0,0 +1,9 @@
|
|||
require "msf/core/rpc/service"
|
||||
require "msf/core/rpc/client"
|
||||
|
||||
require "msf/core/rpc/base"
|
||||
require "msf/core/rpc/auth"
|
||||
require "msf/core/rpc/core"
|
||||
require "msf/core/rpc/session"
|
||||
require "msf/core/rpc/module"
|
||||
require "msf/core/rpc/job"
|
|
@ -0,0 +1,32 @@
|
|||
module Msf
|
||||
module RPC
|
||||
class Auth < Base
|
||||
|
||||
def login(user,pass)
|
||||
|
||||
# handle authentication here
|
||||
fail = true
|
||||
@users.each do |u|
|
||||
if(u[0] == user and u[1] == pass)
|
||||
fail = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if(fail)
|
||||
raise ::XMLRPC::FaultException.new(401, "authentication error")
|
||||
end
|
||||
|
||||
token = Rex::Text.rand_text_alphanumeric(32)
|
||||
@tokens[token] = [user, Time.now.to_i, Time.now.to_i]
|
||||
{ "result" => "success", "token" => token }
|
||||
end
|
||||
|
||||
def logout(token)
|
||||
@tokens.delete(token)
|
||||
{ "result" => "success" }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
module Msf
|
||||
module RPC
|
||||
class Base
|
||||
|
||||
def initialize(framework,tokens,users)
|
||||
@framework = framework
|
||||
@tokens = tokens
|
||||
@users = users
|
||||
end
|
||||
|
||||
def authenticate(token)
|
||||
|
||||
stale = []
|
||||
@tokens.each_key do |t|
|
||||
user,ctime,mtime = @tokens[t]
|
||||
if(mtime + 300 < Time.now.to_i)
|
||||
stale << t
|
||||
end
|
||||
end
|
||||
|
||||
stale.each { |t| @tokens.delete(t) }
|
||||
|
||||
if(not @tokens[token])
|
||||
raise ::XMLRPC::FaultException.new(401, "authentication error")
|
||||
end
|
||||
|
||||
@tokens[token][2] = Time.now.to_i
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,66 @@
|
|||
require "xmlrpc/client"
|
||||
require "rex"
|
||||
|
||||
module Msf
|
||||
module RPC
|
||||
|
||||
# Loosely based on the XMLRPC::ClientS class
|
||||
# Reimplemented for Metasploit
|
||||
|
||||
class Client < ::XMLRPC::Client
|
||||
|
||||
attr_accessor :sock, :token
|
||||
|
||||
# Use a TCP socket to do RPC
|
||||
def initialize(info={})
|
||||
|
||||
@buff = ""
|
||||
self.sock = Rex::Socket::Tcp.create(
|
||||
'PeerHost' => info[:host],
|
||||
'PeerPort' => info[:port],
|
||||
'SSL' => info[:ssl]
|
||||
)
|
||||
end
|
||||
|
||||
# This override hooks into the RPCXML library
|
||||
def do_rpc(request,async)
|
||||
self.sock.put(request + "\x00")
|
||||
|
||||
while(not @buff.index("\x00"))
|
||||
resp = self.sock.get_once
|
||||
if (not resp and @buff.index("\x00").nil?)
|
||||
raise RuntimeError, "XMLRPC connection closed"
|
||||
end
|
||||
|
||||
@buff << resp if resp
|
||||
end
|
||||
|
||||
mesg,left = @buff.split("\x00", 2)
|
||||
@buff = left.to_s
|
||||
mesg
|
||||
end
|
||||
|
||||
def login(user,pass)
|
||||
res = self.call("auth.login", user, pass)
|
||||
if(not (res and res['result'] == "success"))
|
||||
raise RuntimeError, "authentication failed"
|
||||
end
|
||||
self.token = res['token']
|
||||
true
|
||||
end
|
||||
|
||||
# Prepend the authentication token as the first parameter
|
||||
# of every call except auth.login. Requires the
|
||||
def call(meth, *args)
|
||||
if(meth != "auth.login")
|
||||
if(not self.token)
|
||||
raise RuntimeError, "client not authenticated"
|
||||
end
|
||||
args.unshift(self.token)
|
||||
end
|
||||
super(meth, *args)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
module Msf
|
||||
module RPC
|
||||
class Core < Base
|
||||
|
||||
def version(token)
|
||||
authenticate(token)
|
||||
{ "version" => ::Msf::Framework::Version }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
module Msf
|
||||
module RPC
|
||||
class Job < Base
|
||||
|
||||
def list(token)
|
||||
authenticate(token)
|
||||
res = {}
|
||||
res['jobs'] = {}
|
||||
@framework.jobs.each do |j|
|
||||
res['jobs'][j[0]] = j[1].name
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def stop(token,jid)
|
||||
authenticate(token)
|
||||
obj = @framework.jobs[jid.to_s]
|
||||
if(not obj)
|
||||
raise ::XMLRPC::FaultException.new(404, "no such job")
|
||||
else
|
||||
obj.stop
|
||||
{ "result" => "success" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,194 @@
|
|||
module Msf
|
||||
module RPC
|
||||
class Module < Base
|
||||
|
||||
def exploits(token)
|
||||
authenticate(token)
|
||||
{ "modules" => @framework.exploits.keys }
|
||||
end
|
||||
|
||||
def auxiliary(token)
|
||||
authenticate(token)
|
||||
{ "modules" => @framework.auxiliary.keys }
|
||||
end
|
||||
|
||||
def payloads(token)
|
||||
authenticate(token)
|
||||
{ "modules" => @framework.payloads.keys }
|
||||
end
|
||||
|
||||
def encoders(token)
|
||||
authenticate(token)
|
||||
{ "modules" => @framework.encoders.keys }
|
||||
end
|
||||
|
||||
def nops(token)
|
||||
authenticate(token)
|
||||
{ "modules" => @framework.nops.keys }
|
||||
end
|
||||
|
||||
|
||||
def info(token, mtype, mname)
|
||||
authenticate(token)
|
||||
|
||||
m = _find_module(mtype,mname)
|
||||
res = {}
|
||||
|
||||
res['name'] = m.name
|
||||
res['description'] = m.description
|
||||
res['license'] = m.license
|
||||
res['filepath'] = m.file_path
|
||||
res['version'] = m.version
|
||||
|
||||
res['references'] = []
|
||||
m.references.each do |r|
|
||||
res['references'] << [r.ctx_id, r.ctx_val]
|
||||
end
|
||||
|
||||
res['authors'] = []
|
||||
m.each_author do |a|
|
||||
res['authors'] << a.to_s
|
||||
end
|
||||
|
||||
if(m.type == "exploit")
|
||||
res['targets'] = {}
|
||||
m.targets.each_index do |i|
|
||||
res['targets'][i] = m.targets[i].name
|
||||
end
|
||||
|
||||
if (m.default_target)
|
||||
res['default_target'] = m.default_target
|
||||
end
|
||||
end
|
||||
|
||||
if(m.type == "auxiliary")
|
||||
res['actions'] = {}
|
||||
m.actions.each_index do |i|
|
||||
res['actions'][i] = m.actions[i].name
|
||||
end
|
||||
|
||||
if (m.default_action)
|
||||
res['default_action'] = m.default_action
|
||||
end
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
|
||||
def compatible_payloads(token, mname)
|
||||
authenticate(token)
|
||||
m = @framework.exploits[mname]
|
||||
if(not m)
|
||||
raise ::XMLRPC::FaultException.new(404, "unknown module")
|
||||
end
|
||||
|
||||
res = {}
|
||||
res['payloads'] = []
|
||||
m.compatible_payloads.each do |k|
|
||||
res['payloads'] << k[0]
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def options(token, mtype, mname)
|
||||
authenticate(token)
|
||||
|
||||
m = _find_module(mtype,mname)
|
||||
|
||||
res = {}
|
||||
|
||||
m.options.each_key do |k|
|
||||
o = m.options[k]
|
||||
res[k] = {
|
||||
'type' => o.type,
|
||||
'required' => o.required,
|
||||
'advanced' => o.advanced,
|
||||
'evasion' => o.evasion,
|
||||
'desc' => o.desc
|
||||
}
|
||||
|
||||
if(not o.default.nil?)
|
||||
res[k]['default'] = o.default
|
||||
end
|
||||
|
||||
if(o.enums.length > 1)
|
||||
res[k]['enums'] = o.enums
|
||||
end
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def execute(token, mtype, mname, opts)
|
||||
authenticate(token)
|
||||
|
||||
begin
|
||||
mod = _find_module(mtype,mname)
|
||||
case mtype
|
||||
when 'exploit'
|
||||
_run_exploit(mod, opts)
|
||||
when 'auxiliary'
|
||||
_run_auxiliary(mod, opts)
|
||||
when 'payload'
|
||||
_run_payload(mod, opts)
|
||||
end
|
||||
|
||||
rescue ::Exception => e
|
||||
$stderr.puts "#{e.class} #{e} #{e.backtrace}"
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def _find_module(mtype,mname)
|
||||
mod = @framework.modules.create(mname)
|
||||
|
||||
if(not mod)
|
||||
raise ::XMLRPC::FaultException.new(404, "unknown module")
|
||||
end
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
def _run_exploit(mod, opts)
|
||||
s = Msf::Simple::Exploit.exploit_simple(mod, {
|
||||
'Payload' => opts['PAYLOAD'],
|
||||
'Target' => opts['TARGET'],
|
||||
'RunAsJob' => true,
|
||||
'Options' => opts
|
||||
})
|
||||
{"result" => "success"}
|
||||
end
|
||||
|
||||
def _run_auxiliary(mod, opts)
|
||||
Msf::Simple::Auxiliary.run_simple(mod, {
|
||||
'Action' => opts['ACTION'],
|
||||
'RunAsJob' => true,
|
||||
'Options' => opts
|
||||
})
|
||||
{"result" => "success"}
|
||||
end
|
||||
|
||||
def _run_payload(mod, opts)
|
||||
badchars = [opts['BadChars'] || ''].pack("H*")
|
||||
|
||||
begin
|
||||
res = Msf::Simple::Payload.generate_simple(mod, {
|
||||
'BadChars' => badchars,
|
||||
'Encoder' => opts['Encoder'],
|
||||
'NoComment' => true,
|
||||
'Format' => 'raw',
|
||||
'Options' => opts
|
||||
})
|
||||
|
||||
{"result" => "success", "payload" => res.unpack("H*")[0]}
|
||||
rescue ::Exception
|
||||
raise ::XMLRPC::FaultException.new(500, "failed to generate")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,75 @@
|
|||
require "xmlrpc/server"
|
||||
require "rex"
|
||||
|
||||
module Msf
|
||||
module RPC
|
||||
class Service < ::XMLRPC::BasicServer
|
||||
|
||||
attr_accessor :service, :state
|
||||
|
||||
def initialize(srvhost, srvport, ssl=false, cert=nil, ckey=nil)
|
||||
self.service = Rex::Socket::TcpServer.create(
|
||||
'LocalHost' => srvhost,
|
||||
'LocalPort' => srvport,
|
||||
'SSL' => ssl
|
||||
)
|
||||
|
||||
self.service.on_client_connect_proc = Proc.new { |client|
|
||||
on_client_connect(client)
|
||||
}
|
||||
self.service.on_client_data_proc = Proc.new { |client|
|
||||
on_client_data(client)
|
||||
}
|
||||
self.service.on_client_close_proc = Proc.new { |client|
|
||||
on_client_close(client)
|
||||
}
|
||||
|
||||
self.state = {}
|
||||
super()
|
||||
end
|
||||
|
||||
def start
|
||||
self.state = {}
|
||||
self.service.start
|
||||
end
|
||||
|
||||
def stop
|
||||
self.state = {}
|
||||
self.service.stop
|
||||
end
|
||||
|
||||
def wait
|
||||
self.service.wait
|
||||
end
|
||||
|
||||
def on_client_close(c)
|
||||
self.state.delete(c)
|
||||
end
|
||||
|
||||
def on_client_connect(c)
|
||||
self.state[c] = ""
|
||||
end
|
||||
|
||||
def on_client_data(c)
|
||||
data = c.get_once(-1)
|
||||
self.state[c] << data if data
|
||||
|
||||
procxml(c)
|
||||
end
|
||||
|
||||
def procxml(c)
|
||||
while(self.state[c].index("\x00"))
|
||||
mesg,left = self.state[c].split("\x00", 2)
|
||||
self.state[c] = left
|
||||
begin
|
||||
res = process(mesg)
|
||||
rescue ::Exception => e
|
||||
$stderr.puts "ERROR: #{e.class} #{e}"
|
||||
end
|
||||
c.put(res+"\x00")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,65 @@
|
|||
module Msf
|
||||
module RPC
|
||||
class Session < Base
|
||||
|
||||
def list(token)
|
||||
authenticate(token)
|
||||
res = {}
|
||||
@framework.sessions.each do |sess|
|
||||
i,s = sess
|
||||
res[s.sid] = {
|
||||
'type' => s.type,
|
||||
'tunnel_local'=> s.tunnel_local,
|
||||
'tunnel_peer' => s.tunnel_peer,
|
||||
'via_exploit' => s.via_exploit,
|
||||
'via_payload' => s.via_payload,
|
||||
'desc' => s.desc
|
||||
}
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def stop(token, sid)
|
||||
authenticate(token)
|
||||
s = _find_session(sid)
|
||||
s.kill
|
||||
{ "result" => "success" }
|
||||
end
|
||||
|
||||
def shell_read(token, sid)
|
||||
authenticate(token)
|
||||
s = _find_session(sid)
|
||||
if(s.type != "shell")
|
||||
raise ::XMLRPC::FaultException.new(403, "session is not a shell")
|
||||
end
|
||||
|
||||
if(not s.rstream.has_read_data?(0))
|
||||
{ "data" => "" }
|
||||
else
|
||||
{ "data" => s.read_shell }
|
||||
end
|
||||
end
|
||||
|
||||
def shell_write(token, sid, data)
|
||||
authenticate(token)
|
||||
s = _find_session(sid)
|
||||
if(s.type != "shell")
|
||||
raise ::XMLRPC::FaultException.new(403, "session is not a shell")
|
||||
end
|
||||
|
||||
{ "write_count" => s.write_shell(data) }
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def _find_session(sid)
|
||||
s = @framework.sessions[sid.to_i]
|
||||
if(not s)
|
||||
raise ::XMLRPC::FaultException.new(404, "unknown session")
|
||||
end
|
||||
s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -104,7 +104,7 @@ begin
|
|||
length = 16384 unless length
|
||||
|
||||
begin
|
||||
return sslsock.read(length)
|
||||
return sslsock.sysread(length)
|
||||
rescue EOFError, ::Errno::EPIPE
|
||||
return nil
|
||||
end
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env ruby
|
||||
#
|
||||
# This user interface allows users to interact with a remote framework
|
||||
# instance through a RPCXML socket.
|
||||
#
|
||||
|
||||
msfbase = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
||||
$:.unshift(File.join(File.dirname(msfbase), 'lib'))
|
||||
$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']
|
||||
|
||||
require 'msf/core/rpc'
|
||||
require 'rex/ui'
|
||||
|
||||
# Declare the argument parser for msfxmld
|
||||
arguments = Rex::Parser::Arguments.new(
|
||||
"-h" => [ true, "Connect to this IP address" ],
|
||||
"-p" => [ true, "Connect to the specified port instead of 55553" ],
|
||||
"-U" => [ true, "Specify the username to access msfrpcd" ],
|
||||
"-P" => [ true, "Specify the password to access msfrpcd" ],
|
||||
"-S" => [ false, "Disable SSL on the XMLRPC socket" ]
|
||||
)
|
||||
|
||||
opts = {
|
||||
'User' => 'msf',
|
||||
'SSL' => true,
|
||||
'ServerPort' => 55553
|
||||
}
|
||||
|
||||
# Parse command line arguments.
|
||||
arguments.parse(ARGV) { |opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
opts['ServerHost'] = val
|
||||
when "-S"
|
||||
opts['SSL'] = false
|
||||
when "-p"
|
||||
opts['ServerPort'] = val
|
||||
when '-U'
|
||||
opts['User'] = val
|
||||
when '-P'
|
||||
opts['Pass'] = val
|
||||
when "-h"
|
||||
print("\nUsage: #{File.basename(__FILE__)} <options>\n" + arguments.usage)
|
||||
exit
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
if(not opts['ServerHost'])
|
||||
$stderr.puts "[*] Error: a server IP must be specified (-h)"
|
||||
$stderr.puts arguments.usage
|
||||
exit(0)
|
||||
end
|
||||
|
||||
if(not opts['Pass'])
|
||||
$stderr.puts "[*] Error: a password must be specified (-P)"
|
||||
$stderr.puts arguments.usage
|
||||
exit(0)
|
||||
end
|
||||
|
||||
$0 = "msfrpc"
|
||||
|
||||
|
||||
rpc = Msf::RPC::Client.new(
|
||||
:host => opts['ServerHost'],
|
||||
:port => opts['ServerPort'],
|
||||
:ssl => opts['SSL']
|
||||
)
|
||||
|
||||
res = rpc.login(opts['User'], opts['Pass'])
|
||||
|
||||
puts "[*] The 'rpc' object holds the RPC client interface"
|
||||
puts ""
|
||||
|
||||
while(ARGV.shift)
|
||||
end
|
||||
|
||||
Rex::Ui::Text::IrbShell.new(binding).run
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env ruby
|
||||
#
|
||||
# This user interface listens on a port and provides clients that connect to
|
||||
# it with an XMLRPC interface to the Metasploit Framework.
|
||||
#
|
||||
|
||||
msfbase = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
||||
$:.unshift(File.join(File.dirname(msfbase), 'lib'))
|
||||
$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']
|
||||
|
||||
require 'msf/base'
|
||||
require 'msf/ui'
|
||||
|
||||
# Declare the argument parser for msfrpcd
|
||||
arguments = Rex::Parser::Arguments.new(
|
||||
"-a" => [ true, "Bind to this IP address instead of loopback" ],
|
||||
"-p" => [ true, "Bind to this port instead of 55553" ],
|
||||
"-U" => [ true, "Specify the username to access msfrpcd" ],
|
||||
"-P" => [ true, "Specify the password to access msfrpcd" ],
|
||||
"-S" => [ false, "Disable SSL on the XMLRPC socket" ],
|
||||
"-f" => [ false, "Run the daemon in the foreground" ],
|
||||
"-h" => [ false, "Help banner" ])
|
||||
|
||||
opts = {
|
||||
'RunInForeground' => true,
|
||||
'SSL' => true,
|
||||
'ServerHost' => '0.0.0.0',
|
||||
'ServerPort' => 55553
|
||||
}
|
||||
|
||||
foreground = false
|
||||
|
||||
|
||||
# Parse command line arguments.
|
||||
arguments.parse(ARGV) { |opt, idx, val|
|
||||
case opt
|
||||
when "-a"
|
||||
opts['ServerHost'] = val
|
||||
when "-S"
|
||||
opts['SSL'] = false
|
||||
when "-p"
|
||||
opts['ServerPort'] = val
|
||||
when '-U'
|
||||
opts['User'] = val
|
||||
when '-P'
|
||||
opts['Pass'] = val
|
||||
when "-f"
|
||||
foreground = true
|
||||
when "-h"
|
||||
print("\nUsage: #{File.basename(__FILE__)} <options>\n" + arguments.usage)
|
||||
exit
|
||||
end
|
||||
}
|
||||
|
||||
if(not opts['Pass'])
|
||||
$stderr.puts "[*] Error: a password must be specified (-P)"
|
||||
exit(0)
|
||||
end
|
||||
|
||||
$0 = "msfrpcd"
|
||||
|
||||
$stderr.puts "[*] XMLRPC starting on #{opts['ServerHost']}:#{opts['ServerPort']} (#{opts['SSL'] ? "SSL" : "NO SSL"})..."
|
||||
|
||||
# Create an instance of the framework
|
||||
$framework = Msf::Simple::Framework.create
|
||||
|
||||
$stderr.puts "[*] XMLRPC initializing..."
|
||||
|
||||
|
||||
# Fork into the background if requested
|
||||
begin
|
||||
if (not foreground)
|
||||
$stderr.puts "[*] XMLRPC backgrounding..."
|
||||
exit(0) if Process.fork()
|
||||
end
|
||||
rescue ::NotImplementedError
|
||||
$stderr.puts "[*] Background mode is not available on this platform"
|
||||
end
|
||||
|
||||
# Run the plugin instance in the foreground.
|
||||
$framework.plugins.load('xmlrpc', opts).run
|
|
@ -0,0 +1,137 @@
|
|||
#!/usr/bin/env ruby
|
||||
#
|
||||
# This plugin provides an msf daemon interface that spawns a listener on a
|
||||
# defined port (default 55554) and gives each connecting client its own
|
||||
# console interface. These consoles all share the same framework instance.
|
||||
# Be aware that the console instance that spawns on the port is entirely
|
||||
# unauthenticated, so realize that you have been warned.
|
||||
#
|
||||
|
||||
require "msf/core/rpc"
|
||||
require "fileutils"
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This class implements the msfd plugin interface.
|
||||
#
|
||||
###
|
||||
class Plugin::XMLRPC < Msf::Plugin
|
||||
|
||||
#
|
||||
# The default local hostname that the server listens on.
|
||||
#
|
||||
DefaultHost = "127.0.0.1"
|
||||
|
||||
#
|
||||
# The default local port that the server listens on.
|
||||
#
|
||||
DefaultPort = 55553
|
||||
|
||||
#
|
||||
# ServerPort
|
||||
#
|
||||
# The local port to listen on for connections. The default is 8888
|
||||
#
|
||||
def initialize(framework, opts)
|
||||
super
|
||||
|
||||
host = opts['ServerHost'] || DefaultHost
|
||||
port = opts['ServerPort'] || DefaultPort
|
||||
ssl = (opts['SSL'] and opts['SSL'].to_s =~ /^[ty]/i) ? true : false
|
||||
cert = opts['SSLCert']
|
||||
ckey = opts['SSLKey']
|
||||
|
||||
user = opts['User'] || "msf"
|
||||
pass = opts['Pass'] || ::Rex::Text.rand_text_alphanumeric(8)
|
||||
|
||||
print_status(" XMLRPC Service: #{host}:#{port} #{ssl ? " (SSL)" : ""}")
|
||||
print_status("XMLRPC Username: #{user}")
|
||||
print_status("XMLRPC Password: #{pass}")
|
||||
|
||||
@users = [ [user,pass] ]
|
||||
self.server = ::Msf::RPC::Service.new(host,port,ssl,cert,ckey)
|
||||
|
||||
# If the run in foreground flag is not specified, then go ahead and fire
|
||||
# it off in a worker thread.
|
||||
if (opts['RunInForeground'] != true)
|
||||
Thread.new {
|
||||
run
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Returns 'xmlrpc'
|
||||
#
|
||||
def name
|
||||
"xmlrpc"
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the plugin description.
|
||||
#
|
||||
def desc
|
||||
"Provides a XMLRPC interface over a listening TCP port."
|
||||
end
|
||||
|
||||
#
|
||||
# The meat of the plugin, sets up handlers for requests
|
||||
#
|
||||
def run
|
||||
|
||||
# Initialize the list of authenticated sessions
|
||||
@tokens = {}
|
||||
|
||||
args = [framework,@tokens,@users]
|
||||
|
||||
# Add handlers for every class
|
||||
self.server.add_handler(::XMLRPC::iPIMethods("auth"),
|
||||
::Msf::RPC::Auth.new(*args)
|
||||
)
|
||||
|
||||
self.server.add_handler(::XMLRPC::iPIMethods("core"),
|
||||
::Msf::RPC::Core.new(*args)
|
||||
)
|
||||
|
||||
self.server.add_handler(::XMLRPC::iPIMethods("session"),
|
||||
::Msf::RPC::Session.new(*args)
|
||||
)
|
||||
|
||||
self.server.add_handler(::XMLRPC::iPIMethods("job"),
|
||||
::Msf::RPC::Job.new(*args)
|
||||
)
|
||||
|
||||
self.server.add_handler(::XMLRPC::iPIMethods("module"),
|
||||
::Msf::RPC::Module.new(*args)
|
||||
)
|
||||
|
||||
# Set the default/catch-all handler
|
||||
self.server.set_default_handler do |name, *args|
|
||||
raise ::XMLRPC::FaultException.new(-99, "Method #{name} missing or wrong number of parameters!")
|
||||
end
|
||||
|
||||
# Start the actual service
|
||||
self.server.start
|
||||
|
||||
# Wait for the service to complete
|
||||
self.server.wait
|
||||
end
|
||||
|
||||
#
|
||||
# Closes the listener service.
|
||||
#
|
||||
def cleanup
|
||||
self.server.stop if self.server
|
||||
self.server = nil
|
||||
end
|
||||
|
||||
#
|
||||
# The XMLRPC instance.
|
||||
#
|
||||
attr_accessor :server
|
||||
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue