lib/ui/web/server.rb in arachni-0.2.2.2 vs lib/ui/web/server.rb in arachni-0.2.3
- old
+ new
@@ -29,28 +29,29 @@
require Arachni::Options.instance.dir['lib'] + 'ui/cli/output'
require Arachni::Options.instance.dir['lib'] + 'framework'
require Arachni::Options.instance.dir['lib'] + 'rpc/xml/client/dispatcher'
require Arachni::Options.instance.dir['lib'] + 'rpc/xml/client/instance'
require Arachni::Options.instance.dir['lib'] + 'ui/web/report_manager'
+require Arachni::Options.instance.dir['lib'] + 'ui/web/dispatcher_manager'
require Arachni::Options.instance.dir['lib'] + 'ui/web/log'
require Arachni::Options.instance.dir['lib'] + 'ui/web/output_stream'
#
#
# Provides a web user interface for the Arachni Framework using Sinatra.<br/>
#
# @author: Tasos "Zapotek" Laskos
# <tasos.laskos@gmail.com>
# <zapotek@segfault.gr>
-# @version: 0.1.1-pre
+# @version: 0.1.3
#
# @see Arachni::RPC::XML::Client::Instance
# @see Arachni::RPC::XML::Client::Dispatcher
#
module Web
- VERSION = '0.1.1-pre'
+ VERSION = '0.1.3'
class Server < Sinatra::Base
configure do
use Rack::Flash
@@ -65,10 +66,25 @@
end
helpers do
+ def title
+ main = 'Arachni - Web Application Security Scanner Framework'
+
+ sub = env['PATH_INFO'].split( '/' ).map {
+ |part|
+ normalize_section_name( part )
+ }.reject { |part| part.empty? }.join( ' → ' )
+
+ return sub.empty? ? main : sub + ' :: ' + main
+ end
+
+ def normalize_section_name( name )
+ name.gsub( '_', ' ' ).capitalize
+ end
+
def report_count
settings.reports.all.size
end
def plugin_has_required_file_option?( options )
@@ -129,11 +145,11 @@
@@plugins
end
def proc_mem( rss )
# we assume a page size of 4096
- (rss.to_i * 4096 / 1024 / 1024).to_s + 'MB'
+ rss.to_i * 4096 / 1024 / 1024
end
def secs_to_hms( secs )
secs = secs.to_i
return [secs/3600, secs/60 % 60, secs % 60].map {
@@ -159,10 +175,11 @@
#
HELPER_OWNER = "WebUI helper"
set :log, Log.new( Arachni::Options.instance, settings )
set :reports, ReportManager.new( Arachni::Options.instance, settings )
+ set :dispatchers, DispatcherManager.new( Arachni::Options.instance, settings )
enable :sessions
configure do
# shit's on!
@@ -176,13 +193,13 @@
# erb :error, { :layout => true }, :error => 'Remote server has been shut down.'
# end
end
def show( page, layout = true )
- if page == :dispatcher
+ if page == :dispatchers
ensure_dispatcher
- erb :dispatcher, { :layout => true }, :stats => dispatcher_stats
+ erb :dispatchers, { :layout => true }, :stats => dispatcher_stats
else
erb page.to_sym, { :layout => layout }
end
end
@@ -204,54 +221,89 @@
#
# Provides an easy way to connect to an instance by port
#
# @param [Integer] port
#
- def connect_to_instance( port )
+ def connect_to_instance( url, token = nil )
prep_session
+ url = 'https://' + url if URI( url ).scheme != 'https'
+
+ @@connections ||= {}
+
begin
- return Arachni::RPC::XML::Client::Instance.new( options, port_to_url( port ) )
- rescue Exception
- raise "Instance on port #{port} has shutdown."
+ if @@connections[url] && @@connections[url].alive?
+ return @@connections[url]
+ end
+ rescue
end
+
+ begin
+
+ #
+ # Sync up the session authentication tokens with the ones in the
+ # class variables.
+ #
+ # This will allow users to still connect to instances even if they
+ # shutdown the WebUI or removed their cookies.
+ #
+
+ @@tokens ||= {}
+ session['tokens'] ||= {}
+ @@tokens[url] = token if token
+
+ session['tokens'].merge!( @@tokens )
+ @@tokens.merge!( session['tokens'] )
+ session['tokens'].merge!( @@tokens )
+
+ return @@connections[url] =
+ Arachni::RPC::XML::Client::Instance.new( options, url, session['tokens'][url] )
+ rescue Exception => e
+ raise "Instance at #{url} has shutdown."
+ end
end
#
# Converts a port to a URL instance.
#
# @param [Integer] port
#
- def port_to_url( port )
- uri = URI( session[:dispatcher_url] )
+ def port_to_url( port, dispatcher_url, no_scheme = nil )
+ uri = URI( dispatcher_url )
uri.port = port.to_i
- uri.to_s
+ uri = uri.to_s
+
+ uri = sanitize_url( uri ) if no_scheme
+ return uri
end
- #
- # Provides easy access to the dispatcher and handles failure
- #
- def dispatcher
- begin
- @dispatcher ||= Arachni::RPC::XML::Client::Dispatcher.new( options, session[:dispatcher_url] )
- rescue Exception => e
- redirect '/dispatcher_error'
- end
+ def dispatchers
+ settings.dispatchers
end
#
# Provides statistics about running jobs etc using the dispatcher
#
def dispatcher_stats
- stats = dispatcher.stats
- stats['running_jobs'].each {
- |job|
+
+ stats = {}
+ dispatchers.all.each {
+ |dispatcher|
+
begin
- job['paused'] = connect_to_instance( job['port'] ).framework.paused?
+ stats[dispatcher['url']] = dispatchers.connect( dispatcher['url'] ).stats
+ stats[dispatcher['url']]['running_jobs'].each {
+ |job|
+ begin
+ job['paused'] = connect_to_instance( port_to_url( job['port'], dispatcher['url'] ) ).framework.paused?
+ rescue
+ end
+ }
rescue
end
}
+
return stats
end
def options
Arachni::Options.instance
@@ -365,16 +417,21 @@
def helper_instance
begin
@@arachni ||= nil
if !@@arachni
- instance = dispatcher.dispatch( HELPER_OWNER )
- @@arachni = connect_to_instance( instance['port'] )
+
+ d_url = dispatchers.all.first['url']
+ instance = dispatchers.connect( d_url ).dispatch( HELPER_OWNER )
+ instance_url = port_to_url( instance['port'], d_url )
+
+ @@arachni = connect_to_instance( instance_url, instance['token'] )
end
+
return @@arachni
rescue
- redirect '/dispatcher/error'
+ redirect '/dispatchers/edit'
end
end
def component_cache_filled?
begin
@@ -401,12 +458,10 @@
#
# Makes sure that all systems are go and populates the session with default values
#
def prep_session
- session[:dispatcher_url] ||= 'http://localhost:7331'
-
ensure_dispatcher
session['opts'] ||= {}
session['opts']['settings'] ||= {
'audit_links' => true,
@@ -440,15 +495,11 @@
# an appropriate error page.
#
# @return [Bool] true if alive, redirect if not
#
def ensure_dispatcher
- begin
- dispatcher.alive?
- rescue Exception => e
- redirect '/dispatcher/error'
- end
+ redirect '/dispatchers/edit' if dispatchers.all.empty?
end
#
# Saves the report, shuts down the instance and returns the content as HTML
# to be sent back to the user's browser.
@@ -473,81 +524,101 @@
end
#
# Kills all running instances
#
- def shutdown_all
- settings.log.dispatcher_global_shutdown( env )
- dispatcher.stats['running_jobs'].each {
- |job|
- begin
- save_and_shutdown( connect_to_instance( job['port'] ) )
- rescue
+ def shutdown_all( url )
+ settings.log.dispatcher_global_shutdown( env, url )
+
+ dispatcher_stats.each_pair {
+ |d_url, stats|
+
+ next if sanitize_url( d_url.dup ) != url
+
+ stats['running_jobs'].each {
+ |job|
+
+ instance_url = port_to_url( job['port'], d_url )
begin
- connect_to_instance( job['port'] ).service.shutdown!
+ save_and_shutdown( connect_to_instance( instance_url ) )
rescue
- settings.log.instance_fucker_wont_die( env, port_to_url( job['port'] ) )
- next
+ begin
+ connect_to_instance( instance_url ).service.shutdown!
+ rescue
+ settings.log.instance_fucker_wont_die( env, instance_url )
+ next
+ end
end
- end
- settings.log.instance_shutdown( env, port_to_url( job['port'] ) )
+ settings.log.instance_shutdown( env, instance_url )
+ }
}
+
end
#
# Kills all idle instances
#
# @return [Integer] the number of reaped instances
#
def shutdown_zombies
i = 0
- dispatcher.stats['running_jobs'].each {
- |job|
- begin
- arachni = connect_to_instance( job['port'] )
+ dispatcher_stats.each_pair {
+ |url, stats|
+ stats['running_jobs'].each {
+ |job|
+
begin
- if !arachni.framework.busy? && !job['owner'] != HELPER_OWNER
- save_and_shutdown( arachni )
- settings.log.webui_zombie_cleanup( env, port_to_url( job['port'] ) )
- i+=1
+ instance_url = port_to_url( job['port'], url )
+ arachni = connect_to_instance( instance_url )
+
+ begin
+ if !arachni.framework.busy? && !job['owner'] != HELPER_OWNER
+ save_and_shutdown( arachni )
+ settings.log.webui_zombie_cleanup( env, instance_url )
+ i+=1
+ end
+ rescue
end
- rescue
+ rescue
end
-
- rescue
- end
+ }
}
return i
end
+ def sanitize_url( url )
+ url.gsub!( 'http://', '' )
+ escape( url.gsub( 'https://', '' ) )
+ end
+
get "/" do
prep_session
ensure_welcomed
show :home
end
get "/welcome" do
erb :welcome, { :layout => true }
end
- get "/dispatcher" do
- show :dispatcher
+ get "/dispatchers" do
+ show :dispatchers
end
#
# sets the dispatcher URL
#
- post "/dispatcher" do
+ post "/dispatchers" do
if !params['url'] || params['url'].empty?
flash[:err] = "URL cannot be empty."
- show :dispatcher_error
+ show :dispatchers_edit
else
session[:dispatcher_url] = params['url']
settings.log.dispatcher_selected( env, params['url'] )
begin
@@ -555,26 +626,48 @@
settings.log.dispatcher_verified( env, params['url'] )
redirect '/'
rescue
settings.log.dispatcher_error( env, params['url'] )
flash[:err] = "Couldn't find a dispatcher at \"#{escape( params['url'] )}\"."
- show :dispatcher_error
+ show :dispatchers_edit
end
end
end
+ get '/dispatchers/edit' do
+ show :dispatchers_edit
+ end
+
+ post '/dispatchers/add' do
+
+ begin
+ dispatchers.alive?( params[:url] )
+ dispatchers.new( params )
+ rescue
+ flash[:err] = "Couldn't find a dispatcher at \"#{escape( params['url'] )}\"."
+ end
+
+ show :dispatchers_edit
+ end
+
+ post '/dispatchers/:url/delete' do
+ dispatchers.delete( params[:url] )
+ show :dispatchers_edit
+ end
+
+
#
# shuts down all instances
#
- post "/dispatcher/shutdown" do
- shutdown_all
- redirect '/dispatcher'
+ post "/dispatchers/:url/shutdown" do
+ shutdown_all( params[:url] )
+ redirect '/dispatchers'
end
- get '/dispatcher/error' do
- show :dispatcher_error
+ get '/dispatchers/error' do
+ show :dispatchers_edit
end
#
# starts a scan
#
@@ -593,15 +686,17 @@
elsif !valid
flash[:err] = "Invalid URL."
show :home
else
- instance = dispatcher.dispatch( params['url'] )
- settings.log.instance_dispatched( env, port_to_url( instance['port'] ) )
+ instance = dispatchers.connect( params['dispatcher'] ).dispatch( params['url'] )
+ instance_url = port_to_url( instance['port'], params['dispatcher'] )
+
+ settings.log.instance_dispatched( env, instance_url )
settings.log.instance_owner_assigned( env, params['url'] )
- arachni = connect_to_instance( instance['port'] )
+ arachni = connect_to_instance( instance_url, instance['token'] )
session['opts']['settings']['url'] = params['url']
unescape_hash( session['opts'] )
@@ -616,11 +711,11 @@
arachni.plugins.load( YAML::load( session['opts']['plugins'] ) )
arachni.framework.run
settings.log.scan_started( env, params['url'] )
- redirect '/instance/' + instance['port'].to_s
+ redirect '/instance/' + instance_url.to_s.gsub( 'https://', '' )
end
end
get "/modules" do
@@ -675,117 +770,111 @@
flash.now[:ok] = "Settings updated."
show :settings, true
end
- get "/instance/:port" do
+ get "/instance/:url" do
begin
- arachni = connect_to_instance( params[:port] )
+ arachni = connect_to_instance( params[:url] )
erb :instance, { :layout => true }, :paused => arachni.framework.paused?, :shutdown => false
- rescue
- flash.now[:notice] = "Instance on port #{params[:port]} has been shutdown."
+ rescue Exception => e
+ flash.now[:notice] = "Instance at #{params[:url]} has been shutdown."
erb :instance, { :layout => true }, :shutdown => true, :stats => dispatcher_stats
end
end
- get "/instance/:port/output.json" do
+ get "/instance/:url/output.json" do
content_type :json
begin
- arachni = connect_to_instance( params[:port] )
+ arachni = connect_to_instance( params[:url] )
if arachni.framework.busy?
{ 'data' => OutputStream.new( arachni, 38 ).data }.to_json
else
- settings.log.instance_shutdown( env, port_to_url( params[:port] ) )
+ settings.log.instance_shutdown( env, params[:url] )
save_and_shutdown( arachni )
-
- # {
- # 'report' => '"data:text/html;base64, ' +
- # Base64.encode64( save_shutdown_and_show( arachni ) ) + '"'
- # }.to_json
-
{ 'status' => 'finished', 'data' => "The server has been shut down." }.to_json
end
rescue
{ 'status' => 'finished', 'data' => "The server has been shut down." }.to_json
end
end
- get "/instance/:port/output_results.json" do
+ get "/instance/:url/output_results.json" do
content_type :json
begin
- arachni = connect_to_instance( params[:port] )
+ arachni = connect_to_instance( params[:url] )
if !arachni.framework.paused? && arachni.framework.busy?
out = erb( :output_results, { :layout => false }, :issues => YAML.load( arachni.framework.auditstore ).issues)
{ 'data' => out }.to_json
end
rescue
{ 'data' => "The server has been shut down." }.to_json
end
end
- get "/instance/:port/stats.json" do
+ get "/instance/:url/stats.json" do
content_type :json
begin
- arachni = connect_to_instance( params[:port] )
+ arachni = connect_to_instance( params[:url] )
stats = arachni.framework.stats
stats['current_page'] = escape( stats['current_page'] )
{ 'refresh' => true, 'stats' => stats }.to_json
rescue
{ 'refresh' => false }.to_json
end
end
- post "/*/:port/pause" do
- arachni = connect_to_instance( params[:port] )
+ post "/*/:url/pause" do
+ arachni = connect_to_instance( params[:url] )
begin
arachni.framework.pause!
- settings.log.instance_paused( env, port_to_url( params[:port] ) )
+ settings.log.instance_paused( env, params[:url] )
- flash.now[:notice] = "Instance on port #{params[:port]} will pause as soon as the current page is audited."
+ flash.now[:notice] = "Instance at #{params[:url]} will pause as soon as the current page is audited."
erb params[:splat][0].to_sym, { :layout => true }, :paused => arachni.framework.paused?, :shutdown => false, :stats => dispatcher_stats
rescue
- flash.now[:notice] = "Instance on port #{params[:port]} has been shutdown."
+ flash.now[:notice] = "Instance at #{params[:url]} has been shutdown."
erb params[:splat][0].to_sym, { :layout => true }, :shutdown => true, :stats => dispatcher_stats
end
end
- post "/*/:port/resume" do
- arachni = connect_to_instance( params[:port] )
+ post "/*/:url/resume" do
+ arachni = connect_to_instance( params[:url] )
begin
arachni.framework.resume!
- settings.log.instance_resumed( env, port_to_url( params[:port] ) )
+ settings.log.instance_resumed( env, params[:url] )
- flash.now[:notice] = "Instance on port #{params[:port]} resumes."
+ flash.now[:notice] = "Instance at #{params[:url]} resumes."
erb params[:splat][0].to_sym, { :layout => true }, :paused => arachni.framework.paused?, :shutdown => false, :stats => dispatcher_stats
rescue
- flash.now[:notice] = "Instance on port #{params[:port]} has been shutdown."
+ flash.now[:notice] = "Instance at #{params[:url]} has been shutdown."
erb params[:splat][0].to_sym, { :layout => true }, :shutdown => true, :stats => dispatcher_stats
end
end
- post "/*/:port/shutdown" do
- arachni = connect_to_instance( params[:port] )
+ post "/*/:url/shutdown" do
+ arachni = connect_to_instance( params[:url] )
begin
arachni.framework.busy?
- settings.log.instance_shutdown( env, port_to_url( params[:port] ) )
+ settings.log.instance_shutdown( env, params[:url] )
begin
save_shutdown_and_show( arachni )
rescue
- flash.now[:notice] = "Instance on port #{params[:port]} has been shutdown."
+ flash.now[:notice] = "Instance at #{params[:url]} has been shutdown."
show params[:splat][0].to_sym
ensure
arachni.service.shutdown!
end
rescue
- flash.now[:notice] = "Instance on port #{params[:port]} has already been shutdown."
+ flash.now[:notice] = "Instance at #{params[:url]} has already been shutdown."
erb params[:splat][0].to_sym, { :layout => true }, :shutdown => true, :stats => dispatcher_stats
end
end
get "/reports" do