This commit is contained in:
Christian Mehlmauer
2015-05-12 21:28:12 +02:00
parent b5d5c4177d
commit b931df654d
7 changed files with 75 additions and 71 deletions

View File

@@ -116,19 +116,21 @@ def blue(text)
end end
def critical(text) def critical(text)
red(text) $exit_code += 1
"#{red('[!]')} #{text}"
end end
def warning(text) def warning(text)
amber(text) $exit_code += 1
"#{amber('[!]')} #{text}"
end end
def info(text) def info(text)
green(text) "#{green('[+]')} #{text}"
end end
def notice(text) def notice(text)
blue(text) "#{blue('[i]')} #{text}"
end end
# our 1337 banner # our 1337 banner

View File

@@ -6,7 +6,7 @@ class Vulnerability
# output the vulnerability # output the vulnerability
def output(verbose = false) def output(verbose = false)
puts puts
puts "#{critical('[!]')} Title: #{title}" puts critical("Title: #{title}")
references.each do |key, urls| references.each do |key, urls|
methodname = "url_#{key}" methodname = "url_#{key}"
urls.each do |u| urls.each do |u|
@@ -15,7 +15,7 @@ class Vulnerability
end end
end end
if !fixed_in.nil? if !fixed_in.nil?
puts "#{notice('[i]')} Fixed in: #{fixed_in}" puts notice("Fixed in: #{fixed_in}")
end end
end end
end end

View File

@@ -6,19 +6,19 @@ class WpItem
# @return [ Void ] # @return [ Void ]
def output(verbose = false) def output(verbose = false)
puts 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 " | Location: #{url}"
#puts " | WordPress: #{wordpress_url}" if wordpress_org_item? #puts " | WordPress: #{wordpress_url}" if wordpress_org_item?
puts " | Readme: #{readme_url}" if has_readme? puts " | Readme: #{readme_url}" if has_readme?
puts " | Changelog: #{changelog_url}" if has_changelog? puts " | Changelog: #{changelog_url}" if has_changelog?
puts "#{warning('[!]')} Directory listing is enabled: #{url}" if has_directory_listing? 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("An error_log file has been found: #{error_log_url}") if has_error_log?
additional_output(verbose) if respond_to?(:additional_output) additional_output(verbose) if respond_to?(:additional_output)
if version.nil? && vulnerabilities.length > 0 if version.nil? && vulnerabilities.length > 0
puts 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 end
vulnerabilities.output vulnerabilities.output

View File

@@ -5,7 +5,7 @@ class WpTimthumb < WpItem
def output(verbose = false) def output(verbose = false)
puts 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 vulnerabilities.output
end end

View File

@@ -109,13 +109,13 @@ class WpUser < WpItem
elsif response.body =~ /login_error/i elsif response.body =~ /login_error/i
verbose = "\n Incorrect login and/or password." verbose = "\n Incorrect login and/or password."
elsif response.timed_out? elsif response.timed_out?
progression = "#{critical('ERROR:')} Request timed out." progression = critical('ERROR: Request timed out.')
elsif response.code == 0 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/ 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 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") verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
end end

View File

@@ -5,15 +5,15 @@ class WpVersion < WpItem
def output(verbose = false) def output(verbose = false)
puts 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 vulnerabilities = self.vulnerabilities
unless vulnerabilities.empty? unless vulnerabilities.empty?
if vulnerabilities.size == 1 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 else
puts "#{critical('[!]')} #{vulnerabilities.size} vulnerabilities identified from the version number" puts critical("#{vulnerabilities.size} vulnerabilities identified from the version number")
end end
vulnerabilities.output vulnerabilities.output
end end

108
wpscan.rb
View File

