mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-08-28 23:26:18 +02:00
Add MSF REST web service authentication support
This commit is contained in:
parent
a91ad8c09c
commit
c0717d9306
@ -65,9 +65,11 @@ PATH
|
||||
sinatra
|
||||
sqlite3
|
||||
sshkey
|
||||
sysrandom
|
||||
thin
|
||||
tzinfo
|
||||
tzinfo-data
|
||||
warden
|
||||
windows_error
|
||||
xdr
|
||||
xmlrpc
|
||||
@ -325,6 +327,7 @@ GEM
|
||||
sqlite3 (1.3.13)
|
||||
sshkey (1.9.0)
|
||||
swagger-blocks (2.0.2)
|
||||
sysrandom (1.0.5)
|
||||
thin (1.7.2)
|
||||
daemons (~> 1.0, >= 1.0.9)
|
||||
eventmachine (~> 1.0, >= 1.0.4)
|
||||
@ -338,6 +341,8 @@ GEM
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2018.5)
|
||||
tzinfo (>= 1.0.0)
|
||||
warden (1.2.7)
|
||||
rack (>= 1.0)
|
||||
windows_error (0.1.2)
|
||||
xdr (2.0.0)
|
||||
activemodel (>= 4.2.7)
|
||||
|
@ -53,6 +53,7 @@ class Msf::DBManager
|
||||
autoload :Session, 'msf/core/db_manager/session'
|
||||
autoload :SessionEvent, 'msf/core/db_manager/session_event'
|
||||
autoload :Task, 'msf/core/db_manager/task'
|
||||
autoload :User, 'msf/core/db_manager/user'
|
||||
autoload :Vuln, 'msf/core/db_manager/vuln'
|
||||
autoload :VulnAttempt, 'msf/core/db_manager/vuln_attempt'
|
||||
autoload :VulnDetail, 'msf/core/db_manager/vuln_detail'
|
||||
@ -89,6 +90,7 @@ class Msf::DBManager
|
||||
include Msf::DBManager::Session
|
||||
include Msf::DBManager::SessionEvent
|
||||
include Msf::DBManager::Task
|
||||
include Msf::DBManager::User
|
||||
include Msf::DBManager::Vuln
|
||||
include Msf::DBManager::VulnAttempt
|
||||
include Msf::DBManager::VulnDetail
|
||||
|
5
lib/msf/core/db_manager/http/authentication.rb
Normal file
5
lib/msf/core/db_manager/http/authentication.rb
Normal file
@ -0,0 +1,5 @@
|
||||
module Authentication
|
||||
autoload :Strategies, 'msf/core/db_manager/http/authentication/strategies'
|
||||
|
||||
include Strategies
|
||||
end
|
@ -0,0 +1,9 @@
|
||||
module Authentication
|
||||
module Strategies
|
||||
autoload :ApiToken, 'msf/core/db_manager/http/authentication/strategies/api_token'
|
||||
autoload :UserPassword, 'msf/core/db_manager/http/authentication/strategies/user_password'
|
||||
|
||||
include ApiToken
|
||||
include UserPassword
|
||||
end
|
||||
end
|
@ -0,0 +1,38 @@
|
||||
module Authentication
|
||||
module Strategies
|
||||
module ApiToken
|
||||
AUTHORIZATION = 'HTTP_AUTHORIZATION'
|
||||
AUTHORIZATION_SCHEME = 'Bearer'
|
||||
TOKEN_QUERY_PARAM = 'token'
|
||||
|
||||
Warden::Strategies.add(:api_token) do
|
||||
|
||||
# Check if request contains valid data and should be authenticated.
|
||||
# @return [Boolean] true if strategy should be run for the request; otherwise, false.
|
||||
def valid?
|
||||
authorization = request.env[AUTHORIZATION]
|
||||
(authorization.is_a?(String) && authorization.start_with?(AUTHORIZATION_SCHEME)) || !params[TOKEN_QUERY_PARAM].nil?
|
||||
end
|
||||
|
||||
# Authenticate the request.
|
||||
def authenticate!
|
||||
db_manager = env['DBManager']
|
||||
authorization = request.env[AUTHORIZATION]
|
||||
if authorization.is_a?(String) && authorization.start_with?(AUTHORIZATION_SCHEME)
|
||||
token = authorization.sub(/^#{AUTHORIZATION_SCHEME}\s+/, '')
|
||||
else
|
||||
token = params[TOKEN_QUERY_PARAM]
|
||||
end
|
||||
|
||||
user = db_manager.users(persistence_token: token).first
|
||||
|
||||
if user.nil?
|
||||
throw(:warden, message: "Invalid API token.")
|
||||
else
|
||||
success!(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,39 @@
|
||||
module Authentication
|
||||
module Strategies
|
||||
module UserPassword
|
||||
|
||||
Warden::Manager.serialize_into_session{ |user| user.id }
|
||||
Warden::Manager.serialize_from_session{ |id|
|
||||
db_manager = env['DBManager']
|
||||
db_manager.users(id: id).first
|
||||
}
|
||||
|
||||
Warden::Manager.before_failure do |env,opts|
|
||||
# change request method to get control to our handler since authentication failure can happen on any request
|
||||
env['REQUEST_METHOD'] = 'POST'
|
||||
end
|
||||
|
||||
Warden::Strategies.add(:password) do
|
||||
|
||||
# Check if request contains valid data and should be authenticated.
|
||||
# @return [Boolean] true if strategy should be run for the request; otherwise, false.
|
||||
def valid?
|
||||
params['username'] && params['password']
|
||||
end
|
||||
|
||||
# Authenticate the request.
|
||||
def authenticate!
|
||||
db_manager = env['DBManager']
|
||||
user = db_manager.users(username: params['username']).first
|
||||
|
||||
if user.nil? || !db_manager.authenticate_user(id: user.id, password: params['password'])
|
||||
fail("Invalid username or password.")
|
||||
else
|
||||
success!(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,5 +1,5 @@
|
||||
require 'rack'
|
||||
require 'msf/core/db_manager/http/sinatra_app'
|
||||
require 'msf/core/db_manager/http/metasploit_api_app'
|
||||
require 'metasploit/framework/parsed_options/remote_db'
|
||||
require 'rex/ui/text/output/stdio'
|
||||
|
||||
@ -26,14 +26,14 @@ class HttpDBManagerService
|
||||
|
||||
def start_http_server(opts)
|
||||
|
||||
Rack::Handler::Thin.run(SinatraApp, opts) do |server|
|
||||
Rack::Handler::Thin.run(MetasploitApiApp, opts) do |server|
|
||||
|
||||
if opts[:ssl] && opts[:ssl] = true
|
||||
print_good "SSL Enabled"
|
||||
print_good('SSL Enabled')
|
||||
server.ssl = true
|
||||
server.ssl_options = opts[:ssl_opts]
|
||||
else
|
||||
print_warning 'SSL Disabled'
|
||||
print_warning('SSL Disabled')
|
||||
end
|
||||
server.threaded = true
|
||||
end
|
||||
|
83
lib/msf/core/db_manager/http/metasploit_api_app.rb
Normal file
83
lib/msf/core/db_manager/http/metasploit_api_app.rb
Normal file
@ -0,0 +1,83 @@
|
||||
require 'sinatra/base'
|
||||
require 'swagger/blocks'
|
||||
require 'sysrandom/securerandom'
|
||||
require 'warden'
|
||||
require 'msf/core/db_manager/http/authentication'
|
||||
require 'msf/core/db_manager/http/servlet_helper'
|
||||
require 'msf/core/db_manager/http/servlet/api_docs_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/auth_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/host_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/note_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/vuln_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/event_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/web_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/msf_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/workspace_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/service_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/session_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/exploit_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/loot_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/session_event_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/credential_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/nmap_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/db_export_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/vuln_attempt_servlet'
|
||||
|
||||
class MetasploitApiApp < Sinatra::Base
|
||||
helpers ServletHelper
|
||||
|
||||
# Servlet registration
|
||||
register ApiDocsServlet
|
||||
register AuthServlet
|
||||
register HostServlet
|
||||
register VulnServlet
|
||||
register EventServlet
|
||||
register WebServlet
|
||||
register MsfServlet
|
||||
register NoteServlet
|
||||
register WorkspaceServlet
|
||||
register ServiceServlet
|
||||
register SessionServlet
|
||||
register ExploitServlet
|
||||
register LootServlet
|
||||
register SessionEventServlet
|
||||
register CredentialServlet
|
||||
register NmapServlet
|
||||
register DbExportServlet
|
||||
register VulnAttemptServlet
|
||||
|
||||
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['DBManager'] = get_db
|
||||
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
|
||||
end
|
||||
|
||||
end
|
BIN
lib/msf/core/db_manager/http/public/favicon.ico
Normal file
BIN
lib/msf/core/db_manager/http/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
105
lib/msf/core/db_manager/http/servlet/auth_servlet.rb
Normal file
105
lib/msf/core/db_manager/http/servlet/auth_servlet.rb
Normal file
@ -0,0 +1,105 @@
|
||||
module AuthServlet
|
||||
|
||||
def self.api_path
|
||||
'/api/v1/auth'
|
||||
end
|
||||
|
||||
def self.api_account_path
|
||||
"#{AuthServlet.api_path}/account"
|
||||
end
|
||||
|
||||
def self.api_login_path
|
||||
"#{AuthServlet.api_path}/login"
|
||||
end
|
||||
|
||||
def self.api_logout_path
|
||||
"#{AuthServlet.api_path}/logout"
|
||||
end
|
||||
|
||||
def self.api_generate_token_path
|
||||
"#{AuthServlet.api_path}/generate-token"
|
||||
end
|
||||
|
||||
def self.api_unauthenticated_path
|
||||
"#{AuthServlet.api_path}/unauthenticated"
|
||||
end
|
||||
|
||||
def self.registered(app)
|
||||
app.get AuthServlet.api_account_path, &get_api_account
|
||||
|
||||
app.get AuthServlet.api_login_path, &get_login
|
||||
app.post AuthServlet.api_login_path, &post_login
|
||||
|
||||
app.get AuthServlet.api_logout_path, &get_logout
|
||||
app.get AuthServlet.api_generate_token_path, &get_generate_token
|
||||
app.post "#{AuthServlet.api_unauthenticated_path}/?:scope?", &post_unauthenticated
|
||||
end
|
||||
|
||||
#######
|
||||
private
|
||||
#######
|
||||
|
||||
# Get account page
|
||||
def self.get_api_account
|
||||
lambda {
|
||||
erb :'auth/account'
|
||||
}
|
||||
end
|
||||
|
||||
# Get login page
|
||||
def self.get_login
|
||||
lambda {
|
||||
erb :'auth/login'
|
||||
}
|
||||
end
|
||||
|
||||
# Process login request
|
||||
def self.post_login
|
||||
lambda {
|
||||
warden.authenticate!(scope: :user)
|
||||
|
||||
if session[:return_to].nil? || session[:return_to] == AuthServlet.api_login_path
|
||||
redirect AuthServlet.api_account_path
|
||||
else
|
||||
redirect session[:return_to]
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Process user log out
|
||||
def self.get_logout
|
||||
lambda {
|
||||
warden.logout
|
||||
redirect AuthServlet.api_account_path
|
||||
}
|
||||
end
|
||||
|
||||
# Generate a new API token for the current user
|
||||
def self.get_generate_token
|
||||
lambda {
|
||||
# change action to drop the scope param since this is used
|
||||
# by XMLHttpRequest (XHR) and we don't want a redirect
|
||||
warden.authenticate!(scope: :user, action: AuthServlet.api_unauthenticated_path)
|
||||
token = get_db.create_new_user_token(id: warden.user(:user).id, token_length: 40)
|
||||
set_json_data_response(response: {message: "Generated new API token.", token: token})
|
||||
}
|
||||
end
|
||||
|
||||
# Handle the unauthenticated action for multiple scopes
|
||||
def self.post_unauthenticated
|
||||
lambda {
|
||||
if !params['scope'].nil? && params['scope'] == 'user'
|
||||
session[:return_to] = warden_options[:attempted_path] if session[:return_to].nil?
|
||||
redirect AuthServlet.api_login_path
|
||||
end
|
||||
|
||||
msg = warden_options[:message]
|
||||
error = {
|
||||
code: 401,
|
||||
message: "#{!msg.nil? ? "#{msg} " : nil}Authenticate to access this resource."
|
||||
}
|
||||
set_json_error_response(response: error, code: error[:code])
|
||||
}
|
||||
end
|
||||
|
||||
end
|
@ -16,9 +16,19 @@ module ServletHelper
|
||||
[200, '']
|
||||
end
|
||||
|
||||
def set_json_response(data, includes = nil)
|
||||
def set_json_response(data, includes = nil, code = 200)
|
||||
headers = {'Content-Type' => 'application/json'}
|
||||
[200, headers, to_json(data, includes)]
|
||||
[code, headers, to_json(data, includes)]
|
||||
end
|
||||
|
||||
def set_json_data_response(response:, includes: nil, code: 200)
|
||||
data_response = {"data": response}
|
||||
set_json_response(data_response, includes = includes, code = code)
|
||||
end
|
||||
|
||||
def set_json_error_response(response:, includes: nil, code:)
|
||||
error_response = {"error": response}
|
||||
set_json_response(error_response, includes = includes, code = code)
|
||||
end
|
||||
|
||||
def set_html_response(data)
|
||||
@ -70,6 +80,19 @@ module ServletHelper
|
||||
params.symbolize_keys.except(:captures, :splat)
|
||||
end
|
||||
|
||||
# Get Warden::Proxy object from the Rack environment.
|
||||
# @return [Warden::Proxy] The Warden::Proxy object from the Rack environment.
|
||||
def warden
|
||||
env['warden']
|
||||
end
|
||||
|
||||
# Get Warden options hash from the Rack environment.
|
||||
# @return [Hash] The Warden options hash from the Rack environment.
|
||||
def warden_options
|
||||
env['warden.options']
|
||||
end
|
||||
|
||||
|
||||
#######
|
||||
private
|
||||
#######
|
||||
|
@ -1,43 +0,0 @@
|
||||
require 'sinatra/base'
|
||||
require 'swagger/blocks'
|
||||
require 'msf/core/db_manager/http/servlet_helper'
|
||||
require 'msf/core/db_manager/http/servlet/api_docs_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/host_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/note_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/vuln_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/event_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/web_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/msf_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/workspace_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/service_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/session_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/exploit_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/loot_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/session_event_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/credential_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/nmap_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/db_export_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/vuln_attempt_servlet'
|
||||
|
||||
class SinatraApp < Sinatra::Base
|
||||
helpers ServletHelper
|
||||
|
||||
# Servlet registration
|
||||
register ApiDocsServlet
|
||||
register HostServlet
|
||||
register VulnServlet
|
||||
register EventServlet
|
||||
register WebServlet
|
||||
register MsfServlet
|
||||
register NoteServlet
|
||||
register WorkspaceServlet
|
||||
register ServiceServlet
|
||||
register SessionServlet
|
||||
register ExploitServlet
|
||||
register LootServlet
|
||||
register SessionEventServlet
|
||||
register CredentialServlet
|
||||
register NmapServlet
|
||||
register DbExportServlet
|
||||
register VulnAttemptServlet
|
||||
end
|
138
lib/msf/core/db_manager/http/views/auth/account.erb
Normal file
138
lib/msf/core/db_manager/http/views/auth/account.erb
Normal file
@ -0,0 +1,138 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Account - Metasploit API</title>
|
||||
<!-- TODO: use external style sheet -->
|
||||
<style>
|
||||
body {
|
||||
margin:0;
|
||||
}
|
||||
|
||||
ul {
|
||||
background-color: rgb(47,47,47);
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
li a, .dropdown-btn {
|
||||
display: inline-block;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 14px 16px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
li a:hover, .dropdown-menu:hover .dropdown-btn {
|
||||
background-color: rgb(73,73,73);
|
||||
}
|
||||
|
||||
li.dropdown-menu {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: rgb(73,73,73);
|
||||
min-width: 160px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-content a {
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {
|
||||
background-color: rgb(96,96,96);
|
||||
}
|
||||
|
||||
.dropdown-menu:hover .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.api-token {
|
||||
float:left;
|
||||
}
|
||||
|
||||
#api-token-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#api-token {
|
||||
margin-left: 7px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
function getNewApiToken() {
|
||||
loadDoc("GET", "<%= AuthServlet.api_generate_token_path %>", function(xhr) {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
document.getElementById("api-token").innerHTML = response.data.token;
|
||||
}, errorHandler);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function errorHandler(xhr) {
|
||||
if (xhr.status == 401) {
|
||||
window.location.reload(true);
|
||||
}
|
||||
}
|
||||
|
||||
function loadDoc(method, url, callback, errorCallback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState == 4) {
|
||||
if (this.status == 200) {
|
||||
callback(this);
|
||||
} else if (this.status >= 400) {
|
||||
errorCallback(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open(method, url, true);
|
||||
xhr.send();
|
||||
}
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
<% if warden.authenticated?(:user) %>
|
||||
<li class="dropdown-menu">
|
||||
<a href="javascript:void(0)" class="dropdown-btn"><%= warden.user(:user).username %></a>
|
||||
<div class="dropdown-content">
|
||||
<a href="#" onclick="getNewApiToken();">Generate New API Token</a>
|
||||
<a href="<%= AuthServlet.api_logout_path %>">Log Out</a>
|
||||
</div>
|
||||
</li>
|
||||
<% else %>
|
||||
<li><a href="<%= AuthServlet.api_login_path %>">Log In</a></li>
|
||||
<% end %>
|
||||
<li><a href="<%= ApiDocsServlet.html_path %>">API Documentation</a></li>
|
||||
</ul>
|
||||
|
||||
<div style="padding:20px;">
|
||||
<h1>Metasploit API Account</h1>
|
||||
|
||||
<% if warden.authenticated?(:user) %>
|
||||
<div id="api-token-label" class="api-token">Current API Token:</div>
|
||||
<div id="api-token" class="api-token">
|
||||
<%= !warden.user(:user).nil? && !warden.user(:user).persistence_token.nil? ? warden.user(:user).persistence_token : 'none' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
54
lib/msf/core/db_manager/http/views/auth/login.erb
Normal file
54
lib/msf/core/db_manager/http/views/auth/login.erb
Normal file
@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Log In - Metasploit API</title>
|
||||
<!-- TODO: use external style sheet -->
|
||||
<style>
|
||||
.credential-container {
|
||||
border: 1px solid rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.55);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 390px;
|
||||
min-width: 300px;
|
||||
min-height: 250px;
|
||||
padding: 34px;
|
||||
}
|
||||
|
||||
input[type=text], input[type=password] {
|
||||
border-color: rgba(0, 0, 0, 0.6);
|
||||
border-width: 1px;
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
button {
|
||||
border-color: rgba(0, 0, 0, 0.6);
|
||||
border-width: 1px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div style="padding:20px;">
|
||||
<form action="<%= AuthServlet.api_login_path %>" method="post">
|
||||
<div class="credential-container">
|
||||
<h2>Log In - Metasploit API</h2>
|
||||
<label for="username"><b>Username</b></label>
|
||||
<input type="text" placeholder="Enter Username" name="username" required>
|
||||
|
||||
<label for="password"><b>Password</b></label>
|
||||
<input type="password" placeholder="Enter Password" name="password" required>
|
||||
|
||||
<button type="submit">Log In</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
98
lib/msf/core/db_manager/user.rb
Normal file
98
lib/msf/core/db_manager/user.rb
Normal file
@ -0,0 +1,98 @@
|
||||
require 'sysrandom/securerandom'
|
||||
|
||||
module Msf::DBManager::User
|
||||
|
||||
# Returns a list of all users in the database
|
||||
def users(opts)
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
|
||||
search_term = opts.delete(:search_term)
|
||||
if search_term && !search_term.empty?
|
||||
column_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Mdm::User, search_term)
|
||||
Mdm::User.where(opts).where(column_search_conditions)
|
||||
else
|
||||
Mdm::User.where(opts)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
#
|
||||
# Report a user's attributes
|
||||
#
|
||||
# The opts parameter MUST contain
|
||||
# +:XXX+:: -- the users's XXX
|
||||
#
|
||||
# The opts parameter can contain:
|
||||
# +:XXX+:: -- XXX
|
||||
#
|
||||
def report_user(opts)
|
||||
return if !active
|
||||
|
||||
# TODO: implement method
|
||||
raise 'Msf::DBManager::User#report_user is not implemented'
|
||||
end
|
||||
|
||||
def update_user(opts)
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
# process workspace string for update if included in opts
|
||||
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
|
||||
opts[:workspace] = wspace if wspace
|
||||
|
||||
id = opts.delete(:id)
|
||||
Mdm::User.update(id, opts)
|
||||
}
|
||||
end
|
||||
|
||||
# Deletes user entries based on the IDs passed in.
|
||||
#
|
||||
# @param opts[:ids] [Array] Array containing Integers corresponding to the IDs of the user entries to delete.
|
||||
# @return [Array] Array containing the Mdm::User objects that were successfully deleted.
|
||||
def delete_user(opts)
|
||||
raise ArgumentError.new("The following options are required: :ids") if opts[:ids].nil?
|
||||
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
deleted = []
|
||||
opts[:ids].each do |user_id|
|
||||
user = Mdm::User.find(user_id)
|
||||
begin
|
||||
deleted << user.destroy
|
||||
rescue # refs suck
|
||||
elog("Forcibly deleting #{user}")
|
||||
deleted << user.delete
|
||||
end
|
||||
end
|
||||
|
||||
return deleted
|
||||
}
|
||||
end
|
||||
|
||||
# Authenticates the user.
|
||||
#
|
||||
# @param opts[:ids] [Integer] ID of the user to authenticate.
|
||||
# @param opts[:password] [String] The user's password.
|
||||
# @return [Boolean] true if the user is successfully authenticated; otherwise, false.
|
||||
def authenticate_user(opts)
|
||||
raise ArgumentError.new("The following options are required: :id") if opts[:id].nil?
|
||||
raise ArgumentError.new("The following options are required: :password") if opts[:password].nil?
|
||||
|
||||
user = Mdm::User.find(opts[:id])
|
||||
# TODO: Yes, we need proper password salting and hashing here
|
||||
if !user.nil? && user.crypted_password == opts[:password]
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a new API token for the user.
|
||||
#
|
||||
# @param opts[:ids] [Integer] ID for the user.
|
||||
# @return [String] The new API token.
|
||||
def create_new_user_token(opts)
|
||||
raise ArgumentError.new("The following options are required: :id") if opts[:id].nil?
|
||||
|
||||
token_length = opts[:token_length] || 20
|
||||
# NOTE: repurposing persistence_token in the database as the API token
|
||||
Mdm::User.update(opts[:id], {persistence_token: SecureRandom.hex(token_length)}).persistence_token
|
||||
end
|
||||
end
|
@ -103,6 +103,8 @@ Gem::Specification.new do |spec|
|
||||
# Required for msfdb_ws (Metasploit data base as a webservice)
|
||||
spec.add_runtime_dependency 'thin'
|
||||
spec.add_runtime_dependency 'sinatra'
|
||||
spec.add_runtime_dependency 'sysrandom'
|
||||
spec.add_runtime_dependency 'warden'
|
||||
# TimeZone info
|
||||
spec.add_runtime_dependency 'tzinfo-data'
|
||||
# Gem for dealing with SSHKeys
|
||||
|
Loading…
Reference in New Issue
Block a user