Merge pull request #1684 from bcoles/xssrays

Add XssRays API endpoints
This commit is contained in:
bcoles
2019-02-18 02:52:40 +11:00
committed by GitHub
8 changed files with 431 additions and 108 deletions

View File

@@ -8,45 +8,42 @@
* The XssRays Tab panel for the selected zombie.
*/
//TODO: fix positioning issues, probably because we are not creating a nested (fucking) panel
ZombieTab_XssRaysTab = function(zombie) {
var commands_statusbar = new Beef_StatusBar('xssrays-bbar-zombie-'+zombie.session);
var req_pagesize = 30;
var commands_statusbar = new Beef_StatusBar('xssrays-bbar-zombie-'+zombie.session);
// RESTful API token
var token = BeefWUI.get_rest_token();
var req_pagesize = 30;
var xssrays_config_panel = new Ext.Panel({
id: 'xssrays-config-zombie-'+zombie.session,
title: 'Scan Config',
layout: 'fit'
});
var xssrays_config_panel = new Ext.Panel({
id: 'xssrays-config-zombie-'+zombie.session,
title: 'Scan Config',
layout: 'fit'
});
var xssrays_logs_store = new Ext.ux.data.PagingJsonStore({
storeId: 'xssrays-logs-store-zombie-' + zombie.session,
remoteSort: false,
autoDestroy: true,
autoLoad: false,
proxy: new Ext.data.HttpProxy({
method: 'GET',
url: '/api/xssrays/rays/' + zombie.session + '?token=' + token
}),
root: 'rays',
fields: ['id', 'vector_method', 'vector_name', 'vector_poc'],
sortInfo: {field: 'id', direction: 'DESC'},
});
var xssrays_logs_store = new Ext.ux.data.PagingJsonStore({
storeId: 'xssrays-logs-store-zombie-' + zombie.session,
url: '<%= @base_path %>/xssrays/zombie.json',
remoteSort: false,
autoDestroy: true,
autoLoad: false,
root: 'logs',
var xssrays_logs_bbar = new Ext.PagingToolbar({
pageSize: req_pagesize,
store: xssrays_logs_store,
displayInfo: true,
displayMsg: 'Displaying history {0} - {1} of {2}',
emptyMsg: 'No history to display'
});
fields: ['id', 'vector_method', 'vector_name', 'vector_poc'],
sortInfo: {field: 'id', direction: 'DESC'},
baseParams: {
nonce: Ext.get("nonce").dom.value,
zombie_session: zombie.session
}
});
var xssrays_logs_bbar = new Ext.PagingToolbar({
pageSize: req_pagesize,
store: xssrays_logs_store,
displayInfo: true,
displayMsg: 'Displaying history {0} - {1} of {2}',
emptyMsg: 'No history to display'
});
var xssrays_logs_grid = new Ext.grid.GridPanel({
var xssrays_logs_grid = new Ext.grid.GridPanel({
id: 'xssrays-logs-grid-zombie-' + zombie.session,
store: xssrays_logs_store,
bbar: xssrays_logs_bbar,
@@ -75,9 +72,9 @@ ZombieTab_XssRaysTab = function(zombie) {
datagrid.store.reload({params:{start:0,limit:req_pagesize, sort: "date", dir:"DESC"}});
}
}
});
});
var xssrays_logs_panel = new Ext.Panel({
var xssrays_logs_panel = new Ext.Panel({
id: 'xssrays-logs-panel-zombie-'+zombie.session,
title: 'Logs',
items:[xssrays_logs_grid],
@@ -88,9 +85,9 @@ ZombieTab_XssRaysTab = function(zombie) {
xssrays_logs_panel.items.items[0].store.reload();
}
}
});
});
function genScanSettingsPanel(zombie, bar, value) {
function genScanSettingsPanel(zombie, bar, value) {
var form = new Ext.FormPanel({
title: 'Scan settings',
id: 'xssrays-config-form-zombie'+zombie.session,
@@ -157,7 +154,7 @@ ZombieTab_XssRaysTab = function(zombie) {
genScanSettingsPanel(zombie, commands_statusbar);
}
}
});
});
};
Ext.extend(ZombieTab_XssRaysTab, Ext.TabPanel, {} );

