=begin Arachni Copyright (c) 2010-2011 Tasos "Zapotek" Laskos This is free software; you can copy and distribute and modify this program under the term of the GPL v2.0 License (See LICENSE file for details) =end module Arachni require Options.instance.dir['lib'] + 'ui/cli/output' require Options.instance.dir['lib'] + 'framework' module UI # # Arachni::UI:CLI class # # Provides a command line interface for the Arachni Framework.
# Most of the logic is in the Framework class however profiles can only
# be loaded and saved at this level. # # @author: Tasos "Zapotek" Laskos # # # @version: 0.1.7 # @see Arachni::Framework # class CLI # # Instance options # # @return [Options] # attr_reader :opts # the output interface for CLI include Arachni::UI::Output include Arachni::Module::Utilities # # Initializes the command line interface and the framework # # @param [Options] opts # def initialize( opts ) @opts = opts # if we have a load profile load it and merge it with the # user supplied options if( @opts.load_profile ) load_profile( @opts.load_profile ) end # # the stdout report is the default one for the CLI, # each UI should have it's own default # # always load the stdout report unless the user requested # to see a list of the available reports # # *do not* forget this check, otherwise the reports registry # will desync # if( @opts.reports.empty? && @opts.lsrep.empty? ) @opts.reports['stdout'] = {} end # instantiate the big-boy! @arachni = Arachni::Framework.new( @opts ) # echo the banner banner( ) # work on the user supplied arguments parse_opts( ) # trap Ctrl+C interrupts trap( 'INT' ) { handle_interrupt( ) } end # # Runs Arachni # def run( ) print_status( 'Initing...' ) begin # start the show! @arachni.run( ){ @interrupt_handler.join if @interrupt_handler } print_stats rescue Arachni::Exceptions::NoMods => e print_error( e.to_s ) print_info( "Run arachni with the '-h' parameter for help or " ) print_info( "with the '--lsmod' parameter to see all available modules." ) print_line exit 0 rescue Arachni::Exceptions => e print_error( e.to_s ) print_info( "Run arachni with the '-h' parameter for help." ) print_line exit 0 rescue Exception => e exception_jail{ raise e } exit 0 end end private def print_stats( refresh_time = false ) stats = @arachni.stats( refresh_time ) audited = stats[:auditmap_size] mapped = stats[:sitemap_size] print_line print_info( "Audit progress: #{stats[:progress]}% ( Discovered #{mapped} pages )" ) print_line print_info( "Sent #{stats[:requests]} requests." ) print_info( "Received and analyzed #{stats[:responses]} responses." ) print_info( 'In ' + stats[:time] ) avg = 'Average: ' + stats[:avg].to_s + ' requests/second.' print_info( avg ) print_line print_info( "Currently auditing #{stats[:current_page]}" ) print_info( "Burst response time total #{stats[:curr_res_time]}" ) print_info( "Burst response count total #{stats[:curr_res_cnt]} " ) print_info( "Burst average response time #{stats[:average_res_time]}" ) print_info( "Burst average #{stats[:curr_avg]} requests/second" ) print_info( "Timed-out requests #{stats[:time_out_count]}" ) print_info( "Original max concurrency #{@opts.http_req_limit}" ) print_info( "Throttled max concurrency #{stats[:max_concurrency]}" ) print_line end # # Handles Ctrl+C interrupts # # Once an interrupt has been trapped the system pauses and waits # for user input.
# The user can either continue or exit. # # The interrupt will be handled after a module has finished. # def handle_interrupt( ) return if @interrupt_handler && @interrupt_handler.alive? only_positives_opt = only_positives? @@only_positives = false @interrupt_handler = Thread.new { Thread.new { case gets[0] when 'e' @@only_positives = false unmute! @interrupt_handler.kill print_info( 'Exiting...' ) exit 0 when 'r' unmute! @arachni.reports.run( @arachni.audit_store( ) ) end @@only_positives = only_positives_opt unmute! @interrupt_handler.kill Thread.kill } while( 1 ) unmute! print_line clear_screen print_info( 'Results thus far:' ) begin print_issues( @arachni.audit_store( true ) ) print_stats( true ) rescue Exception => e exception_jail{ raise e } exit 0 end print_info( 'Continue? (hit \'enter\' to continue, \'r\' to generate reports and \'e\' to exit)' ) mute! ::IO::select( nil, nil, nil, 1 ) end unmute! } end def clear_screen puts "\e[H\e[2J" end def print_issues( audit_store ) print_line( ) print_info( audit_store.issues.size.to_s + ' issues have been detected.' ) print_line( ) audit_store.issues.each { |issue| print_ok( "#{issue.name} (In #{issue.elem} variable '#{issue.var}'" + " - Severity: #{issue.severity} - Variations: #{issue.variations.size.to_s})" ) print_info( issue.variations[0]['url'] ) print_line( ) } print_line( ) end # # It parses and processes the user options. # # Loads modules, reports, saves/loads profiles etc.
# It basically prepares the framework before calling {Arachni::Framework#run}. # def parse_opts( ) if !@opts.repload if( !@opts.mods || @opts.mods.empty? ) print_info( "No modules were specified." ) print_info( " -> Will run all mods." ) @opts.mods = ['*'] end if( !@opts.audit_links && !@opts.audit_forms && !@opts.audit_cookies && !@opts.audit_headers ) print_info( "No audit options were specified." ) print_info( " -> Will audit links, forms and cookies." ) @opts.audit_links = true @opts.audit_forms = true @opts.audit_cookies = true end end @arachni.plugins.load_defaults! @opts.to_h.each { |opt, arg| case opt.to_s when 'help' usage exit 0 when 'arachni_verbose' verbose! when 'debug' debug! when 'only_positives' only_positives! when 'lsmod' next if arg.empty? lsmod exit 0 when 'lsplug' next if arg.empty? lsplug exit 0 when 'lsrep' next if arg.empty? lsrep exit 0 when 'show_profile' print_profile( ) exit 0 when 'save_profile' exception_jail{ save_profile( arg ) } exit 0 when 'mods' begin exception_jail{ @opts.mods = @arachni.modules.load( arg ) } rescue exit 0 end when 'reports' begin exception_jail{ @arachni.reports.load( arg.keys ) } rescue exit 0 end when 'plugins' begin exception_jail{ @arachni.plugins.load( arg.keys ) } rescue exit 0 end when 'repload' exception_jail{ @arachni.reports.run( AuditStore.load( arg ), false ) } exit 0 end } # Check for missing url if( !@opts.url && !@opts.repload ) print_error( "Missing url argument." ) exit 0 end end # # Outputs all available modules and their info. # def lsmod print_line print_line print_info( 'Available modules:' ) print_line mods = @arachni.lsmod( ) i = 0 mods.each { |info| print_status( "#{info[:mod_name]}:" ) print_line( "--------------------" ) print_line( "Name:\t\t" + info[:name] ) print_line( "Description:\t" + info[:description] ) if( info[:elements] && info[:elements].size > 0 ) print_line( "Elements:\t" + info[:elements].join( ', ' ).downcase ) end print_line( "Author:\t\t" + info[:author] ) print_line( "Version:\t" + info[:version] ) if( info[:references] ) print_line( "References:" ) info[:references].keys.each { |key| print_info( key + "\t\t" + info[:references][key] ) } end print_line( "Targets:" ) info[:targets].keys.each { |key| print_info( key + "\t\t" + info[:targets][key] ) } if( info[:issue] && ( sploit = info[:issue][:metasploitable] ) ) print_line( "Metasploitable:\t" + sploit ) end print_line( "Path:\t" + info[:path] ) i+=1 # pause every 3 modules to give the user time to read # (cheers to aungkhant@yehg.net for suggesting it) if( i % 3 == 0 && i != mods.size ) print_line print_line( 'Hit to continue, any other key to exit. ' ) if gets[0] != " " print_line return end end print_line } end # # Outputs all available reports and their info. # def lsrep print_line print_line print_info( 'Available reports:' ) print_line @arachni.lsrep().each { |info| print_status( "#{info[:rep_name]}:" ) print_line( "--------------------" ) print_line( "Name:\t\t" + info[:name] ) print_line( "Description:\t" + info[:description] ) if( info[:options] && !info[:options].empty? ) print_line( "Options:\t" ) info[:options].each { |option| print_info( "\t#{option.name} - #{option.desc}" ) print_info( "\tType: #{option.type}" ) print_info( "\tDefault: #{option.default}" ) print_info( "\tRequired?: #{option.required?}" ) print_line( ) } end print_line( "Author:\t\t" + info[:author] ) print_line( "Version:\t" + info[:version] ) print_line( "Path:\t" + info[:path] ) print_line } end # # Outputs all available reports and their info. # def lsplug print_line print_line print_info( 'Available plugins:' ) print_line @arachni.lsplug().each { |info| print_status( "#{info[:plug_name]}:" ) print_line( "--------------------" ) print_line( "Name:\t\t" + info[:name] ) print_line( "Description:\t" + info[:description] ) if( info[:options] && !info[:options].empty? ) print_line( "Options:\t" ) info[:options].each { |option| print_info( "\t#{option.name} - #{option.desc}" ) print_info( "\tType: #{option.type}" ) print_info( "\tDefault: #{option.default}" ) print_info( "\tRequired?: #{option.required?}" ) print_line( ) } end print_line( "Author:\t\t" + info[:author] ) print_line( "Version:\t" + info[:version] ) print_line( "Path:\t" + info[:path] ) print_line } end # # Loads an Arachni Framework Profile file and merges it with the # user supplied options. # # @param [String] filename the file to load # def load_profile( profiles ) exception_jail{ @opts.load_profile = nil profiles.each { |filename| @opts.merge!( YAML::load( IO.read( filename ) ) ) } } end # # Saves options to an Arachni Framework Profile file.
# The file will be appended with the {PROFILE_EXT} extension. # # @param [String] filename # def save_profile( filename ) if filename = @opts.save( filename ) print_status( "Saved profile in '#{filename}'." ) print_line( ) else banner( ) print_error( 'Could not save profile.' ) exit 0 end end def print_profile( ) print_info( 'Running profile:' ) print_info( @opts.to_args ) end # # Outputs Arachni banner.
# Displays version number, revision number, author details etc. # # @see VERSION # @see REVISION # # @return [void] # def banner print_line 'Arachni - Web Application Security Scanner Framework v' + @arachni.version + ' [' + @arachni.revision + '] Author: Tasos "Zapotek" Laskos (With the support of the community and the Arachni Team.) Website: http://arachni.segfault.gr - http://github.com/Zapotek/arachni Documentation: http://github.com/Zapotek/arachni/wiki' print_line print_line end # # Outputs help/usage information.
# Displays supported options and parameters. # # @return [void] # def usage print_line < netscape HTTP cookie file, use curl to create it --user-agent= specify user agent --authed-by= who authorized the scan, include name and e-mail address (It'll make it easier on the sys-admins during log reviews.) (Will be appended to the user-agent string.) Profiles ----------------------- --save-profile= save the current run profile/options to --load-profile= load a run profile from (Can be used multiple times.) (You can complement it with more options, except for: * --mods * --redundant) --show-profile will output the running profile as CLI arguments Crawler ----------------------- -e --exclude= exclude urls matching regex (Can be used multiple times.) -i --include= include urls matching this regex only (Can be used multiple times.) --redundant=: limit crawl on redundant pages like galleries or catalogs (URLs matching will be crawled links deep.) (Can be used multiple times.) -f --follow-subdomains follow links to subdomains (default: off) --obey-robots-txt obey robots.txt file (default: off) --depth= depth limit (default: inf) (How deep Arachni should go into the site structure.) --link-count= how many links to follow (default: inf) --redirect-limit= how many redirects to follow (default: inf) --spider-first spider first, audit later Auditor ------------------------ -g --audit-links audit link variables (GET) -p --audit-forms audit form variables (usually POST, can also be GET) -c --audit-cookies audit cookies (COOKIE) --exclude-cookie= cookies not to audit (You should exclude session cookies.) (Can be used multiple times.) --audit-headers audit HTTP headers (*NOTE*: Header audits use brute force. Almost all valid HTTP request headers will be audited even if there's no indication that the web app uses them.) (*WARNING*: Enabling this option will result in increased requests, maybe by an order of magnitude.) Modules ------------------------ --lsmod= list available modules based on the provided regular expression (If no regexp is provided all modules will be listed.) (Can be used multiple times.) -m --mods= comma separated list of modules to deploy (Use '*' as a module name to deploy all modules or inside module names like so: xss_* to load all xss modules sqli_* to load all sql injection modules etc. You can exclude modules by prefixing their name with a dash: --mods=*,-backup_files,-xss The above will load all modules except for the 'backup_files' and 'xss' modules. Or mix and match: -xss_* to unload all xss modules. ) Reports ------------------------ --lsrep list available reports --repload= load audit results from an .afr file (Allows you to create new reports from finished scans.) --report=':=,=,...' : the name of the report as displayed by '--lsrep' (Default: stdout) (Can be used multiple times.) Plugins ------------------------ --lsplug list available plugins --plugin=':=,=,...' : the name of the plugin as displayed by '--lsplug' (Can be used multiple times.) Proxy -------------------------- --proxy= specify proxy --proxy-auth= specify proxy auth credentials --proxy-type= proxy type can be http, http_1_0, socks4, socks5, socks4a (Default: http) USAGE end end end end