Land #10861, Add framework for JSON-RPC and future Sinatra apps
commit
181fc292c2
|
@ -0,0 +1,85 @@
|
|||
require 'sinatra/base'
|
||||
require 'uri'
|
||||
|
||||
require 'metasploit/framework/data_service/remote/http/core'
|
||||
require 'msf/base/simple/framework'
|
||||
|
||||
module Msf::WebServices
|
||||
# Extension provides a Metasploit Framework instance to a Sinatra application.
|
||||
# The framework instance is stored with the setting name framework and is
|
||||
# also accessible via the framework helper method. If the data service URL
|
||||
# environment variable is set, then the framework instance will be configured
|
||||
# to use the data service rather than the local database.
|
||||
#
|
||||
# Environment Variables:
|
||||
# MSF_WS_DATA_SERVICE_URL - The data service URL.
|
||||
# MSF_WS_DATA_SERVICE_API_TOKEN - API token used to authenticate to the remote data service.
|
||||
# MSF_WS_DATA_SERVICE_CERT - Certificate file matching the remote data server's certificate.
|
||||
# Needed when using self-signed SSL certificates.
|
||||
# MSF_WS_DATA_SERVICE_SKIP_VERIFY - (Boolean) Skip validating authenticity of server's certificate.
|
||||
module FrameworkExtension
|
||||
FALSE_VALUES = [nil, false, 0, '0', 'f', 'false', 'off', 'no'].to_set
|
||||
|
||||
module Helpers
|
||||
# Get framework instance from settings.
|
||||
def framework
|
||||
settings.framework
|
||||
end
|
||||
end
|
||||
|
||||
def self.registered(app)
|
||||
app.helpers FrameworkExtension::Helpers
|
||||
|
||||
app.set :data_service_url, ENV.fetch('MSF_WS_DATA_SERVICE_URL', nil)
|
||||
app.set :data_service_api_token, ENV.fetch('MSF_WS_DATA_SERVICE_API_TOKEN', nil)
|
||||
app.set :data_service_cert, ENV.fetch('MSF_WS_DATA_SERVICE_CERT', nil)
|
||||
app.set :data_service_skip_verify, to_bool(ENV.fetch('MSF_WS_DATA_SERVICE_SKIP_VERIFY', false))
|
||||
|
||||
# Create simplified instance of the framework
|
||||
app.set :framework, Msf::Simple::Framework.create
|
||||
|
||||
if !app.settings.data_service_url.nil? && !app.settings.data_service_url.empty?
|
||||
framework_db_connect_http_data_service(framework: app.settings.framework,
|
||||
data_service_url: app.settings.data_service_url,
|
||||
api_token: app.settings.data_service_api_token,
|
||||
cert: app.settings.data_service_cert,
|
||||
skip_verify: app.settings.data_service_skip_verify)
|
||||
end
|
||||
end
|
||||
|
||||
def self.framework_db_connect_http_data_service(
|
||||
framework:, data_service_url:, api_token: nil, cert: nil, skip_verify: false)
|
||||
# local database is required to use Mdm objects
|
||||
unless framework.db.active
|
||||
raise "No local database connected"
|
||||
end
|
||||
|
||||
opts = {}
|
||||
https_opts = {}
|
||||
opts[:url] = data_service_url unless data_service_url.nil?
|
||||
opts[:api_token] = api_token unless api_token.nil?
|
||||
https_opts[:cert] = cert unless cert.nil?
|
||||
https_opts[:skip_verify] = skip_verify if skip_verify
|
||||
opts[:https_opts] = https_opts unless https_opts.empty?
|
||||
|
||||
begin
|
||||
uri = URI.parse(data_service_url)
|
||||
remote_data_service = Metasploit::Framework::DataService::RemoteHTTPDataService.new(uri.to_s, opts)
|
||||
framework.db.register_data_service(remote_data_service)
|
||||
framework.db.workspace = framework.db.default_workspace
|
||||
rescue => e
|
||||
raise "Failed to connect to the HTTP data service: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.to_bool(value)
|
||||
if value.is_a?(String)
|
||||
value = value.downcase
|
||||
end
|
||||
|
||||
!FALSE_VALUES.include?(value)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,64 +4,69 @@ require 'sysrandom/securerandom'
|
|||
require 'warden'
|
||||
require 'msf/core/rpc'
|
||||
require 'msf/core/web_services/authentication'
|
||||
require 'msf/core/web_services/framework_extension'
|
||||
require 'msf/core/web_services/servlet_helper'
|
||||
require 'msf/core/web_services/servlet/auth_servlet'
|
||||
require 'msf/core/web_services/servlet/json_rpc_servlet'
|
||||
|
||||
class JsonRpcApp < Sinatra::Base
|
||||
helpers ServletHelper
|
||||
helpers Msf::RPC::JSON::DispatcherHelper
|
||||
module Msf::WebServices
|
||||
class JsonRpcApp < Sinatra::Base
|
||||
helpers ServletHelper
|
||||
helpers Msf::RPC::JSON::DispatcherHelper
|
||||
|
||||
# Servlet registration
|
||||
register AuthServlet
|
||||
register JsonRpcServlet
|
||||
# Extension registration
|
||||
register FrameworkExtension
|
||||
|
||||
set :framework, Msf::Simple::Framework.create({})
|
||||
set :dispatchers, {}
|
||||
# Servlet registration
|
||||
register AuthServlet
|
||||
register JsonRpcServlet
|
||||
|
||||
configure do
|
||||
set :dispatchers, {}
|
||||
|
||||
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
|
||||
|
||||
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
|
|
@ -1,34 +1,36 @@
|
|||
require 'msf/core/rpc'
|
||||
|
||||
module JsonRpcServlet
|
||||
module Msf::WebServices
|
||||
module JsonRpcServlet
|
||||
|
||||
def self.api_path
|
||||
'/api/:version/json-rpc'
|
||||
end
|
||||
def self.api_path
|
||||
'/api/:version/json-rpc'
|
||||
end
|
||||
|
||||
def self.registered(app)
|
||||
app.post JsonRpcServlet.api_path, &post_rpc
|
||||
end
|
||||
def self.registered(app)
|
||||
app.post JsonRpcServlet.api_path, &post_rpc
|
||||
end
|
||||
|
||||
#######
|
||||
private
|
||||
#######
|
||||
#######
|
||||
private
|
||||
#######
|
||||
|
||||
# Process JSON-RPC request
|
||||
def self.post_rpc
|
||||
lambda {
|
||||
warden.authenticate!
|
||||
begin
|
||||
body = request.body.read
|
||||
tmp_params = sanitize_params(params)
|
||||
data = get_dispatcher(settings.dispatchers, tmp_params[:version].to_sym, 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
|
||||
}
|
||||
# Process JSON-RPC request
|
||||
def self.post_rpc
|
||||
lambda {
|
||||
warden.authenticate!
|
||||
begin
|
||||
body = request.body.read
|
||||
tmp_params = sanitize_params(params)
|
||||
data = get_dispatcher(settings.dispatchers, tmp_params[:version].to_sym, 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
|
||||
end
|
|
@ -18,4 +18,4 @@ end
|
|||
# Note: setup Rails environment before calling require
|
||||
require 'msf/core/web_services/json_rpc_app'
|
||||
|
||||
run JsonRpcApp
|
||||
run Msf::WebServices::JsonRpcApp
|
||||
|
|
Loading…
Reference in New Issue