Initial JSON-RPC servlet and support architecture

This commit is contained in:
Matthew Kienow
2018-09-20 17:39:33 -04:00
parent 0fd98d5eaa
commit 8bd9faad22
11 changed files with 792 additions and 11 deletions
+32 -11
View File
@@ -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
+206
View File
@@ -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
+136
View File
@@ -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
+80
View File
@@ -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
+117
View File
@@ -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
+30
View File
@@ -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
+69
View File
@@ -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
+21
View File
@@ -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