Initial JSON-RPC servlet and support architecture
This commit is contained in:
+32
-11
@@ -1,14 +1,35 @@
|
||||
# -*- coding: binary -*-
|
||||
require "msf/core/rpc/service"
|
||||
require "msf/core/rpc/client"
|
||||
module Msf::RPC
|
||||
require 'msf/core/rpc/v10/constants'
|
||||
|
||||
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"
|
||||
require "msf/core/rpc/console"
|
||||
require "msf/core/rpc/db"
|
||||
require "msf/core/rpc/plugin"
|
||||
require 'msf/core/rpc/v10/service'
|
||||
require 'msf/core/rpc/v10/client'
|
||||
|
||||
require 'msf/core/rpc/v10/rpc_auth'
|
||||
require 'msf/core/rpc/v10/rpc_base'
|
||||
require 'msf/core/rpc/v10/rpc_console'
|
||||
require 'msf/core/rpc/v10/rpc_core'
|
||||
require 'msf/core/rpc/v10/rpc_db'
|
||||
require 'msf/core/rpc/v10/rpc_job'
|
||||
require 'msf/core/rpc/v10/rpc_module'
|
||||
require 'msf/core/rpc/v10/rpc_plugin'
|
||||
require 'msf/core/rpc/v10/rpc_session'
|
||||
|
||||
|
||||
module JSON
|
||||
autoload :Dispatcher, 'msf/core/rpc/json/dispatcher'
|
||||
autoload :DispatcherHelper, 'msf/core/rpc/json/dispatcher_helper'
|
||||
autoload :RpcCommand, 'msf/core/rpc/json/rpc_command'
|
||||
autoload :RpcCommandFactory, 'msf/core/rpc/json/rpc_command_factory'
|
||||
|
||||
# exception classes
|
||||
autoload :Error, 'msf/core/rpc/json/error'
|
||||
autoload :ParseError, 'msf/core/rpc/json/error'
|
||||
autoload :InvalidRequest, 'msf/core/rpc/json/error'
|
||||
autoload :MethodNotFound, 'msf/core/rpc/json/error'
|
||||
autoload :InvalidParams, 'msf/core/rpc/json/error'
|
||||
autoload :InternalError, 'msf/core/rpc/json/error'
|
||||
autoload :ServerError, 'msf/core/rpc/json/error'
|
||||
autoload :ApplicationServerError, 'msf/core/rpc/json/error'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
require 'json'
|
||||
require 'msf/core/rpc'
|
||||
|
||||
module Msf::RPC::JSON
|
||||
class Dispatcher
|
||||
JSON_RPC_VERSION = '2.0'
|
||||
|
||||
attr_reader :framework
|
||||
attr_reader :command
|
||||
|
||||
def initialize(framework)
|
||||
@framework = framework
|
||||
@command = nil
|
||||
end
|
||||
|
||||
def set_command(command)
|
||||
@command = command
|
||||
$stderr.puts("Msf::RPC::JSON::Dispatcher.set_command(): command=#{command}, @command=#{@command}") # TODO: remove
|
||||
end
|
||||
|
||||
def process(source)
|
||||
begin
|
||||
$stderr.puts("Msf::RPC::JSON::Dispatcher.process(): source=#{source}") # TODO: remove
|
||||
request = parse_json_request(source)
|
||||
$stderr.puts("Msf::RPC::JSON::Dispatcher.process(): request=#{request}") # TODO: remove
|
||||
if request.is_a?(Array)
|
||||
$stderr.puts("Msf::RPC::JSON::Dispatcher.process(): batch request") # TODO: remove
|
||||
# If the batch rpc call itself fails to be recognized as an valid
|
||||
# JSON or as an Array with at least one value, the response from
|
||||
# the Server MUST be a single Response object.
|
||||
raise InvalidRequest.new if request.empty?
|
||||
# process batch request
|
||||
response = request.map { |r| process_request(r) }
|
||||
# A Response object SHOULD exist for each Request object, except that
|
||||
# there SHOULD NOT be any Response objects for notifications.
|
||||
# Remove nil responses from response array
|
||||
response.compact!
|
||||
else
|
||||
response = process_request(request)
|
||||
end
|
||||
rescue ParseError, InvalidRequest => e
|
||||
# If there was an error in detecting the id in the Request object
|
||||
# (e.g. Parse error/Invalid Request), then the id member MUST be
|
||||
# Null. Don't pass request obj when building the error response.
|
||||
response = self.class.create_error_response(e)
|
||||
rescue RpcError => e
|
||||
# other JSON-RPC errors should include the id from the Request object
|
||||
response = self.class.create_error_response(e, request)
|
||||
rescue => e
|
||||
response = self.class.create_error_response(ApplicationServerError.new(e), request)
|
||||
end
|
||||
|
||||
# When a rpc call is made, the Server MUST reply with a Response, except
|
||||
# for in the case of Notifications. The Response is expressed as a single
|
||||
# JSON Object.
|
||||
self.class.to_json(response)
|
||||
end
|
||||
|
||||
def process_request(request)
|
||||
begin
|
||||
$stderr.puts("Msf::RPC::JSON::Dispatcher.process_request(): request=#{request}") # TODO: remove
|
||||
|
||||
if !validate_rpc_request(request)
|
||||
response = self.class.create_error_response(InvalidRequest.new)
|
||||
return response
|
||||
end
|
||||
|
||||
# dispatch method execution to command
|
||||
result = @command.execute(request[:method], request[:params])
|
||||
$stderr.puts("Msf::RPC::JSON::Dispatcher.process_request(): dispatch result=#{result}, result.class=#{result.class}") # TODO: remove
|
||||
|
||||
# A Notification is a Request object without an "id" member. A Request
|
||||
# object that is a Notification signifies the Client's lack of interest
|
||||
# in the corresponding Response object, and as such no Response object
|
||||
# needs to be returned to the client. The Server MUST NOT reply to a
|
||||
# Notification, including those that are within a batch request.
|
||||
if request.key?(:id)
|
||||
response = self.class.create_success_response(result, request)
|
||||
else
|
||||
response = nil
|
||||
end
|
||||
|
||||
response
|
||||
rescue ArgumentError
|
||||
raise InvalidParams.new
|
||||
rescue Msf::RPC::Exception => e
|
||||
ApplicationServerError.new(e.message, data: { code: e.code })
|
||||
# rescue => e
|
||||
# raise ApplicationServerError.new(e)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_rpc_request(request)
|
||||
required_members = %i(jsonrpc method)
|
||||
member_types = {
|
||||
# A String specifying the version of the JSON-RPC protocol.
|
||||
jsonrpc: [String],
|
||||
# A String containing the name of the method to be invoked.
|
||||
method: [String],
|
||||
# If present, parameters for the rpc call MUST be provided as a Structured
|
||||
# value. Either by-position through an Array or by-name through an Object.
|
||||
# * by-position: params MUST be an Array, containing the values in the
|
||||
# Server expected order.
|
||||
# * by-name: params MUST be an Object, with member names that match the
|
||||
# Server expected parameter names. The absence of expected names MAY
|
||||
# result in an error being generated. The names MUST match exactly,
|
||||
# including case, to the method's expected parameters.
|
||||
params: [Array, Hash],
|
||||
# An identifier established by the Client that MUST contain a String,
|
||||
# Number, or NULL value if included. If it is not included it is assumed
|
||||
# to be a notification. The value SHOULD normally not be Null [1] and
|
||||
# Numbers SHOULD NOT contain fractional parts [2]
|
||||
id: [Integer, String, NilClass]
|
||||
}
|
||||
|
||||
$stderr.puts("Msf::RPC::JSON::Dispatcher.validate_rpc_request(): request.is_a?(Hash)=#{request.is_a?(Hash)}, request=#{request}")
|
||||
# validate request is an object
|
||||
return false unless request.is_a?(Hash)
|
||||
|
||||
# validate request contains required members
|
||||
required_members.each { |member| return false unless request.key?(member) }
|
||||
# required_members.each do |member|
|
||||
# $stderr.puts("Msf::RPC::JSON::Dispatcher.validate_rpc_request(): member=#{member}, request.key?(member)=#{request.key?(member)}")
|
||||
# return false unless request.key?(member)
|
||||
# end
|
||||
|
||||
$stderr.puts("Msf::RPC::JSON::Dispatcher.validate_rpc_request(): request[:jsonrpc] != JSON_RPC_VERSION=#{request[:jsonrpc] != JSON_RPC_VERSION}")
|
||||
return false if request[:jsonrpc] != JSON_RPC_VERSION
|
||||
|
||||
# validate request members are correct types
|
||||
request.each do |member, value|
|
||||
return false if member_types.key?(member) &&
|
||||
!member_types[member].one? { |type| value.is_a?(type) }
|
||||
# if member_types.key?(member) && !member_types[member].one? { |type| value.is_a?(type) }
|
||||
# return false
|
||||
# else
|
||||
# return false
|
||||
# end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Parse the JSON document source into a Hash or Array with symbols for the names (keys).
|
||||
# @return [Hash or Array] source
|
||||
def parse_json_request(source)
|
||||
begin
|
||||
JSON.parse(source, symbolize_names: true)
|
||||
rescue
|
||||
raise ParseError.new
|
||||
end
|
||||
end
|
||||
|
||||
# Serialize data as JSON string.
|
||||
# @return [String] data serialized JSON string if data not nil; otherwise, nil.
|
||||
def self.to_json(data)
|
||||
return nil if data.nil?
|
||||
|
||||
json = data.to_json
|
||||
return json.to_s
|
||||
end
|
||||
|
||||
def self.create_success_response(result, request = nil)
|
||||
response = {
|
||||
# A String specifying the version of the JSON-RPC protocol.
|
||||
jsonrpc: JSON_RPC_VERSION,
|
||||
|
||||
# This member is REQUIRED on success.
|
||||
# This member MUST NOT exist if there was an error invoking the method.
|
||||
# The value of this member is determined by the method invoked on the Server.
|
||||
result: result
|
||||
}
|
||||
|
||||
self.add_response_id_member(response, request)
|
||||
$stderr.puts("Msf::RPC::JSON::Dispatcher.success_response(): response=#{response}")
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
def self.create_error_response(error, request = nil)
|
||||
response = {
|
||||
# A String specifying the version of the JSON-RPC protocol.
|
||||
jsonrpc: JSON_RPC_VERSION,
|
||||
|
||||
# This member is REQUIRED on error.
|
||||
# This member MUST NOT exist if there was no error triggered during invocation.
|
||||
# The value for this member MUST be an Object as defined in section 5.1.
|
||||
error: error.to_h
|
||||
}
|
||||
|
||||
self.add_response_id_member(response, request)
|
||||
$stderr.puts("Msf::RPC::JSON::Dispatcher.error_response(): response=#{response}")
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
# Adds response id based on request id.
|
||||
def self.add_response_id_member(response, request)
|
||||
if !request.nil? && request.key?(:id)
|
||||
response[:id] = request[:id]
|
||||
else
|
||||
response[:id] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
require 'msf/core/rpc'
|
||||
|
||||
module Msf::RPC::JSON
|
||||
module DispatcherHelper
|
||||
def get_dispatcher(dispatchers, version, framework)
|
||||
$stderr.puts("Msf::RPC::JSON::DispatcherHelper.get_dispatcher(): dispatchers=#{dispatchers}, version=#{version}, framework=#{framework}")
|
||||
version_sym = version.to_sym
|
||||
unless dispatchers.key?(version_sym)
|
||||
$stderr.puts("Msf::RPC::JSON::DispatcherHelper.get_dispatcher(): creating dispatcher for RPC version #{version}...")
|
||||
dispatchers[version_sym] = create_dispatcher(version_sym, framework)
|
||||
end
|
||||
|
||||
dispatchers[version_sym]
|
||||
end
|
||||
|
||||
def create_dispatcher(version, framework)
|
||||
$stderr.puts("Msf::RPC::JSON::DispatcherHelper.create_dispatcher(): version=#{version}, framework=#{framework}")
|
||||
$stderr.puts("Msf::RPC::JSON::DispatcherHelper.create_dispatcher(): creating RpcCommand...")
|
||||
command = RpcCommandFactory.create(version, framework)
|
||||
$stderr.puts("Msf::RPC::JSON::DispatcherHelper.create_dispatcher(): command=#{command}")
|
||||
$stderr.puts("Msf::RPC::JSON::DispatcherHelper.create_dispatcher(): creating Dispatcher...")
|
||||
dispatcher = Dispatcher.new(framework)
|
||||
dispatcher.set_command(command)
|
||||
|
||||
dispatcher
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,136 @@
|
||||
module Msf::RPC::JSON
|
||||
|
||||
# JSON-RPC 2.0 Error Codes
|
||||
## Specification errors:
|
||||
PARSE_ERROR = -32700
|
||||
INVALID_REQUEST = -32600
|
||||
METHOD_NOT_FOUND = -32601
|
||||
INVALID_PARAMS = -32602
|
||||
INTERNAL_ERROR = -32603
|
||||
## Implementation-defined server-errors:
|
||||
SERVER_ERROR_MAX = -32000
|
||||
SERVER_ERROR_MIN = -32099
|
||||
APPLICATION_SERVER_ERROR = -32000
|
||||
|
||||
# JSON-RPC 2.0 Error Messages
|
||||
ERROR_MESSAGES = {
|
||||
# Specification errors:
|
||||
PARSE_ERROR => 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.',
|
||||
INVALID_REQUEST => 'The JSON sent is not a valid Request object.',
|
||||
METHOD_NOT_FOUND => 'The method %<name>s does not exist.',
|
||||
INVALID_PARAMS => 'Invalid method parameter(s).',
|
||||
INTERNAL_ERROR => 'Internal JSON-RPC error',
|
||||
# Implementation-defined server-errors:
|
||||
APPLICATION_SERVER_ERROR => 'Application server error: %<msg>s',
|
||||
}
|
||||
|
||||
# Base class for all Msf::RPC::JSON exceptions.
|
||||
class RpcError < StandardError
|
||||
# Code Message Meaning
|
||||
# -32700 Parse error Invalid JSON was received by the server. An error
|
||||
# occurred on the server while parsing the JSON text.
|
||||
# -32600 Invalid Request The JSON sent is not a valid Request object.
|
||||
# -32601 Method not found The method does not exist / is not available.
|
||||
# -32602 Invalid params Invalid method parameter(s).
|
||||
# -32603 Internal error Internal JSON-RPC error.
|
||||
# -32000 to -32099 Server error Reserved for implementation-defined server-errors.
|
||||
|
||||
attr_reader :code
|
||||
attr_reader :message
|
||||
attr_reader :data
|
||||
|
||||
# Instantiate an RpcError object.
|
||||
#
|
||||
# @param code [Integer] A Number that indicates the error type that occurred.
|
||||
# @param message [String] A String providing a short description of the error.
|
||||
# The message SHOULD be limited to a concise single sentence.
|
||||
# @param data [Object] A Primitive or Structured value that contains additional
|
||||
# information about the error. This may be omitted. The value of this member is
|
||||
# defined by the Server (e.g. detailed error information, nested errors etc.).
|
||||
# The default value is nil.
|
||||
def initialize(code, message, data: nil)
|
||||
super(message)
|
||||
@code = code
|
||||
@message = message
|
||||
@data = data
|
||||
end
|
||||
|
||||
def to_h
|
||||
hash = {
|
||||
code: @code,
|
||||
message: @message
|
||||
}
|
||||
|
||||
# process data member
|
||||
unless @data.nil?
|
||||
if @data.is_a?(String) || @data.kind_of?(Numeric) || @data.is_a?(Array) || @data.is_a?(Hash)
|
||||
hash[:data] = @data
|
||||
elsif @data.respond_to?(:to_h)
|
||||
hash[:data] = @data.to_h
|
||||
else
|
||||
hash[:data] = @data.to_s
|
||||
end
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
class ParseError < RpcError
|
||||
def initialize(data: nil)
|
||||
super(PARSE_ERROR, ERROR_MESSAGES[PARSE_ERROR], data: data)
|
||||
end
|
||||
end
|
||||
|
||||
class InvalidRequest < RpcError
|
||||
def initialize(data: nil)
|
||||
super(INVALID_REQUEST, ERROR_MESSAGES[INVALID_REQUEST], data: data)
|
||||
end
|
||||
end
|
||||
|
||||
class MethodNotFound < RpcError
|
||||
def initialize(method, data: nil)
|
||||
super(METHOD_NOT_FOUND, ERROR_MESSAGES[METHOD_NOT_FOUND] % {name: method}, data: data)
|
||||
end
|
||||
end
|
||||
|
||||
class InvalidParams < RpcError
|
||||
def initialize(data: nil)
|
||||
super(INVALID_PARAMS, ERROR_MESSAGES[INVALID_PARAMS], data: data)
|
||||
end
|
||||
end
|
||||
|
||||
class InternalError < RpcError
|
||||
def initialize(e, data: nil)
|
||||
super(INTERNAL_ERROR, "#{ERROR_MESSAGES[INTERNAL_ERROR]}: #{e}", data: data)
|
||||
end
|
||||
end
|
||||
|
||||
# Class is reserved for implementation-defined server-error exceptions.
|
||||
class ServerError < RpcError
|
||||
|
||||
# Instantiate a ServerError object.
|
||||
#
|
||||
# @param code [Integer] A Number that indicates the error type that occurred.
|
||||
# The code must be between -32000 and -32099.
|
||||
# @param message [String] A String providing a short description of the error.
|
||||
# The message SHOULD be limited to a concise single sentence.
|
||||
# @param data [Object] A Primitive or Structured value that contains additional
|
||||
# information about the error. This may be omitted. The value of this member is
|
||||
# defined by the Server (e.g. detailed error information, nested errors etc.).
|
||||
# The default value is nil.
|
||||
# @raise [ArgumentError] Module not found (either the wrong type or name).
|
||||
def initialize(code, message, data: nil)
|
||||
if code < SERVER_ERROR_MIN || code > SERVER_ERROR_MAX
|
||||
raise ArgumentError.new("invalid code #{code}, must be between #{SERVER_ERROR_MAX} and #{SERVER_ERROR_MIN}")
|
||||
end
|
||||
super(code, message, data: data)
|
||||
end
|
||||
end
|
||||
|
||||
class ApplicationServerError < ServerError
|
||||
def initialize(message, data: nil)
|
||||
super(APPLICATION_SERVER_ERROR, ERROR_MESSAGES[APPLICATION_SERVER_ERROR] % {msg: message}, data: data)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,80 @@
|
||||
module Msf::RPC::JSON
|
||||
class RpcCommand
|
||||
attr_reader :framework
|
||||
attr_accessor :execute_timeout
|
||||
|
||||
def initialize(framework, execute_timeout: 7200)
|
||||
@framework = framework
|
||||
@execute_timeout = execute_timeout
|
||||
@methods = {}
|
||||
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.initialize(): @framework=#{@framework}, @framework.object_id=#{@framework.object_id}")
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.initialize(): @execute_timeout=#{@execute_timeout}")
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.initialize(): @methods=#{@methods}")
|
||||
end
|
||||
|
||||
# Add a method to the RPC Command
|
||||
def register_method(method, name: nil)
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.register_method(): method.class=#{method.class}, method.inspect=#{method.inspect}, name=#{name}")
|
||||
if name.nil?
|
||||
if method.is_a?(Method)
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.register_method(): method.name=#{method.name}")
|
||||
name = method.name.to_s
|
||||
else
|
||||
name = method.to_s
|
||||
end
|
||||
end
|
||||
@methods[name] = method
|
||||
end
|
||||
|
||||
# Call method on the receiver object previously registered.
|
||||
def execute(method, params)
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.execute(): method=#{method}, params=#{params}")
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.execute(): @methods.key?(method)=#{@methods.key?(method)}")
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.execute(): @methods[method]=#{@methods[method]}") if @methods.key?(method)
|
||||
|
||||
unless @methods.key?(method)
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.execute(): raising MethodNotFound...")
|
||||
raise MethodNotFound.new(method)
|
||||
end
|
||||
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.execute(): calling method_name=#{method}...")
|
||||
::Timeout.timeout(@execute_timeout) do
|
||||
params = prepare_params(params)
|
||||
if params.nil?
|
||||
return @methods[method].call()
|
||||
elsif params.is_a?(Array)
|
||||
return @methods[method].call(*params)
|
||||
else
|
||||
return @methods[method].call(params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Prepare params for use by RPC methods by converting all hashes to use strings for their names (keys).
|
||||
def prepare_params(params)
|
||||
clean_params = params
|
||||
if params.is_a?(Array)
|
||||
clean_params = params.map do |p|
|
||||
if p.is_a?(Hash)
|
||||
stringify_names(p)
|
||||
else
|
||||
p
|
||||
end
|
||||
end
|
||||
elsif params.is_a?(Hash)
|
||||
clean_params = stringify_names(params)
|
||||
end
|
||||
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommand.prepare_params(): params=#{params}, clean_params=#{clean_params}")
|
||||
clean_params
|
||||
end
|
||||
|
||||
# Returns a new hash with strings for the names (keys).
|
||||
def stringify_names(hash)
|
||||
JSON.parse(JSON.dump(hash), symbolize_names: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
require 'msf/core/rpc'
|
||||
require 'msf/core/rpc/json/v1_0/rpc_command'
|
||||
require 'msf/core/rpc/json/v2_0/rpc_test'
|
||||
|
||||
module Msf::RPC::JSON
|
||||
class RpcCommandFactory
|
||||
def self.create(version, framework)
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommandFactory.create: version=#{version}, framework=#{framework}") # TODO: remove
|
||||
case version
|
||||
when :v1, :v1_0, :v10
|
||||
return Msf::RPC::JSON::V1_0::RpcCommand.new(framework)
|
||||
when :v2, :v2_0
|
||||
return RpcCommandFactory.create_rpc_command_v2_0(framework)
|
||||
else
|
||||
raise ArgumentError.new("invalid RPC version #{version}")
|
||||
end
|
||||
end
|
||||
|
||||
def self.create_rpc_command_v2_0(framework)
|
||||
# TODO: does belong in some sort of loader class for an RPC version?
|
||||
# instantiate receiver
|
||||
rpc_test = Msf::RPC::JSON::V2_0::RpcTest.new()
|
||||
$stderr.puts("Msf::RPC::JSON::RpcCommandFactory.create_rpc_command_v2_0: rpc_test=#{rpc_test}")
|
||||
|
||||
command = Msf::RPC::JSON::RpcCommand.new(framework)
|
||||
|
||||
# Add class methods
|
||||
command.register_method(Msf::RPC::JSON::V2_0::RpcTest.method(:add))
|
||||
command.register_method(Msf::RPC::JSON::V2_0::RpcTest.method(:add), name: 'add_alias')
|
||||
# Add instance methods
|
||||
command.register_method(rpc_test.method(:get_instance_rand_num))
|
||||
command.register_method(rpc_test.method(:add_instance_rand_num))
|
||||
|
||||
command
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,117 @@
|
||||
require 'base64'
|
||||
require 'msf/core/rpc'
|
||||
|
||||
module Msf::RPC::JSON
|
||||
module V1_0
|
||||
class RpcCommand < ::Msf::RPC::JSON::RpcCommand
|
||||
METHOD_GROUP_SEPARATOR = '.'
|
||||
|
||||
MODULE_EXECUTE_KEY = 'module.execute'
|
||||
PAYLOAD_MODULE_TYPE_KEY = 'payload'
|
||||
PAYLOAD_KEY = 'payload'
|
||||
|
||||
def initialize(framework, execute_timeout: 7200)
|
||||
super(framework, execute_timeout: execute_timeout)
|
||||
|
||||
# The legacy Msf::RPC::Service will not be started, however, it will be used to proxy
|
||||
# requests to existing handlers. This frees the command from having to act as the
|
||||
# service to RPC_Base subclasses and expose accessors for tokens and users.
|
||||
@legacy_rpc_service = ::Msf::RPC::Service.new(@framework, {
|
||||
execute_timeout: @execute_timeout
|
||||
})
|
||||
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.initialize(): @framework=#{@framework}, @framework.object_id=#{@framework.object_id}")
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.initialize(): @legacy_rpc_service=#{@legacy_rpc_service}, @legacy_rpc_service.handlers=#{@legacy_rpc_service.handlers}")
|
||||
end
|
||||
|
||||
def register_method(method, name: nil)
|
||||
raise "#{self.class.name}##{__method__} is not implemented"
|
||||
end
|
||||
|
||||
# Call method on the receiver object previously registered.
|
||||
def execute(method, params)
|
||||
result = execute_internal(method, params)
|
||||
|
||||
# post process result
|
||||
result = post_process_result(result, method, params)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Call method on the receiver object previously registered.
|
||||
def execute_internal(method, params)
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.execute(): method=#{method}, params=#{params}")
|
||||
|
||||
# parse method string
|
||||
group, base_method = parse_method_group(method)
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.execute(): group=#{group}, base_method=#{base_method}") # TODO: remove
|
||||
|
||||
method_name = "rpc_#{base_method}"
|
||||
method_name_noauth = "rpc_#{base_method}_noauth"
|
||||
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.execute(): method_name=#{base_method}, method_name_noauth=#{method_name_noauth}, @legacy_rpc_service.handlers[group]=#{@legacy_rpc_service.handlers[group]}")
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.execute(): @legacy_rpc_service.handlers[group].nil?=#{@legacy_rpc_service.handlers[group].nil?}")
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.execute(): @legacy_rpc_service.handlers[group].respond_to?(method_name)=#{@legacy_rpc_service.handlers[group].respond_to?(method_name)}")
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.execute(): @legacy_rpc_service.handlers[group].respond_to?(method_name_noauth)=#{@legacy_rpc_service.handlers[group].respond_to?(method_name_noauth)}")
|
||||
|
||||
|
||||
handler = (find_handler(@legacy_rpc_service.handlers, group, method_name) || find_handler(@legacy_rpc_service.handlers, group, method_name_noauth))
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.execute(): handler=#{handler}")
|
||||
if handler.nil?
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.execute(): raising MethodNotFound...")
|
||||
raise MethodNotFound.new(method)
|
||||
end
|
||||
|
||||
if handler.respond_to?(method_name_noauth)
|
||||
method_name = method_name_noauth
|
||||
end
|
||||
|
||||
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.execute(): calling method_name=#{method_name}...")
|
||||
::Timeout.timeout(@execute_timeout) do
|
||||
params = prepare_params(params)
|
||||
if params.nil?
|
||||
return handler.send(method_name)
|
||||
elsif params.is_a?(Array)
|
||||
return handler.send(method_name, *params)
|
||||
else
|
||||
return handler.send(method_name, params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parse_method_group(method)
|
||||
idx = method.rindex(METHOD_GROUP_SEPARATOR)
|
||||
if idx.nil?
|
||||
group = nil
|
||||
base_method = method
|
||||
else
|
||||
group = method[0..idx - 1]
|
||||
base_method = method[idx + 1..-1]
|
||||
end
|
||||
return group, base_method
|
||||
end
|
||||
|
||||
def find_handler(handlers, group, method_name)
|
||||
handler = nil
|
||||
if !handlers[group].nil? && handlers[group].respond_to?(method_name)
|
||||
handler = handlers[group]
|
||||
end
|
||||
|
||||
handler
|
||||
end
|
||||
|
||||
def post_process_result(result, method, params)
|
||||
if method == MODULE_EXECUTE_KEY && params.size >= 2 &&
|
||||
params[0] == PAYLOAD_MODULE_TYPE_KEY && result.key?(PAYLOAD_KEY)
|
||||
result[PAYLOAD_KEY] = Base64.strict_encode64(result[PAYLOAD_KEY])
|
||||
$stderr.puts("Msf::RPC::JSON::V1_0::RpcCommand.post_process_result(): converted result key '#{PAYLOAD_KEY}': new value=#{result[PAYLOAD_KEY]}")
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
module Msf::RPC::JSON::V2_0
|
||||
class RpcTest
|
||||
|
||||
def initialize
|
||||
r = Random.new
|
||||
@rand_num = r.rand(0..100)
|
||||
$stderr.puts("Msf::RPC::JSON::V2_0::RpcTest.initialize(): @rand_num=#{@rand_num}")
|
||||
end
|
||||
|
||||
def self.add(x, y)
|
||||
result = x + y
|
||||
$stderr.puts("Msf::RPC::JSON::V2_0::RpcTest.add(): x=#{x}, y=#{y}, result=#{result}")
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def get_instance_rand_num
|
||||
$stderr.puts("Msf::RPC::JSON::V2_0::RpcTest instance.get_instance_rand_num(): @rand_num=#{@rand_num}")
|
||||
|
||||
@rand_num
|
||||
end
|
||||
|
||||
def add_instance_rand_num(x)
|
||||
@rand_num = @rand_num + x
|
||||
$stderr.puts("Msf::RPC::JSON::V2_0::RpcTest instance.add_instance_rand_num(): x=#{x}, @rand_num=#{@rand_num}")
|
||||
|
||||
@rand_num
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,69 @@
|
||||
require 'sinatra/base'
|
||||
require 'swagger/blocks'
|
||||
require 'sysrandom/securerandom'
|
||||
require 'warden'
|
||||
require 'msf/core/rpc'
|
||||
require 'msf/core/db_manager/http/authentication'
|
||||
require 'msf/core/db_manager/http/servlet_helper'
|
||||
require 'msf/core/db_manager/http/servlet/auth_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/user_servlet'
|
||||
require 'msf/core/web_services/servlet/json_rpc_servlet'
|
||||
|
||||
class JsonRpcApp < Sinatra::Base
|
||||
helpers ServletHelper
|
||||
helpers Msf::RPC::JSON::DispatcherHelper
|
||||
|
||||
# Servlet registration
|
||||
register AuthServlet
|
||||
register UserServlet
|
||||
register JsonRpcServlet
|
||||
|
||||
set :framework, Msf::Simple::Framework.create({})
|
||||
set :dispatchers, {}
|
||||
|
||||
configure do
|
||||
set :sessions, {key: 'msf-ws.session', expire_after: 300}
|
||||
set :session_secret, ENV.fetch('MSF_WS_SESSION_SECRET') { SecureRandom.hex(16) }
|
||||
end
|
||||
|
||||
before do
|
||||
# store DBManager in request environment so that it is available to Warden
|
||||
request.env['msf.db_manager'] = get_db
|
||||
# store flag indicating whether authentication is initialized in the request environment
|
||||
@@auth_initialized ||= get_db.users({}).count > 0
|
||||
request.env['msf.auth_initialized'] = @@auth_initialized
|
||||
end
|
||||
|
||||
use Warden::Manager do |config|
|
||||
# failed authentication is handled by this application
|
||||
config.failure_app = self
|
||||
# don't intercept 401 responses since the app will provide custom failure messages
|
||||
config.intercept_401 = false
|
||||
config.default_scope = :api
|
||||
|
||||
config.scope_defaults :user,
|
||||
# whether to persist the result in the session or not
|
||||
store: true,
|
||||
# list of strategies to use
|
||||
strategies: [:password],
|
||||
# action (route) of the failure application
|
||||
action: "#{AuthServlet.api_unauthenticated_path}/user"
|
||||
|
||||
config.scope_defaults :api,
|
||||
# whether to persist the result in the session or not
|
||||
store: false,
|
||||
# list of strategies to use
|
||||
strategies: [:api_token],
|
||||
# action (route) of the failure application
|
||||
action: AuthServlet.api_unauthenticated_path
|
||||
|
||||
config.scope_defaults :admin_api,
|
||||
# whether to persist the result in the session or not
|
||||
store: false,
|
||||
# list of strategies to use
|
||||
strategies: [:admin_api_token],
|
||||
# action (route) of the failure application
|
||||
action: AuthServlet.api_unauthenticated_path
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
require 'msf/core/rpc'
|
||||
|
||||
module JsonRpcServlet
|
||||
|
||||
def self.api_path
|
||||
'/api/:version/json-rpc'
|
||||
end
|
||||
|
||||
def self.registered(app)
|
||||
app.post JsonRpcServlet.api_path, &post_rpc
|
||||
end
|
||||
|
||||
#######
|
||||
private
|
||||
#######
|
||||
|
||||
def self.post_rpc
|
||||
lambda {
|
||||
warden.authenticate!
|
||||
begin
|
||||
body = request.body.read
|
||||
$stderr.puts("JsonRpcServlet: body=#{body}")
|
||||
tmp_params = sanitize_params(params)
|
||||
$stderr.puts("JsonRpcServlet: tmp_params=#{tmp_params}")
|
||||
|
||||
data = get_dispatcher(settings.dispatchers, tmp_params[:version], settings.framework).process(body)
|
||||
set_raw_response(data)
|
||||
rescue => e
|
||||
print_error("There was an error executing the RPC: #{e.message}.", e)
|
||||
error = Msf::RPC::JSON::Dispatcher.create_error_response(Msf::RPC::JSON::InternalError.new(e))
|
||||
data = Msf::RPC::JSON::Dispatcher.to_json(error)
|
||||
set_raw_response(data, code: 500)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
# msf-json-rpc.ru
|
||||
# Start using thin:
|
||||
# thin --rackup msf-json-rpc.ru --address localhost --port 8081 --environment development --tag msf-json-rpc start
|
||||
#
|
||||
|
||||
require 'pathname'
|
||||
@framework_path = '.'
|
||||
root = Pathname.new(@framework_path).expand_path
|
||||
@framework_lib_path = root.join('lib')
|
||||
$LOAD_PATH << @framework_lib_path unless $LOAD_PATH.include?(@framework_lib_path)
|
||||
|
||||
require 'msfenv'
|
||||
|
||||
if ENV['MSF_LOCAL_LIB']
|
||||
$LOAD_PATH << ENV['MSF_LOCAL_LIB'] unless $LOAD_PATH.include?(ENV['MSF_LOCAL_LIB'])
|
||||
end
|
||||
|
||||
# Note: setup Rails environment before calling require
|
||||
require 'msf/core/web_services/json_rpc_app'
|
||||
|
||||
run JsonRpcApp
|
||||
Reference in New Issue
Block a user