Land #10861, Add framework for JSON-RPC and future Sinatra apps

GSoC/Meterpreter_Web_Console
Brent Cook 2018-11-26 14:12:08 -06:00
commit 181fc292c2
No known key found for this signature in database
GPG Key ID: 1FFAA0B24B708F96
4 changed files with 171 additions and 79 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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