View File

@@ -6,22 +6,24 @@
module BeEF
module Extension
module Xssrays
module RegisterHttpHandler
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
# We register the http handler for the requester.
# This http handler will retrieve the http responses for all requests
#
# Mounts the handlers and REST interface for processing XSS rays
#
# @param beef_server [BeEF::Core::Server] HTTP server instance
#
def self.mount_handler(beef_server)
# We register the http handler for the requester.
# This http handler will retrieve the http responses for all requests
beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new)
# REST API endpoint
beef_server.mount('/api/xssrays', BeEF::Extension::Xssrays::XssraysRest.new)
end
end
module RegisterPreHookCallback
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
# checks at every polling if there are new scans to be started
@@ -31,9 +33,7 @@ module Xssrays
xssrays.start_scan(hooked_browser, body)
end
end
end
end
end
end

View File

@@ -22,64 +22,43 @@ class Xssrays < BeEF::Extension::AdminUI::HttpController
super({
'paths' => {
'/set_scan_target' => method(:set_scan_target),
'/zombie.json' => method(:get_xssrays_logs),
'/createNewScan' => method(:create_new_scan)
}
})
end
# called by the UI when rendering xssrays_details table content in the XssRays zombie tab
def get_xssrays_logs
# validate nonce
nonce = @params['nonce'] || nil
(print_error "nonce is nil";return @body = {'success' => 'false'}.to_json) if nonce.nil?
(print_error "nonce incorrect";return @body = {'success' => 'false'}.to_json) if @session.get_nonce != nonce
# validate that the hooked browser's session has been sent
zombie_session = @params['zombie_session'] || nil
(print_error "Zombie session is nil";return @body = {'success' => 'false'}.to_json) if zombie_session.nil?
# validate that the hooked browser exists in the db
zombie = Z.first(:session => zombie_session) || nil
(print_error "Invalid hooked browser session";return @body = {'success' => 'false'}.to_json) if zombie.nil?
logs = []
BeEF::Core::Models::Xssraysdetail.all(:hooked_browser_id => zombie.id).each{|log|
logs << {
'id' => log.id,
'vector_method' => log.vector_method,
'vector_name' => log.vector_name,
'vector_poc' => log.vector_poc
}
}
@body = {'success' => 'true', 'logs' => logs}.to_json
end
# called by the UI. needed to pass the hooked browser ID/session and store a new scan in the DB.
# This is called when right-clicking the hooked browser from the tree. Default config options are read from config.yaml
def set_scan_target
# called by the UI. needed to pass the hooked browser ID/session and store a new scan in the DB.
# This is called when right-clicking the hooked browser from the tree.
# Default config options are read from config.yaml
def set_scan_target
hooked_browser = HB.first(:session => @params['hb_id'].to_s)
if(hooked_browser != nil)
xssrays_scan = XS.new(
:hooked_browser_id => hooked_browser.id,
:scan_start => Time.now,
:domain => hooked_browser.domain,
:cross_domain => CROSS_DOMAIN, #check also cross-domain URIs found by the spider
:clean_timeout => CLEAN_TIMEOUT #check also cross-domain URIs found by the spider
)
xssrays_scan.save
print_info("[XSSRAYS] Starting XSSRays [ip:#{hooked_browser.ip.to_s}], hooked domain [#{hooked_browser.domain.to_s}]")
if hooked_browser.nil?
print_error "[XSSRAYS] Invalid hooked browser ID"
return
end
end
xssrays_scan = XS.new(
:hooked_browser_id => hooked_browser.id,
:scan_start => Time.now,
:domain => hooked_browser.domain,
:cross_domain => CROSS_DOMAIN, #check also cross-domain URIs found by the spider
:clean_timeout => CLEAN_TIMEOUT #check also cross-domain URIs found by the spider
)
xssrays_scan.save
# called by the UI, in the XssRays zombie tab
# Needed if we want to start a scan overriding default scan parameters without rebooting BeEF
print_info("[XSSRAYS] Starting XSSRays [ip:#{hooked_browser.ip}], hooked domain [#{hooked_browser.domain}]")
end
# called by the UI, in the XssRays zombie tab
# Needed if we want to start a scan overriding default scan parameters without rebooting BeEF
def create_new_scan
hooked_browser = HB.first(:session => @params['zombie_session'].to_s)
if(hooked_browser != nil)
if hooked_browser.nil?
print_error "[XSSRAYS] Invalid hooked browser ID"
return
end
# set Cross-domain settings
cross_domain = @params['cross_domain']
@@ -106,8 +85,7 @@ class Xssrays < BeEF::Extension::AdminUI::HttpController
)
xssrays_scan.save
print_info("[XSSRAYS] Starting XSSRays [ip:#{hooked_browser.ip.to_s}], hooked domain [#{hooked_browser.domain.to_s}]")
end
print_info("[XSSRAYS] Starting XSSRays [ip:#{hooked_browser.ip}], hooked domain [#{hooked_browser.domain}]")
end
end

