diff --git a/lib/common/common_helper.rb b/lib/common/common_helper.rb index f20e7665..6aec819c 100644 --- a/lib/common/common_helper.rb +++ b/lib/common/common_helper.rb @@ -116,19 +116,21 @@ def blue(text) end def critical(text) - red(text) + $exit_code += 1 + "#{red('[!]')} #{text}" end def warning(text) - amber(text) + $exit_code += 1 + "#{amber('[!]')} #{text}" end def info(text) - green(text) + "#{green('[+]')} #{text}" end def notice(text) - blue(text) + "#{blue('[i]')} #{text}" end # our 1337 banner diff --git a/lib/common/models/vulnerability/output.rb b/lib/common/models/vulnerability/output.rb index 171c7d0b..ba9bd95c 100644 --- a/lib/common/models/vulnerability/output.rb +++ b/lib/common/models/vulnerability/output.rb @@ -6,7 +6,7 @@ class Vulnerability # output the vulnerability def output(verbose = false) puts - puts "#{critical('[!]')} Title: #{title}" + puts critical("Title: #{title}") references.each do |key, urls| methodname = "url_#{key}" urls.each do |u| @@ -15,7 +15,7 @@ class Vulnerability end end if !fixed_in.nil? - puts "#{notice('[i]')} Fixed in: #{fixed_in}" + puts notice("Fixed in: #{fixed_in}") end end end diff --git a/lib/common/models/wp_item/output.rb b/lib/common/models/wp_item/output.rb index d8c66e26..889883e1 100644 --- a/lib/common/models/wp_item/output.rb +++ b/lib/common/models/wp_item/output.rb @@ -6,19 +6,19 @@ class WpItem # @return [ Void ] def output(verbose = false) puts - puts "#{info('[+]')} Name: #{self}" #this will also output the version number if detected + puts info("Name: #{self}") #this will also output the version number if detected puts " | Location: #{url}" #puts " | WordPress: #{wordpress_url}" if wordpress_org_item? puts " | Readme: #{readme_url}" if has_readme? puts " | Changelog: #{changelog_url}" if has_changelog? - puts "#{warning('[!]')} Directory listing is enabled: #{url}" if has_directory_listing? - puts "#{warning('[!]')} An error_log file has been found: #{error_log_url}" if has_error_log? + puts warning("Directory listing is enabled: #{url}") if has_directory_listing? + puts warning("An error_log file has been found: #{error_log_url}") if has_error_log? additional_output(verbose) if respond_to?(:additional_output) if version.nil? && vulnerabilities.length > 0 puts - puts "#{warning('[+]')} We could not determine a version so all vulnerabilities are printed out" + puts warning('We could not determine a version so all vulnerabilities are printed out') end vulnerabilities.output diff --git a/lib/common/models/wp_timthumb/output.rb b/lib/common/models/wp_timthumb/output.rb index 8a87c124..b7f2c7ed 100644 --- a/lib/common/models/wp_timthumb/output.rb +++ b/lib/common/models/wp_timthumb/output.rb @@ -5,7 +5,7 @@ class WpTimthumb < WpItem def output(verbose = false) puts - puts "#{info('[+]')} #{self}" #this will also output the version number if detected + puts info("#{self}") #this will also output the version number if detected vulnerabilities.output end diff --git a/lib/common/models/wp_user/brute_forcable.rb b/lib/common/models/wp_user/brute_forcable.rb index 0a5391ba..089ebff7 100644 --- a/lib/common/models/wp_user/brute_forcable.rb +++ b/lib/common/models/wp_user/brute_forcable.rb @@ -109,13 +109,13 @@ class WpUser < WpItem elsif response.body =~ /login_error/i verbose = "\n Incorrect login and/or password." elsif response.timed_out? - progression = "#{critical('ERROR:')} Request timed out." + progression = critical('ERROR: Request timed out.') elsif response.code == 0 - progression = "#{critical('ERROR:')} No response from remote server. WAF/IPS? (#{response.return_message})" + progression = critical("ERROR: No response from remote server. WAF/IPS? (#{response.return_message})") elsif response.code.to_s =~ /^50/ - progression = "#{critical('ERROR:')} Server error, try reducing the number of threads." + progression = critical('ERROR: Server error, try reducing the number of threads.') else - progression = "#{critical('ERROR:')} We received an unknown response for #{password}..." + progression = critical("ERROR: We received an unknown response for #{password}...") verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n") end diff --git a/lib/common/models/wp_version/output.rb b/lib/common/models/wp_version/output.rb index 09438a88..a724d711 100644 --- a/lib/common/models/wp_version/output.rb +++ b/lib/common/models/wp_version/output.rb @@ -5,15 +5,15 @@ class WpVersion < WpItem def output(verbose = false) puts - puts "#{info('[+]')} WordPress version #{self.number} identified from #{self.found_from}" + puts info("WordPress version #{self.number} identified from #{self.found_from}") vulnerabilities = self.vulnerabilities unless vulnerabilities.empty? if vulnerabilities.size == 1 - puts "#{critical('[!]')} #{vulnerabilities.size} vulnerability identified from the version number" + puts critical("#{vulnerabilities.size} vulnerability identified from the version number") else - puts "#{critical('[!]')} #{vulnerabilities.size} vulnerabilities identified from the version number" + puts critical("#{vulnerabilities.size} vulnerabilities identified from the version number") end vulnerabilities.output end diff --git a/wpscan.rb b/wpscan.rb index 78fc15c5..1932e597 100755 --- a/wpscan.rb +++ b/wpscan.rb @@ -4,6 +4,8 @@ $: << '.' require File.dirname(__FILE__) + '/lib/wpscan/wpscan_helper' +$exit_code = 0 + def main # delete old logfile, check if it is a symlink first. File.delete(LOG_FILE) if File.exist?(LOG_FILE) and !File.symlink?(LOG_FILE) @@ -48,20 +50,20 @@ def main # check if db file needs upgrade and we are not running in batch mode # also no need to check if the user supplied the --update switch if update_required? && !wpscan_options.batch && !wpscan_options.update - puts "#{notice('[i]')} It seems like you have not updated the database for some time." + puts notice('It seems like you have not updated the database for some time.') print '[?] Do you want to update now? [Y]es [N]o [A]bort, default: [N]' if (input = Readline.readline) =~ /^y/i wpscan_options.update = true elsif input =~ /^a/i puts 'Scan aborted' - exit(0) + exit(1) end end if wpscan_options.update || missing_db_file? - puts "#{notice('[i]')} Updating the Database ..." + puts notice('Updating the Database ...') DbUpdater.new(DATA_DIR).update(wpscan_options.verbose) - puts "#{notice('[i]')} Update completed." + puts notice('Update completed.') # Exit program if only option --update is used exit(0) unless wpscan_options.url end @@ -88,12 +90,12 @@ def main # Remote website has a redirection? if (redirection = wp_target.redirection) if redirection =~ /\/wp-admin\/install\.php$/ - puts "#{critical('[!]')} The Website is not fully configured and currently in install mode. Call it to create a new admin user." + puts critical('The Website is not fully configured and currently in install mode. Call it to create a new admin user.') else if wpscan_options.follow_redirection puts "Following redirection #{redirection}" else - puts "#{notice('[i]')} The remote host tried to redirect to: #{redirection}" + puts notice("The remote host tried to redirect to: #{redirection}") print '[?] Do you want follow the redirection ? [Y]es [N]o [A]bort, default: [N]' end if wpscan_options.follow_redirection || !wpscan_options.batch @@ -103,7 +105,7 @@ def main else if input =~ /^a/i puts 'Scan aborted' - exit(0) + exit(1) end end end @@ -123,7 +125,7 @@ def main # Remote website is wordpress? unless wpscan_options.force unless wp_target.wordpress? - raise "#{critical('[!]')} The remote website is up, but does not seem to be running WordPress." + raise critical('The remote website is up, but does not seem to be running WordPress.') end end @@ -136,51 +138,51 @@ def main puts 'You can specify one per command line option (don\'t forget to include the wp-content directory if needed)' puts '[?] Continue? [Y]es [N]o, default: [N]' if wpscan_options.batch || Readline.readline !~ /^y/i - exit(0) + exit(1) end end # Output runtime data start_time = Time.now start_memory = get_memory_usage - puts "#{info('[+]')} URL: #{wp_target.url}" - puts "#{info('[+]')} Started: #{start_time.asctime}" + puts info("URL: #{wp_target.url}") + puts info("Started: #{start_time.asctime}") puts if wp_target.wordpress_hosted? - puts "#{critical('[!]')} We do not support scanning *.wordpress.com hosted blogs" + puts critical('We do not support scanning *.wordpress.com hosted blogs') end if wp_target.has_robots? - puts "#{info('[+]')} robots.txt available under: '#{wp_target.robots_url}'" + puts info("robots.txt available under: '#{wp_target.robots_url}'") wp_target.parse_robots_txt.each do |dir| - puts "#{info('[+]')} Interesting entry from robots.txt: #{dir}" + puts info("Interesting entry from robots.txt: #{dir}") end end if wp_target.has_readme? - puts "#{warning('[!]')} The WordPress '#{wp_target.readme_url}' file exists exposing a version number" + puts warning("The WordPress '#{wp_target.readme_url}' file exists exposing a version number") end if wp_target.has_full_path_disclosure? - puts "#{warning('[!]')} Full Path Disclosure (FPD) in: '#{wp_target.full_path_disclosure_url}'" + puts warning("Full Path Disclosure (FPD) in: '#{wp_target.full_path_disclosure_url}'") end if wp_target.has_debug_log? - puts "#{critical('[!]')} Debug log file found: #{wp_target.debug_log_url}" + puts critical("Debug log file found: #{wp_target.debug_log_url}") end wp_target.config_backup.each do |file_url| - puts "#{critical('[!]')} A wp-config.php backup file has been found in: '#{file_url}'" + puts critical("A wp-config.php backup file has been found in: '#{file_url}'") end if wp_target.search_replace_db_2_exists? - puts "#{critical('[!]')} searchreplacedb2.php has been found in: '#{wp_target.search_replace_db_2_url}'" + puts critical("searchreplacedb2.php has been found in: '#{wp_target.search_replace_db_2_url}'") end wp_target.interesting_headers.each do |header| - output = "#{info('[+]')} Interesting header: " + output = info('Interesting header: ') if header[1].class == Array header[1].each do |value| @@ -192,23 +194,23 @@ def main end if wp_target.multisite? - puts "#{info('[+]')} This site seems to be a multisite (http://codex.wordpress.org/Glossary#Multisite)" + puts info('This site seems to be a multisite (http://codex.wordpress.org/Glossary#Multisite)') end if wp_target.has_must_use_plugins? - puts "#{info('[+]')} This site has 'Must Use Plugins' (http://codex.wordpress.org/Must_Use_Plugins)" + puts info("This site has 'Must Use Plugins' (http://codex.wordpress.org/Must_Use_Plugins)") end if wp_target.registration_enabled? - puts "#{warning('[+]')} Registration is enabled: #{wp_target.registration_url}" + puts warning("Registration is enabled: #{wp_target.registration_url}") end if wp_target.has_xml_rpc? - puts "#{info('[+]')} XML-RPC Interface available under: #{wp_target.xml_rpc_url}" + puts info("XML-RPC Interface available under: #{wp_target.xml_rpc_url}") end if wp_target.upload_directory_listing_enabled? - puts "#{warning('[!]')} Upload directory has directory listing enabled: #{wp_target.upload_dir_url}" + puts warning("Upload directory has directory listing enabled: #{wp_target.upload_dir_url}") end enum_options = { @@ -220,13 +222,13 @@ def main wp_version.output(wpscan_options.verbose) else puts - puts "#{notice('[i]')} WordPress version can not be detected" + puts notice('WordPress version can not be detected') end if wp_theme = wp_target.theme puts # Theme version is handled in #to_s - puts "#{info('[+]')} WordPress theme in use: #{wp_theme}" + puts info("WordPress theme in use: #{wp_theme}") wp_theme.output(wpscan_options.verbose) # Check for parent Themes @@ -236,7 +238,7 @@ def main parent = wp_theme.get_parent_theme puts - puts "#{info('[+]')} Detected parent theme: #{parent}" + puts info("Detected parent theme: #{parent}") parent.output(wpscan_options.verbose) wp_theme = parent end @@ -245,7 +247,7 @@ def main if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil puts - puts "#{info('[+]')} Enumerating plugins from passive detection ..." + puts info('Enumerating plugins from passive detection ...') wp_plugins = WpPlugins.passive_detection(wp_target) if !wp_plugins.empty? @@ -256,14 +258,14 @@ def main end wp_plugins.output(wpscan_options.verbose) else - puts "#{info('[+]')} No plugins found" + puts info('No plugins found') end end # Enumerate the installed plugins if wpscan_options.enumerate_plugins or wpscan_options.enumerate_only_vulnerable_plugins or wpscan_options.enumerate_all_plugins puts - puts "#{info('[+]')} Enumerating installed plugins #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_plugins} ..." + puts info("Enumerating installed plugins #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_plugins} ...") puts wp_plugins = WpPlugins.aggressive_detection(wp_target, @@ -274,18 +276,18 @@ def main ) puts if !wp_plugins.empty? - puts "#{info('[+]')} We found #{wp_plugins.size} plugins:" + puts info("We found #{wp_plugins.size} plugins:") wp_plugins.output(wpscan_options.verbose) else - puts "#{info('[+]')} No plugins found" + puts info('No plugins found') end end # Enumerate installed themes if wpscan_options.enumerate_themes or wpscan_options.enumerate_only_vulnerable_themes or wpscan_options.enumerate_all_themes puts - puts "#{info('[+]')} Enumerating installed themes #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_themes} ..." + puts info("Enumerating installed themes #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_themes} ...") puts wp_themes = WpThemes.aggressive_detection(wp_target, @@ -296,17 +298,17 @@ def main ) puts if !wp_themes.empty? - puts "#{info('[+]')} We found #{wp_themes.size} themes:" + puts info("We found #{wp_themes.size} themes:") wp_themes.output(wpscan_options.verbose) else - puts "#{info('[+]')} No themes found" + puts info('No themes found') end end if wpscan_options.enumerate_timthumbs puts - puts "#{info('[+]')} Enumerating timthumb files ..." + puts info('Enumerating timthumb files ...') puts wp_timthumbs = WpTimthumbs.aggressive_detection(wp_target, @@ -317,22 +319,21 @@ def main ) puts if !wp_timthumbs.empty? - puts "#{info('[+]')} We found #{wp_timthumbs.size} timthumb file/s:" + puts info("We found #{wp_timthumbs.size} timthumb file/s:") wp_timthumbs.output(wpscan_options.verbose) else - puts "#{info('[+]')} No timthumb files found" + puts info('No timthumb files found') end end # If we haven't been supplied a username/usernames list, enumerate them... if !wpscan_options.username && !wpscan_options.usernames && wpscan_options.wordlist || wpscan_options.enumerate_usernames puts - puts "#{info('[+]')} Enumerating usernames ..." + puts info('Enumerating usernames ...') if wp_target.has_plugin?('stop-user-enumeration') - puts "#{warning('[!]')} Stop User Enumeration plugin detected, results might be empty. " \ - "However a bypass exists for v1.2.8 and below, see stop_user_enumeration_bypass.rb in #{File.expand_path(File.dirname(__FILE__))}" + puts warning("Stop User Enumeration plugin detected, results might be empty. However a bypass exists for v1.2.8 and below, see stop_user_enumeration_bypass.rb in #{File.expand_path(File.dirname(__FILE__))}") end wp_users = WpUsers.aggressive_detection(wp_target, @@ -343,7 +344,7 @@ def main ) if wp_users.empty? - puts "#{info('[+]')} We did not enumerate any usernames" + puts info('We did not enumerate any usernames') if wpscan_options.wordlist puts 'Try supplying your own username with the --username option' @@ -351,10 +352,10 @@ def main exit(1) end else - puts "#{info('[+]')} Identified the following #{wp_users.size} user/s:" + puts info("Identified the following #{wp_users.size} user/s:") wp_users.output(margin_left: ' ' * 4) if wp_users[0].login == "admin" - puts "#{warning('[!]')} Default first WordPress username 'admin' is still used" + puts warning("Default first WordPress username 'admin' is still used") end end @@ -378,14 +379,14 @@ def main protection_plugin = wp_target.login_protection_plugin() puts - puts "#{warning('[!]')} The plugin #{protection_plugin.name} has been detected. It might record the IP and timestamp of every failed login and/or prevent brute forcing altogether. Not a good idea for brute forcing!" + puts warning("The plugin #{protection_plugin.name} has been detected. It might record the IP and timestamp of every failed login and/or prevent brute forcing altogether. Not a good idea for brute forcing!") puts '[?] Do you want to start the brute force anyway ? [Y]es [N]o, default: [N]' bruteforce = false if wpscan_options.batch || Readline.readline !~ /^y/i end if bruteforce - puts "#{info('[+]')} Starting the password brute forcer" + puts info('Starting the password brute forcer') begin wp_users.brute_force( @@ -398,7 +399,7 @@ def main wp_users.output(show_password: true, margin_left: ' ' * 2) end else - puts "#{critical('[!]')} Brute forcing aborted" + puts critical('Brute forcing aborted') end end @@ -407,11 +408,12 @@ def main used_memory = get_memory_usage - start_memory puts - puts info("[+] Finished: #{stop_time.asctime}") - puts info("[+] Requests Done: #{@total_requests_done}") - puts info("[+] Memory used: #{used_memory.bytes_to_human}") - puts info("[+] Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}") - exit(0) # must exit! + puts info("Finished: #{stop_time.asctime}") + puts info("Requests Done: #{@total_requests_done}") + puts info("Memory used: #{used_memory.bytes_to_human}") + puts info("Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}") + puts $exit_code + exit($exit_code) # must exit! rescue SystemExit, Interrupt