#!/usr/bin/env ruby # encoding: UTF-8 $: << '.' require File.dirname(__FILE__) + '/lib/wpscan/wpscan_helper' 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) banner() begin wpscan_options = WpscanOptions.load_from_arguments unless wpscan_options.has_options? # first parameter only url? if ARGV.length == 1 wpscan_options.url = ARGV[0] else usage() raise('No argument supplied') end end # Define a global variable $COLORSWITCH = wpscan_options.no_color if wpscan_options.help help() usage() exit(0) end if wpscan_options.version puts "Current version: #{version}" exit(0) end # Check for updates if wpscan_options.update puts 'Updating the DB ..' DbUpdater.new(DATA_DIR).update puts 'Done.' exit # remove me if !@updater.nil? if @updater.has_local_changes? print "#{red('[!]')} Local file changes detected, an update will override local changes, do you want to continue updating? [y/n] " Readline.readline =~ /^y/i ? @updater.reset_head : raise('Update aborted') end puts @updater.update() else puts '[i] Svn / Git not installed, or wpscan has not been installed with one of them.' puts "#{red('[!]')} Update aborted" end exit(0) end unless wpscan_options.url raise 'The URL is mandatory, please supply it with --url or -u' end wp_target = WpTarget.new(wpscan_options.url, wpscan_options.to_h) # Remote website up? unless wp_target.online? raise "The WordPress URL supplied '#{wp_target.uri}' seems to be down." end if wpscan_options.proxy proxy_response = Browser.get(wp_target.url) unless WpTarget::valid_response_codes.include?(proxy_response.code) raise "Proxy Error :\r\n#{proxy_response.headers}" end end # Remote website has a redirection? if (redirection = wp_target.redirection) if wpscan_options.follow_redirection puts "Following redirection #{redirection}" else puts "#{blue('[i]')} 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 if wpscan_options.follow_redirection || (input = Readline.readline) =~ /^y/i wpscan_options.url = redirection wp_target = WpTarget.new(redirection, wpscan_options.to_h) else if input =~ /^a/i puts 'Scan aborted' exit(0) end end end end if wp_target.has_basic_auth? && wpscan_options.basic_auth.nil? raise 'Basic authentication is required, please provide it with --basic-auth ' end # Remote website is wordpress? unless wpscan_options.force unless wp_target.wordpress? raise "#{red('[!]')} The remote website is up, but does not seem to be running WordPress." end end unless wp_target.wp_content_dir raise 'The wp_content_dir has not been found, please supply it with --wp-content-dir' end unless wp_target.wp_plugins_dir_exists? puts "The plugins directory '#{wp_target.wp_plugins_dir}' does not exist." 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) end end # Output runtime data start_time = Time.now start_memory = get_memory_usage puts "#{green('[+]')} URL: #{wp_target.url}" puts "#{green('[+]')} Started: #{start_time.asctime}" puts if wp_target.wordpress_hosted? puts "#{red('[!]')} We do not support scanning *.wordpress.com hosted blogs" end if wp_target.has_robots? puts "#{green('[+]')} robots.txt available under: '#{wp_target.robots_url}'" wp_target.parse_robots_txt.each do |dir| puts "#{green('[+]')} Interesting entry from robots.txt: #{dir}" end end if wp_target.has_readme? puts "#{red('[!]')} The WordPress '#{wp_target.readme_url}' file exists" end if wp_target.has_full_path_disclosure? puts "#{red('[!]')} Full Path Disclosure (FPD) in: '#{wp_target.full_path_disclosure_url}'" end if wp_target.has_debug_log? puts "#{red('[!]')} Debug log file found: #{wp_target.debug_log_url}" end wp_target.config_backup.each do |file_url| puts "#{red('[!]')} A wp-config.php backup file has been found in: '#{file_url}'" end if wp_target.search_replace_db_2_exists? puts "#{red('[!]')} searchreplacedb2.php has been found in: '#{wp_target.search_replace_db_2_url}'" end wp_target.interesting_headers.each do |header| output = "#{green('[+]')} Interesting header: " if header[1].class == Array header[1].each do |value| puts output + "#{header[0]}: #{value}" end else puts output + "#{header[0]}: #{header[1]}" end end if wp_target.multisite? puts "#{green('[+]')} This site seems to be a multisite (http://codex.wordpress.org/Glossary#Multisite)" end if wp_target.has_must_use_plugins? puts "#{green('[+]')} This site has must use plugins (http://codex.wordpress.org/Must_Use_Plugins)" end if wp_target.registration_enabled? puts "#{amber('[+]')} Registration is enabled: #{wp_target.registration_url}" end if wp_target.has_xml_rpc? puts "#{green('[+]')} XML-RPC Interface available under: #{wp_target.xml_rpc_url}" end if wp_target.upload_directory_listing_enabled? puts "#{red('[!]')} Upload directory has directory listing enabled: #{wp_target.upload_dir_url}" end if wp_target.has_malwares? malwares = wp_target.malwares puts "#{red('[!]')} #{malwares.size} malware(s) found:" malwares.each do |malware_url| puts puts ' | ' + red("#{malware_url}") end puts end enum_options = { show_progression: true, exclude_content: wpscan_options.exclude_content_based } if wp_version = wp_target.version(WP_VERSIONS_FILE) wp_version.output(wpscan_options.verbose) else puts puts "#{blue('[i]')} WordPress version can not be detected" end if wp_theme = wp_target.theme puts # Theme version is handled in #to_s puts "#{green('[+]')} WordPress theme in use: #{wp_theme}" wp_theme.output(wpscan_options.verbose) # Check for parent Themes while wp_theme.is_child_theme? parent = wp_theme.get_parent_theme puts puts "#{green('[+]')} Detected parent theme: #{parent}" parent.output(wpscan_options.verbose) wp_theme = parent end end if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil puts puts "#{green('[+]')} Enumerating plugins from passive detection ..." wp_plugins = WpPlugins.passive_detection(wp_target) if !wp_plugins.empty? puts " | #{wp_plugins.size} plugins found:" wp_plugins.output(wpscan_options.verbose) else puts "#{green('[+]')} 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 "#{green('[+]')} Enumerating installed plugins #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_plugins} ..." puts wp_plugins = WpPlugins.aggressive_detection(wp_target, enum_options.merge( file: wpscan_options.enumerate_all_plugins ? PLUGINS_FULL_FILE : PLUGINS_FILE, only_vulnerable: wpscan_options.enumerate_only_vulnerable_plugins || false ) ) puts if !wp_plugins.empty? puts "#{green('[+]')} We found #{wp_plugins.size} plugins:" wp_plugins.output(wpscan_options.verbose) else puts "#{green('[+]')} 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 "#{green('[+]')} Enumerating installed themes #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_themes} ..." puts wp_themes = WpThemes.aggressive_detection(wp_target, enum_options.merge( file: wpscan_options.enumerate_all_themes ? THEMES_FULL_FILE : THEMES_FILE, only_vulnerable: wpscan_options.enumerate_only_vulnerable_themes || false ) ) puts if !wp_themes.empty? puts "#{green('[+]')} We found #{wp_themes.size} themes:" wp_themes.output(wpscan_options.verbose) else puts "#{green('[+]')} No themes found" end end if wpscan_options.enumerate_timthumbs puts puts "#{green('[+]')} Enumerating timthumb files ..." puts wp_timthumbs = WpTimthumbs.aggressive_detection(wp_target, enum_options.merge( file: DATA_DIR + '/timthumbs.txt', theme_name: wp_theme ? wp_theme.name : nil ) ) puts if !wp_timthumbs.empty? puts "#{green('[+]')} We found #{wp_timthumbs.size} timthumb file/s:" wp_timthumbs.output(wpscan_options.verbose) else puts "#{green('[+]')} No timthumb files found" end end # If we haven't been supplied a username, enumerate them... if !wpscan_options.username and wpscan_options.wordlist or wpscan_options.enumerate_usernames puts puts "#{green('[+]')} Enumerating usernames ..." if wp_target.has_plugin?('stop-user-enumeration') puts "#{red('[!]')} Stop User Enumeration plugin detected, results might be empty. " \ "However a bypass exists, see stop_user_enumeration_bypass.rb in #{File.expand_path(File.dirname(__FILE__))}" end wp_users = WpUsers.aggressive_detection(wp_target, enum_options.merge( range: wpscan_options.enumerate_usernames_range, show_progression: false ) ) if wp_users.empty? puts "#{green('[+]')} We did not enumerate any usernames" if wpscan_options.wordlist puts 'Try supplying your own username with the --username option' puts exit(1) end else puts "#{green('[+]')} Identified the following #{wp_users.size} user/s:" wp_users.output(margin_left: ' ' * 4) end else # FIXME : Change the .username to .login (and also the --username in the CLI) wp_users = WpUsers.new << WpUser.new(wp_target.uri, login: wpscan_options.username) end # Start the brute forcer bruteforce = true if wpscan_options.wordlist if wp_target.has_login_protection? protection_plugin = wp_target.login_protection_plugin() puts puts "#{red('[!]')} 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 "#{green('[+]')} Starting the password brute forcer" begin wp_users.brute_force( wpscan_options.wordlist, show_progression: true, verbose: wpscan_options.verbose ) ensure puts wp_users.output(show_password: true, margin_left: ' ' * 2) end else puts "#{red('[!]')} Brute forcing aborted" end end stop_time = Time.now elapsed = stop_time - start_time used_memory = get_memory_usage - start_memory puts puts green("[+] Finished: #{stop_time.asctime}") puts green("[+] Memory used: #{used_memory.bytes_to_human}") puts green("[+] Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}") exit(0) # must exit! rescue SystemExit, Interrupt rescue => e puts puts red(e.message) if wpscan_options && wpscan_options.verbose puts red('Trace:') puts red(e.backtrace.join("\n")) end exit(1) ensure # Ensure a clean abort of Hydra # See https://github.com/wpscanteam/wpscan/issues/461#issuecomment-42735615 Browser.instance.hydra.abort Browser.instance.hydra.run end end main()