@@ -4,6 +4,8 @@
$: << '.' $: << '.'
require File.dirname(__FILE__) + '/lib/wpscan/wpscan_helper' require File.dirname(__FILE__) + '/lib/wpscan/wpscan_helper'
$exit_code = 0
def main def main
# delete old logfile, check if it is a symlink first. # delete old logfile, check if it is a symlink first.
File.delete(LOG_FILE) if File.exist?(LOG_FILE) and !File.symlink?(LOG_FILE) 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 # 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 # also no need to check if the user supplied the --update switch
if update_required? && !wpscan_options.batch && !wpscan_options.update 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]' print '[?] Do you want to update now? [Y]es [N]o [A]bort, default: [N]'
if (input = Readline.readline) =~ /^y/i if (input = Readline.readline) =~ /^y/i
wpscan_options.update = true wpscan_options.update = true
elsif input =~ /^a/i elsif input =~ /^a/i
puts 'Scan aborted' puts 'Scan aborted'
exit(0) exit(1)
end end
end end
if wpscan_options.update || missing_db_file? 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) 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 program if only option --update is used
exit(0) unless wpscan_options.url exit(0) unless wpscan_options.url
end end
@@ -88,12 +90,12 @@ def main
# Remote website has a redirection? # Remote website has a redirection?
if (redirection = wp_target.redirection) if (redirection = wp_target.redirection)
if redirection =~ /\/wp-admin\/install\.php$/ 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 else
if wpscan_options.follow_redirection if wpscan_options.follow_redirection
puts "Following redirection #{redirection}" puts "Following redirection #{redirection}"
else 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]' print '[?] Do you want follow the redirection ? [Y]es [N]o [A]bort, default: [N]'
end end
if wpscan_options.follow_redirection || !wpscan_options.batch if wpscan_options.follow_redirection || !wpscan_options.batch
@@ -103,7 +105,7 @@ def main
else else
if input =~ /^a/i if input =~ /^a/i
puts 'Scan aborted' puts 'Scan aborted'
exit(0) exit(1)
end end
end end
end end
@@ -123,7 +125,7 @@ def main
# Remote website is wordpress? # Remote website is wordpress?
unless wpscan_options.force unless wpscan_options.force
unless wp_target.wordpress? 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
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 '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]' puts '[?] Continue? [Y]es [N]o, default: [N]'
if wpscan_options.batch || Readline.readline !~ /^y/i if wpscan_options.batch || Readline.readline !~ /^y/i
exit(0) exit(1)
end end
end end
# Output runtime data # Output runtime data
start_time = Time.now start_time = Time.now
start_memory = get_memory_usage start_memory = get_memory_usage
puts "#{info('[+]')} URL: #{wp_target.url}" puts info("URL: #{wp_target.url}")
puts "#{info('[+]')} Started: #{start_time.asctime}" puts info("Started: #{start_time.asctime}")
puts puts
if wp_target.wordpress_hosted? 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 end
if wp_target.has_robots? 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| 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
end end
if wp_target.has_readme? 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 end
if wp_target.has_full_path_disclosure? 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 end
if wp_target.has_debug_log? 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 end
wp_target.config_backup.each do |file_url| 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 end
if wp_target.search_replace_db_2_exists? 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 end
wp_target.interesting_headers.each do |header| wp_target.interesting_headers.each do |header|
output = "#{info('[+]')} Interesting header: " output = info('Interesting header: ')
if header[1].class == Array if header[1].class == Array
header[1].each do |value| header[1].each do |value|
@@ -192,23 +194,23 @@ def main
end end
if wp_target.multisite? 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 end
if wp_target.has_must_use_plugins? 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 end
if wp_target.registration_enabled? if wp_target.registration_enabled?
puts "#{warning('[+]')} Registration is enabled: #{wp_target.registration_url}" puts warning("Registration is enabled: #{wp_target.registration_url}")
end end
if wp_target.has_xml_rpc? 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 end
if wp_target.upload_directory_listing_enabled? 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 end
enum_options = { enum_options = {
@@ -220,13 +222,13 @@ def main
wp_version.output(wpscan_options.verbose) wp_version.output(wpscan_options.verbose)
else else
puts puts
puts "#{notice('[i]')} WordPress version can not be detected" puts notice('WordPress version can not be detected')
end end
if wp_theme = wp_target.theme if wp_theme = wp_target.theme
puts puts
# Theme version is handled in #to_s # 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) wp_theme.output(wpscan_options.verbose)
# Check for parent Themes # Check for parent Themes
@@ -236,7 +238,7 @@ def main
parent = wp_theme.get_parent_theme parent = wp_theme.get_parent_theme
puts puts
puts "#{info('[+]')} Detected parent theme: #{parent}" puts info("Detected parent theme: #{parent}")
parent.output(wpscan_options.verbose) parent.output(wpscan_options.verbose)
wp_theme = parent wp_theme = parent
end end
@@ -245,7 +247,7 @@ def main
if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil
puts puts
puts "#{info('[+]')} Enumerating plugins from passive detection ..." puts info('Enumerating plugins from passive detection ...')
wp_plugins = WpPlugins.passive_detection(wp_target) wp_plugins = WpPlugins.passive_detection(wp_target)
if !wp_plugins.empty? if !wp_plugins.empty?
@@ -256,14 +258,14 @@ def main
end end
wp_plugins.output(wpscan_options.verbose) wp_plugins.output(wpscan_options.verbose)
else else
puts "#{info('[+]')} No plugins found" puts info('No plugins found')
end end
end end
# Enumerate the installed plugins # Enumerate the installed plugins
if wpscan_options.enumerate_plugins or wpscan_options.enumerate_only_vulnerable_plugins or wpscan_options.enumerate_all_plugins if wpscan_options.enumerate_plugins or wpscan_options.enumerate_only_vulnerable_plugins or wpscan_options.enumerate_all_plugins
puts 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 puts
wp_plugins = WpPlugins.aggressive_detection(wp_target, wp_plugins = WpPlugins.aggressive_detection(wp_target,
@@ -274,18 +276,18 @@ def main
) )
puts puts
if !wp_plugins.empty? 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) wp_plugins.output(wpscan_options.verbose)
else else
puts "#{info('[+]')} No plugins found" puts info('No plugins found')
end end
end end
# Enumerate installed themes # Enumerate installed themes
if wpscan_options.enumerate_themes or wpscan_options.enumerate_only_vulnerable_themes or wpscan_options.enumerate_all_themes if wpscan_options.enumerate_themes or wpscan_options.enumerate_only_vulnerable_themes or wpscan_options.enumerate_all_themes
puts 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 puts
wp_themes = WpThemes.aggressive_detection(wp_target, wp_themes = WpThemes.aggressive_detection(wp_target,
@@ -296,17 +298,17 @@ def main
) )
puts puts
if !wp_themes.empty? 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) wp_themes.output(wpscan_options.verbose)
else else
puts "#{info('[+]')} No themes found" puts info('No themes found')
end end
end end
if wpscan_options.enumerate_timthumbs if wpscan_options.enumerate_timthumbs
puts puts
puts "#{info('[+]')} Enumerating timthumb files ..." puts info('Enumerating timthumb files ...')
puts puts
wp_timthumbs = WpTimthumbs.aggressive_detection(wp_target, wp_timthumbs = WpTimthumbs.aggressive_detection(wp_target,
@@ -317,22 +319,21 @@ def main
) )
puts puts
if !wp_timthumbs.empty? 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) wp_timthumbs.output(wpscan_options.verbose)
else else
puts "#{info('[+]')} No timthumb files found" puts info('No timthumb files found')
end end
end end
# If we haven't been supplied a username/usernames list, enumerate them... # 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 if !wpscan_options.username && !wpscan_options.usernames && wpscan_options.wordlist || wpscan_options.enumerate_usernames
puts puts
puts "#{info('[+]')} Enumerating usernames ..." puts info('Enumerating usernames ...')
if wp_target.has_plugin?('stop-user-enumeration') if wp_target.has_plugin?('stop-user-enumeration')
puts "#{warning('[!]')} Stop User Enumeration plugin detected, results might be empty. " \ 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__))}")
"However a bypass exists for v1.2.8 and below, see stop_user_enumeration_bypass.rb in #{File.expand_path(File.dirname(__FILE__))}"
end end
wp_users = WpUsers.aggressive_detection(wp_target, wp_users = WpUsers.aggressive_detection(wp_target,
@@ -343,7 +344,7 @@ def main
) )
if wp_users.empty? if wp_users.empty?
puts "#{info('[+]')} We did not enumerate any usernames" puts info('We did not enumerate any usernames')
if wpscan_options.wordlist if wpscan_options.wordlist
puts 'Try supplying your own username with the --username option' puts 'Try supplying your own username with the --username option'
@@ -351,10 +352,10 @@ def main
exit(1) exit(1)
end end
else 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) wp_users.output(margin_left: ' ' * 4)
if wp_users[0].login == "admin" 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
end end
@@ -378,14 +379,14 @@ def main
protection_plugin = wp_target.login_protection_plugin() protection_plugin = wp_target.login_protection_plugin()
puts 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]' 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 bruteforce = false if wpscan_options.batch || Readline.readline !~ /^y/i
end end
if bruteforce if bruteforce
puts "#{info('[+]')} Starting the password brute forcer" puts info('Starting the password brute forcer')
begin begin
wp_users.brute_force( wp_users.brute_force(
@@ -398,7 +399,7 @@ def main
wp_users.output(show_password: true, margin_left: ' ' * 2) wp_users.output(show_password: true, margin_left: ' ' * 2)
end end
else else
puts "#{critical('[!]')} Brute forcing aborted" puts critical('Brute forcing aborted')
end end
end end
@@ -407,11 +408,12 @@ def main
used_memory = get_memory_usage - start_memory used_memory = get_memory_usage - start_memory
puts puts
puts info("[+] Finished: #{stop_time.asctime}") puts info("Finished: #{stop_time.asctime}")
puts info("[+] Requests Done: #{@total_requests_done}") puts info("Requests Done: #{@total_requests_done}")
puts info("[+] Memory used: #{used_memory.bytes_to_human}") puts info("Memory used: #{used_memory.bytes_to_human}")
puts info("[+] Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}") puts info("Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}")
exit(0) # must exit! puts $exit_code
exit($exit_code) # must exit!
rescue SystemExit, Interrupt rescue SystemExit, Interrupt