View File

@@ -16,3 +16,5 @@ require 'extensions/xssrays/models/xssraysdetail'
require 'extensions/xssrays/api/scan'
require 'extensions/xssrays/handler'
require 'extensions/xssrays/api'
require 'extensions/xssrays/rest/xssrays'

View File

@@ -56,16 +56,20 @@ module BeEF
xssrays_scan = XS.first(:id => rays_scan_id)
hooked_browser = HB.first(:session => params[:hbsess])
if (xssrays_scan != nil)
xssrays_detail = XD.new(
:hooked_browser_id => hooked_browser.id,
:vector_name => params[:n],
:vector_method => params[:m],
:vector_poc => params[:p],
:xssraysscan_id => xssrays_scan.id
)
xssrays_detail.save
if xssrays_scan.nil?
print_error "[XSSRAYS] Invalid scan"
return
end
xssrays_detail = XD.new(
:hooked_browser_id => hooked_browser.session,
:vector_name => params[:n],
:vector_method => params[:m],
:vector_poc => params[:p],
:xssraysscan_id => xssrays_scan.id
)
xssrays_detail.save
print_info("[XSSRAYS] Scan id [#{xssrays_scan.id}] received ray [ip:#{hooked_browser.ip}], hooked domain [#{hooked_browser.domain}]")
print_debug("[XSSRAYS] Ray info: \n #{request.query_string}")
end
@@ -74,10 +78,13 @@ module BeEF
def finalize_scan(rays_scan_id)
xssrays_scan = BeEF::Core::Models::Xssraysscan.first(:id => rays_scan_id)
if (xssrays_scan != nil)
xssrays_scan.update(:is_finished => true, :scan_finish => Time.now)
print_info("[XSSRAYS] Scan id [#{xssrays_scan.id}] finished at [#{xssrays_scan.scan_finish}]")
if xssrays_scan.nil?
print_error "[XSSRAYS] Invalid scan"
return
end
xssrays_scan.update(:is_finished => true, :scan_finish => Time.now)
print_info("[XSSRAYS] Scan id [#{xssrays_scan.id}] finished at [#{xssrays_scan.scan_finish}]")
end
end
end

View File

