From 42de9a01f6d04b875abed42d54fbcdda594d424c Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Sun, 17 Feb 2019 15:58:44 +0000 Subject: [PATCH] Add XssRays API endpoints --- .../ui/panel/tabs/ZombieTabXssRays.js | 73 ++++--- extensions/xssrays/api.rb | 20 +- extensions/xssrays/controllers/xssrays.rb | 74 +++---- extensions/xssrays/extension.rb | 2 + extensions/xssrays/handler.rb | 31 +-- extensions/xssrays/rest/xssrays.rb | 184 ++++++++++++++++++ tools/rest_api_examples/lib/beef_rest_api.rb | 51 +++++ tools/rest_api_examples/xssrays | 104 ++++++++++ 8 files changed, 431 insertions(+), 108 deletions(-) create mode 100644 extensions/xssrays/rest/xssrays.rb create mode 100755 tools/rest_api_examples/xssrays diff --git a/extensions/admin_ui/media/javascript/ui/panel/tabs/ZombieTabXssRays.js b/extensions/admin_ui/media/javascript/ui/panel/tabs/ZombieTabXssRays.js index 78542ee3e..a2b3e0236 100644 --- a/extensions/admin_ui/media/javascript/ui/panel/tabs/ZombieTabXssRays.js +++ b/extensions/admin_ui/media/javascript/ui/panel/tabs/ZombieTabXssRays.js @@ -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, {} ); diff --git a/extensions/xssrays/api.rb b/extensions/xssrays/api.rb index 1ec1b088d..025b1ed9f 100644 --- a/extensions/xssrays/api.rb +++ b/extensions/xssrays/api.rb @@ -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 diff --git a/extensions/xssrays/controllers/xssrays.rb b/extensions/xssrays/controllers/xssrays.rb index bc0c4702b..bf6f967e0 100644 --- a/extensions/xssrays/controllers/xssrays.rb +++ b/extensions/xssrays/controllers/xssrays.rb @@ -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 diff --git a/extensions/xssrays/extension.rb b/extensions/xssrays/extension.rb index 91d606140..888e8fa7e 100644 --- a/extensions/xssrays/extension.rb +++ b/extensions/xssrays/extension.rb @@ -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' + diff --git a/extensions/xssrays/handler.rb b/extensions/xssrays/handler.rb index c395cd588..8dd66ba90 100644 --- a/extensions/xssrays/handler.rb +++ b/extensions/xssrays/handler.rb @@ -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 diff --git a/extensions/xssrays/rest/xssrays.rb b/extensions/xssrays/rest/xssrays.rb new file mode 100644 index 000000000..f11409017 --- /dev/null +++ b/extensions/xssrays/rest/xssrays.rb @@ -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 diff --git a/tools/rest_api_examples/lib/beef_rest_api.rb b/tools/rest_api_examples/lib/beef_rest_api.rb index a59faeb1a..c43bc8278 100644 --- a/tools/rest_api_examples/lib/beef_rest_api.rb +++ b/tools/rest_api_examples/lib/beef_rest_api.rb @@ -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 ################################################################################ diff --git a/tools/rest_api_examples/xssrays b/tools/rest_api_examples/xssrays new file mode 100755 index 000000000..ca955c2d3 --- /dev/null +++ b/tools/rest_api_examples/xssrays @@ -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 +