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-b9f4589650da
This commit is contained in:
@@ -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
|
length = 16384 unless length
|
||||||
|
|
||||||
begin
|
begin
|
||||||
return sslsock.read(length)
|
return sslsock.sysread(length)
|
||||||
rescue EOFError, ::Errno::EPIPE
|
rescue EOFError, ::Errno::EPIPE
|
||||||
return nil
|
return nil
|
||||||
end
|
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
|
||||||
Reference in New Issue
Block a user