@@ -0,0 +1,184 @@
#
# Copyright (c) 2006-2019 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Xssrays
# This class handles the routing of RESTful API requests for XssRays
class XssraysRest < BeEF::Core::Router::Router
HB = BeEF::Core::Models::HookedBrowser
XS = BeEF::Core::Models::Xssraysscan
XD = BeEF::Core::Models::Xssraysdetail
# Filters out bad requests before performing any routing
before do
config = BeEF::Core::Configuration.instance
@nh = BeEF::Core::Models::NetworkHost
@ns = BeEF::Core::Models::NetworkService
# Require a valid API token from a valid IP address
halt 401 unless params[:token] == config.get('beef.api_token')
halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip)
headers 'Content-Type' => 'application/json; charset=UTF-8',
'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0'
end
# Returns the entire list of rays for all zombies
get '/rays' do
begin
rays = XD.all(:unique => true, :order => [:id.asc])
count = rays.length
result = {}
result[:count] = count
result[:rays] = []
rays.each do |ray|
result[:rays] << ray2hash(ray)
end
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving rays (#{e.message})"
halt 500
end
end
# Returns all rays given a specific hooked browser id
get '/rays/:id' do
begin
id = params[:id]
rays = XD.all(:hooked_browser_id => id, :unique => true, :order => [:id.asc])
count = rays.length
result = {}
result[:count] = count
result[:rays] = []
rays.each do |ray|
result[:rays] << ray2hash(ray)
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving rays list for hooked browser with id #{id} (#{e.message})"
halt 500
end
end
# Returns the entire list of scans for all zombies
get '/scans' do
begin
scans = XS.all(:unique => true, :order => [:id.asc])
count = scans.length
result = {}
result[:count] = count
result[:scans] = []
scans.each do |scan|
result[:scans] << scan2hash(scan)
end
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving scans (#{e.message})"
halt 500
end
end
# Returns all scans given a specific hooked browser id
get '/scans/:id' do
begin
id = params[:id]
scans = XS.all(:hooked_browser_id => id, :unique => true, :order => [:id.asc])
count = scans.length
result = {}
result[:count] = count
result[:scans] = []
scans.each do |scans|
result[:scans] << scan2hash(scan)
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving scans list for hooked browser with id #{id} (#{e.message})"
halt 500
end
end
# Starts a new scan on the specified zombie ID
=begin
post '/scan/:id' do
begin
# TODO
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving host with id #{id} (#{e.message})"
halt 500
end
end
=end
private
# Convert a ray object to JSON
def ray2hash(ray)
{
:id => ray.id,
:hooked_browser_id => ray.hooked_browser_id,
:vector_name => ray.vector_name,
:vector_method => ray.vector_method,
:vector_poc => ray.vector_poc
}
end
# Convert a scan object to JSON
def scan2hash(scan)
{
:id => scan.id,
:hooked_browser_id => scan.hooked_browser_id,
:scan_start=> scan.scan_start,
:scan_finish=> scan.scan_finish,
:domain => scan.domain,
:cross_domain => scan.cross_domain,
:clean_timeout => scan.clean_timeout,
:is_started => scan.is_started,
:is_finished => scan.is_finished
}
end
# Raised when invalid JSON input is passed to an /api/xssrays handler.
class InvalidJsonError < StandardError
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/xssrays handler'
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
end
# Raised when an invalid named parameter is passed to an /api/xssrays handler.
class InvalidParamError < StandardError
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/xssrays handler'
def initialize(message = nil)
str = "Invalid \"%s\" parameter passed to /api/xssrays handler"
message = sprintf str, message unless message.nil?
super(message)
end
end
end
end
end
end

View File

@@ -348,6 +348,57 @@ def network_services session
end
end
################################################################################
### XssRays API
################################################################################
# get all rays
def xssrays_rays_all
print_verbose "Retrieving all rays"
response = RestClient.get "#{@url}xssrays/rays", {:params => {:token => @token}}
details = JSON.parse(response.body)
print_good "Retrieved #{details['count']} rays"
details
rescue => e
print_error "Could not retrieve rays: #{e.message}"
end
# get rays by session
def xssrays_rays session
print_verbose "Retrieving rays for hooked browser [session: #{session}]"
response = RestClient.get "#{@url}xssrays/rays/#{session}", {:params => {:token => @token}}
details = JSON.parse(response.body)
print_good "Retrieved #{details['count']} rays"
details
rescue => e
print_error "Could not retrieve rays: #{e.message}"
end
# get all scans
def xssrays_scans_all
print_verbose "Retrieving all scans"
response = RestClient.get "#{@url}xssrays/scans", {:params => {:token => @token}}
details = JSON.parse(response.body)
print_good "Retrieved #{details['count']} scans"
details
rescue => e
print_error "Could not retrieve scans: #{e.message}"
end
# get scans by session
def xssrays_scans session
print_verbose "Retrieving scans for hooked browser [session: #{session}]"
response = RestClient.get "#{@url}xssrays/scans/#{session}", {:params => {:token => @token}}
details = JSON.parse(response.body)
print_good "Retrieved #{details['count']} scans"
details
rescue => e
print_error "Could not retrieve scans: #{e.message}"
end
################################################################################
### DNS API
################################################################################

104
tools/rest_api_examples/xssrays Executable file
View File

@@ -0,0 +1,104 @@
#!/usr/bin/env ruby
# xssrays - Example BeEF RESTful API script
# Refer to the wiki for info: https://github.com/beefproject/beef/wiki/BeEF-RESTful-API
##
require 'rest-client'
require 'json'
require 'optparse'
require 'pp'
require './lib/string' # colored strings
require './lib/print' # print wrappers
require './lib/beef_rest_api'
if ARGV.length == 0
puts "#{$0}:"
puts "| Example BeEF RESTful API script"
puts "| Use --help for help"
puts "|_ Use verbose mode (-v) and debug mode (-d) for more output"
exit 1
end
# API config
proto = 'http'
host = '127.0.0.1'
port = '3000'
user = 'beef'
pass = 'beef'
# Command line options
@debug = false
@verbose = false
OptionParser.new do |opts|
opts.on('-h', '--help', 'Shows this help screen') do
puts opts
exit 1
end
opts.on('--host HOST', "Set BeEF host (default: #{host})") do |h|
host = h
end
opts.on('--port PORT', "Set BeEF port (default: #{port})") do |p|
port = p
end
opts.on('--user USERNAME', "Set BeEF username (default: #{user})") do |u|
user = u
end
opts.on('--pass PASSWORD', "Set BeEF password (default: #{pass})") do |p|
pass = p
end
opts.on('--ssl', 'Use HTTPS') do
proto = 'https'
end
opts.on('-v', '--verbose', 'Enable verbose output') do
@verbose = true
end
opts.on('-d', '--debug', 'Enable debug output') do
@debug = true
end
end.parse!
@api = BeefRestAPI.new proto, host, port, user, pass
# Retrieve the RESTful API token
print_status "Authenticating to: #{proto}://#{host}:#{port}"
@api.auth
# Retrieve BeEF version
@api.version
# Retrieve all scans
scans = @api.xssrays_scans_all
print_debug scans
# Retrieve all rays
rays = @api.xssrays_rays_all
print_debug rays
# Retrieve online hooked browser list
hooks = @api.online_browsers.flatten
exit 1 if hooks.empty?
print_debug hooks
# Retrieve rays for each hooked browser
hooks.each do |hook|
next if hook['id'].nil?
print_status "Retrieving rays for browser [id: #{hook['id']}]"
rays = @api.xssrays_rays(hook['session'])
print_debug rays
rays['rays'].each do |ray|
next if ray['id'].nil?
print_verbose "#{ray['vector_name']} (#{ray['vector_method']})"
end
end
# Retrieve scans for each hooked browser
hooks.each do |hook|
next if hook['id'].nil?
print_status "Retrieving scans for browser [id: #{hook['id']}]"
scans = @api.xssrays_scans(hook['session'])
print_debug scans
scans['scans'].each do |scan|
next if scan['id'].nil?
print_verbose "Scan [#{scan['id']}] on domain #{scan['domain']}"
end
end