lib/wpscan rubocopied

This commit is contained in:
erwanlr
2013-01-24 18:23:54 +01:00
parent b0dd9ba989
commit 3094d31633
24 changed files with 338 additions and 296 deletions

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -34,10 +35,10 @@ module BruteForce
request_count = 0 request_count = 0
password_found = false password_found = false
File.open(wordlist_path, "r").each do |password| File.open(wordlist_path, 'r').each do |password|
# ignore file comments, but will miss passwords if they start with a hash... # ignore file comments, but will miss passwords if they start with a hash...
next if password[0,1] == "#" next if password[0, 1] == '#'
# keep a count of the amount of requests to be sent # keep a count of the amount of requests to be sent
request_count += 1 request_count += 1
@@ -50,9 +51,9 @@ module BruteForce
# the request object # the request object
request = Browser.instance.forge_request(login_url, request = Browser.instance.forge_request(login_url,
{ {
:method => :post, method: :post,
:params => {:log => username, :pwd => password}, params: {log: username, pwd: password},
:cache_timeout => 0 cache_timeout: 0
} }
) )
@@ -64,20 +65,20 @@ module BruteForce
if response.body =~ /login_error/i if response.body =~ /login_error/i
puts "\nIncorrect username and/or password." if @verbose puts "\nIncorrect username and/or password." if @verbose
elsif response.code == 302 elsif response.code == 302
puts "\n " + green("[SUCCESS]") + " Username : #{username} Password : #{password}\n" if show_progression puts "\n " + green('[SUCCESS]') + " Username : #{username} Password : #{password}\n" if show_progression
found << { :name => username, :password => password } found << { name: username, password: password }
password_found = true password_found = true
elsif response.timed_out? elsif response.timed_out?
puts red("ERROR:") + " Request timed out." if show_progression puts red('ERROR:') + ' Request timed out.' if show_progression
elsif response.code == 0 elsif response.code == 0
puts red("ERROR:") + " No response from remote server. WAF/IPS?" if show_progression puts red('ERROR:') + ' No response from remote server. WAF/IPS?' if show_progression
# code is a fixnum, needs a string for regex # code is a fixnum, needs a string for regex
elsif response.code.to_s =~ /^50/ elsif response.code.to_s =~ /^50/
puts red("ERROR:") + " Server error, try reducing the number of threads." if show_progression puts red('ERROR:') + ' Server error, try reducing the number of threads.' if show_progression
else else
puts "\n" + red("ERROR:") + " We recieved an unknown response for #{password}..." if show_progression puts "\n" + red('ERROR:') + " We recieved an unknown response for #{password}..." if show_progression
# ugly method to get the coverage :/ (otherwise some output is present in the rspec) # HACK to get the coverage :/ (otherwise some output is present in the rspec)
puts red("Code: #{response.code.to_s}") if @verbose puts red("Code: #{response.code.to_s}") if @verbose
puts red("Body: #{response.body}") if @verbose puts red("Body: #{response.body}") if @verbose
puts if @verbose puts if @verbose
@@ -116,7 +117,7 @@ module BruteForce
# wordlists, although bareable. # wordlists, although bareable.
def self.lines_in_file(file_path) def self.lines_in_file(file_path)
lines = 0 lines = 0
File.open(file_path, 'r').each { || lines += 1 } File.open(file_path, 'r').each { |_| lines += 1 }
lines lines
end end
end end

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -17,7 +18,10 @@
#++ #++
module Malwares module Malwares
# Used as cache : nil => malwares not checked, [] => no malwares, otherwise array of malwares url found # Used as cache :
# nil => malwares not checked,
# [] => no malwares,
# otherwise array of malwares url found
@malwares = nil @malwares = nil
def has_malwares?(malwares_file_path = nil) def has_malwares?(malwares_file_path = nil)

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -34,7 +35,7 @@ module WebSite
response = Browser.instance.get( response = Browser.instance.get(
login_url(), login_url(),
{:follow_location => true, :max_redirects => 2} { follow_location: true, max_redirects: 2 }
) )
if response.body =~ %r{WordPress}i if response.body =~ %r{WordPress}i
@@ -42,7 +43,7 @@ module WebSite
else else
response = Browser.instance.get( response = Browser.instance.get(
xml_rpc_url, xml_rpc_url,
{:follow_location => true, :max_redirects => 2} { follow_location: true, max_redirects: 2 }
) )
if response.body =~ %r{XML-RPC server accepts POST requests only}i if response.body =~ %r{XML-RPC server accepts POST requests only}i
@@ -60,7 +61,7 @@ module WebSite
def xml_rpc_url def xml_rpc_url
unless @xmlrpc_url unless @xmlrpc_url
headers = Browser.instance.get(@uri.to_s).headers_hash headers = Browser.instance.get(@uri.to_s).headers_hash
value = headers["x-pingback"] value = headers['x-pingback']
if value.nil? or value.empty? if value.nil? or value.empty?
@xmlrpc_url = nil @xmlrpc_url = nil
else else
@@ -105,7 +106,7 @@ module WebSite
# Return the MD5 hash of a 404 page # Return the MD5 hash of a 404 page
def error_404_hash def error_404_hash
unless @error_404_hash unless @error_404_hash
non_existant_page = Digest::MD5.hexdigest(rand(9999999999).to_s) + ".html" non_existant_page = Digest::MD5.hexdigest(rand(999_999_999).to_s) + '.html'
@error_404_hash = WebSite.page_hash(@uri.merge(non_existant_page).to_s) @error_404_hash = WebSite.page_hash(@uri.merge(non_existant_page).to_s)
end end
@error_404_hash @error_404_hash

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -25,6 +26,6 @@ module WpFullPathDisclosure
end end
def full_path_disclosure_url def full_path_disclosure_url
@uri.merge("wp-includes/rss-functions.php").to_s @uri.merge('wp-includes/rss-functions.php').to_s
end end
end end

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -37,10 +38,10 @@ module WpLoginProtection
plugin_name = symbol_to_call[LOGIN_PROTECTION_METHOD_PATTERN, 1].gsub('_', '-') plugin_name = symbol_to_call[LOGIN_PROTECTION_METHOD_PATTERN, 1].gsub('_', '-')
return @login_protection_plugin = WpPlugin.new( return @login_protection_plugin = WpPlugin.new(
:name => plugin_name, name: plugin_name,
:base_url => @uri, base_url: @uri,
:path => "/plugins/#{plugin_name}/", path: "/plugins/#{plugin_name}/",
:wp_content_dir => @wp_content_dir wp_content_dir: @wp_content_dir
) )
end end
end end
@@ -67,10 +68,10 @@ module WpLoginProtection
end end
def better_wp_security_url def better_wp_security_url
WpPlugin.new(:wp_content_dir => @wp_content_dir, WpPlugin.new(wp_content_dir: @wp_content_dir,
:base_url => @uri, base_url: @uri,
:path => "/plugins/better-wp-security/", path: '/plugins/better-wp-security/',
:name => "better-wp-security" name: 'better-wp-security'
).get_url_without_filename ).get_url_without_filename
end end
@@ -80,10 +81,10 @@ module WpLoginProtection
end end
def simple_login_lockdown_url def simple_login_lockdown_url
WpPlugin.new(:wp_content_dir => @wp_content_dir, WpPlugin.new(wp_content_dir: @wp_content_dir,
:base_url => @uri, base_url: @uri,
:path => "/plugins/simple-login-lockdown/", path: '/plugins/simple-login-lockdown/',
:name => "simple-login-lockdown" name: 'simple-login-lockdown'
).get_url_without_filename ).get_url_without_filename
end end
@@ -93,10 +94,10 @@ module WpLoginProtection
end end
def login_security_solution_url def login_security_solution_url
WpPlugin.new(:wp_content_dir => @wp_content_dir, WpPlugin.new(wp_content_dir: @wp_content_dir,
:base_url => @uri, base_url: @uri,
:path => "/plugins/login-security-solution/", path: '/plugins/login-security-solution/',
:name => "login-security-solution" name: 'login-security-solution'
).get_url_without_filename ).get_url_without_filename
end end
@@ -106,10 +107,10 @@ module WpLoginProtection
end end
def limit_login_attempts_url def limit_login_attempts_url
WpPlugin.new(:wp_content_dir => @wp_content_dir, WpPlugin.new(wp_content_dir: @wp_content_dir,
:base_url => @uri, base_url: @uri,
:path => "/plugins/limit-login-attempts/", path: '/plugins/limit-login-attempts/',
:name => "limit-login-attempts" name: 'limit-login-attempts'
).get_url_without_filename ).get_url_without_filename
end end
@@ -119,10 +120,10 @@ module WpLoginProtection
end end
def bluetrait_event_viewer_url def bluetrait_event_viewer_url
WpPlugin.new(:wp_content_dir => @wp_content_dir, WpPlugin.new(wp_content_dir: @wp_content_dir,
:base_url => @uri, base_url: @uri,
:path => "/plugins/bluetrait-event-viewer/", path: '/plugins/bluetrait-event-viewer/',
:name => "bluetrait-event-viewer" name: 'bluetrait-event-viewer'
).get_url_without_filename ).get_url_without_filename
end end
end end

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -22,22 +23,24 @@ module WpPlugins
# #
# return array of WpPlugin # return array of WpPlugin
def plugins_from_aggressive_detection(options) def plugins_from_aggressive_detection(options)
if options[:vulns_file].nil? or options[:vulns_file] == ''
options[:vulns_file] = PLUGINS_VULNS_FILE
end
options[:file] = options[:file] || (options[:full] ? PLUGINS_FULL_FILE : PLUGINS_FILE) options[:file] = options[:file] || (options[:full] ? PLUGINS_FULL_FILE : PLUGINS_FILE)
options[:vulns_file] = (options[:vulns_file] != nil and options[:vulns_file] != "") ?
options[:vulns_file] : PLUGINS_VULNS_FILE
options[:vulns_xpath] = "//plugin[@name='#{@name}']/vulnerability" options[:vulns_xpath] = "//plugin[@name='#{@name}']/vulnerability"
options[:vulns_xpath_2] = "//plugin" options[:vulns_xpath_2] = '//plugin'
options[:type] = "plugins" options[:type] = 'plugins'
result = WpDetector.aggressive_detection(options) result = WpDetector.aggressive_detection(options)
plugins = [] plugins = []
result.each do |r| result.each do |r|
plugins << WpPlugin.new( plugins << WpPlugin.new(
:base_url => r.base_url, base_url: r.base_url,
:path => r.path, path: r.path,
:wp_content_dir => r.wp_content_dir, wp_content_dir: r.wp_content_dir,
:name => r.name, name: r.name,
:type => "plugins", type: 'plugins',
:wp_plugins_dir => r.wp_plugins_dir wp_plugins_dir: r.wp_plugins_dir
) )
end end
plugins.sort_by { |p| p.name } plugins.sort_by { |p| p.name }
@@ -51,16 +54,16 @@ module WpPlugins
# return array of WpPlugin # return array of WpPlugin
def plugins_from_passive_detection(options) def plugins_from_passive_detection(options)
plugins = [] plugins = []
temp = WpDetector.passive_detection(options[:base_url], "plugins", options[:wp_content_dir]) temp = WpDetector.passive_detection(options[:base_url], 'plugins', options[:wp_content_dir])
temp.each do |item| temp.each do |item|
plugins << WpPlugin.new( plugins << WpPlugin.new(
:base_url => item.base_url, base_url: item.base_url,
:name => item.name, name: item.name,
:path => item.path, path: item.path,
:wp_content_dir => options[:wp_content_dir], wp_content_dir: options[:wp_content_dir],
:type => "plugins", type: 'plugins',
:wp_plugins_dir => options[:wp_plugins_dir] wp_plugins_dir: options[:wp_plugins_dir]
) )
end end
plugins.sort_by { |p| p.name } plugins.sort_by { |p| p.name }

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -31,6 +32,6 @@ module WpReadme
end end
def readme_url def readme_url
@uri.merge("readme.html").to_s @uri.merge('readme.html').to_s
end end
end end

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -19,20 +20,22 @@
module WpThemes module WpThemes
def themes_from_aggressive_detection(options) def themes_from_aggressive_detection(options)
if options[:vulns_file].nil? or options[:vulns_file] == ''
options[:vulns_file] = THEMES_VULNS_FILE
end
options[:file] = options[:file] || (options[:full] ? THEMES_FULL_FILE : THEMES_FILE) options[:file] = options[:file] || (options[:full] ? THEMES_FULL_FILE : THEMES_FILE)
options[:vulns_file] = (options[:vulns_file] != nil and options[:vulns_file] != "") ?
options[:vulns_file] : THEMES_VULNS_FILE
options[:vulns_xpath] = "//theme[@name='#{@name}']/vulnerability" options[:vulns_xpath] = "//theme[@name='#{@name}']/vulnerability"
options[:vulns_xpath_2] = "//theme" options[:vulns_xpath_2] = '//theme'
options[:type] = "themes" options[:type] = 'themes'
result = WpDetector.aggressive_detection(options) result = WpDetector.aggressive_detection(options)
themes = [] themes = []
result.each do |r| result.each do |r|
themes << WpTheme.new( themes << WpTheme.new(
:base_url => r.base_url, base_url: r.base_url,
:path => r.path, path: r.path,
:wp_content_dir => r.wp_content_dir, wp_content_dir: r.wp_content_dir,
:name => r.name name: r.name
) )
end end
themes.sort_by { |t| t.name } themes.sort_by { |t| t.name }
@@ -40,14 +43,14 @@ module WpThemes
def themes_from_passive_detection(options) def themes_from_passive_detection(options)
themes = [] themes = []
temp = WpDetector.passive_detection(options[:base_url], "themes", options[:wp_content_dir]) temp = WpDetector.passive_detection(options[:base_url], 'themes', options[:wp_content_dir])
temp.each do |item| temp.each do |item|
themes << WpTheme.new( themes << WpTheme.new(
:base_url => item.base_url, base_url: item.base_url,
:name => item.name, name: item.name,
:path => item.path, path: item.path,
:wp_content_dir => options[:wp_content_dir] wp_content_dir: options[:wp_content_dir]
) )
end end
themes.sort_by { |t| t.name } themes.sort_by { |t| t.name }

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -18,7 +19,10 @@
module WpTimthumbs module WpTimthumbs
# Used as cache : nil => timthumbs not checked, [] => no timthumbs, otherwise array of timthumbs url found # Used as cache :
# nil => timthumbs not checked,
# [] => no timthumbs,
# otherwise array of timthumbs url found
@wp_timthumbs = nil @wp_timthumbs = nil
def has_timthumbs?(theme_name, options = {}) def has_timthumbs?(theme_name, options = {})
@@ -27,12 +31,12 @@ module WpTimthumbs
def timthumbs(theme_name = nil, options = {}) def timthumbs(theme_name = nil, options = {})
if @wp_timthumbs.nil? if @wp_timthumbs.nil?
options[:type] = "timthumbs" options[:type] = 'timthumbs'
options[:only_vulnerable_ones] = false options[:only_vulnerable_ones] = false
options[:file] = options[:file] || DATA_DIR + "/timthumbs.txt" options[:file] = options[:file] || DATA_DIR + '/timthumbs.txt'
options[:vulns_file] = "xxx" options[:vulns_file] = 'xxx'
options[:vulns_xpath] = "xxx" options[:vulns_xpath] = 'xxx'
options[:vulns_xpath_2] = "xxx" options[:vulns_xpath_2] = 'xxx'
WpOptions.check_options(options) WpOptions.check_options(options)
if theme_name == nil if theme_name == nil
@@ -55,13 +59,13 @@ module WpTimthumbs
scripts/timthumb.php tools/timthumb.php functions/timthumb.php scripts/timthumb.php tools/timthumb.php functions/timthumb.php
}.each do |file| }.each do |file|
targets << WpItem.new( targets << WpItem.new(
:base_url => options[:base_url], base_url: options[:base_url],
:path => "themes/#{theme_name}/#{file}", path: "themes/#{theme_name}/#{file}",
:wp_content_dir => options[:wp_content_dir], wp_content_dir: options[:wp_content_dir],
:name => theme_name, name: theme_name,
:vulns_file => "XX", vulns_file: 'XX',
:type => "timthumbs", type: 'timthumbs',
:wp_plugins_dir => options[:wp_plugins_dir] wp_plugins_dir: options[:wp_plugins_dir]
) )
end end
targets targets

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -58,7 +59,7 @@ module WpUsernames
end end
def get_nickname_from_url(url) def get_nickname_from_url(url)
resp = Browser.instance.get(url, {:follow_location => true, :max_redirects => 2}) resp = Browser.instance.get(url, { follow_location: true, max_redirects: 2 })
nickname = nil nickname = nil
if resp.code == 200 if resp.code == 200
nickname = extract_nickname_from_body(resp.body) nickname = extract_nickname_from_body(resp.body)
@@ -80,21 +81,21 @@ module WpUsernames
def remove_junk_from_nickname(usernames) def remove_junk_from_nickname(usernames)
unless usernames.kind_of? Array unless usernames.kind_of? Array
raise("Need an array as input") raise('Need an array as input')
end end
nicknames = [] nicknames = []
usernames.each do |u| usernames.each do |u|
unless u.kind_of? WpUser unless u.kind_of? WpUser
raise("Items must be of type WpUser") raise('Items must be of type WpUser')
end end
nickname = u.nickname nickname = u.nickname
unless nickname == "empty" unless nickname == 'empty'
nicknames << nickname nicknames << nickname
end end
end end
junk = get_equal_string_end(nicknames) junk = get_equal_string_end(nicknames)
usernames.each do |u| usernames.each do |u|
u.nickname = u.nickname.sub(/#{Regexp.escape(junk)}$/, "") u.nickname = u.nickname.sub(/#{Regexp.escape(junk)}$/, '')
end end
usernames usernames
end end

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -30,10 +31,10 @@ class Vulnerable
xml.xpath(@vulns_xpath).each do |node| xml.xpath(@vulns_xpath).each do |node|
vulnerabilities << WpVulnerability.new( vulnerabilities << WpVulnerability.new(
node.search("title").text, node.search('title').text,
node.search("reference").map(&:text), node.search('reference').map(&:text),
node.search("type").text, node.search('type').text,
node.search("metasploit").map(&:text) node.search('metasploit').map(&:text)
) )
end end
vulnerabilities vulnerabilities

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -63,12 +64,12 @@ class WpDetector
names.each do |item| names.each do |item|
items << WpItem.new( items << WpItem.new(
:base_url => url, base_url: url,
:name => item, name: item,
:type => type, type: type,
:path => "#{item}/", path: "#{item}/",
:wp_content_dir => wp_content_dir, wp_content_dir: wp_content_dir,
:vulns_file => "" vulns_file: ''
) )
end end
items items

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -54,7 +55,7 @@ class WpEnumerator
targets.each do |target| targets.each do |target|
url = target.get_full_url url = target.get_full_url
request = enum_browser.forge_request(url, { :cache_timeout => 0, :follow_location => true }) request = enum_browser.forge_request(url, { cache_timeout: 0, follow_location: true })
request_count += 1 request_count += 1
request.on_complete do |response| request.on_complete do |response|
@@ -102,17 +103,17 @@ class WpEnumerator
unless only_vulnerable unless only_vulnerable
# Open and parse the 'most popular' plugin list... # Open and parse the 'most popular' plugin list...
File.open(file, "r") do |f| File.open(file, 'r') do |f|
f.readlines.collect do |line| f.readlines.collect do |line|
l = line.strip l = line.strip
targets_url << WpItem.new( targets_url << WpItem.new(
:base_url => url, base_url: url,
:path => l, path: l,
:wp_content_dir => wp_content_dir, wp_content_dir: wp_content_dir,
:name => l =~ /.+\/.+/ ? File.dirname(l) : l.sub(/\/$/, ""), name: l =~ /.+\/.+/ ? File.dirname(l) : l.sub(/\/$/, ''),
:vulns_file => vulns_file, vulns_file: vulns_file,
:type => type, type: type,
:wp_plugins_dir => plugins_dir wp_plugins_dir: plugins_dir
) )
end end
end end
@@ -126,15 +127,15 @@ class WpEnumerator
# We check if the plugin name from the plugin_vulns_file is already in targets, otherwise we add it # We check if the plugin name from the plugin_vulns_file is already in targets, otherwise we add it
xml.xpath(options[:vulns_xpath_2]).each do |node| xml.xpath(options[:vulns_xpath_2]).each do |node|
name = node.attribute("name").text name = node.attribute('name').text
targets_url << WpItem.new( targets_url << WpItem.new(
:base_url => url, base_url: url,
:path => name, path: name,
:wp_content_dir => wp_content_dir, wp_content_dir: wp_content_dir,
:name => name, name: name,
:vulns_file => vulns_file, vulns_file: vulns_file,
:type => type, type: type,
:wp_plugins_dir => plugins_dir wp_plugins_dir: plugins_dir
) )
end end
end end

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -24,7 +25,7 @@ class WpItem < Vulnerable
def initialize(options) def initialize(options)
@type = options[:type] @type = options[:type]
@wp_content_dir = options[:wp_content_dir] ? options[:wp_content_dir].sub(/^\//, "").sub(/\/$/, "") : "wp-content" @wp_content_dir = options[:wp_content_dir] ? options[:wp_content_dir].sub(/^\//, '').sub(/\/$/, '') : 'wp-content'
@wp_plugins_dir = options[:wp_plugins_dir] || "#@wp_content_dir/plugins" @wp_plugins_dir = options[:wp_plugins_dir] || "#@wp_content_dir/plugins"
@base_url = options[:base_url] @base_url = options[:base_url]
@path = options[:path] @path = options[:path]
@@ -32,36 +33,36 @@ class WpItem < Vulnerable
@vulns_file = options[:vulns_file] @vulns_file = options[:vulns_file]
@vulns_xpath = options[:vulns_xpath].sub(/\$name\$/, @name) unless options[:vulns_xpath] == nil @vulns_xpath = options[:vulns_xpath].sub(/\$name\$/, @name) unless options[:vulns_xpath] == nil
raise("base_url not set") unless @base_url raise('base_url not set') unless @base_url
raise("path not set") unless @path raise('path not set') unless @path
raise("wp_content_dir not set") unless @wp_content_dir raise('wp_content_dir not set') unless @wp_content_dir
raise("name not set") unless @name raise('name not set') unless @name
raise("vulns_file not set") unless @vulns_file raise('vulns_file not set') unless @vulns_file
raise("type not set") unless @type raise('type not set') unless @type
end end
# The wordpress.org plugins directory URL # The wordpress.org plugins directory URL
# See: https://github.com/wpscanteam/wpscan/issues/100 # See: https://github.com/wpscanteam/wpscan/issues/100
def wp_org_url def wp_org_url
case @type case @type
when "themes" when 'themes'
return URI("http://wordpress.org/extend/themes/").merge("#@name/") return URI('http://wordpress.org/extend/themes/').merge("#@name/")
when "plugins" when 'plugins'
return URI("http://wordpress.org/extend/plugins/").merge("#@name/") return URI('http://wordpress.org/extend/plugins/').merge("#@name/")
else else
raise("No Wordpress URL for #@type") raise("No Wordpress URL for #@type")
end end
end end
# returns true if this theme or plugin is hosted on wordpress.org # returns true if this theme or plugin is hosted on wordpress.org
def wp_org_item? def wp_org_item?
case @type case @type
when "themes" when 'themes'
file = THEMES_FULL_FILE file = THEMES_FULL_FILE
when "plugins" when 'plugins'
file = PLUGINS_FULL_FILE file = PLUGINS_FULL_FILE
else else
raise("Unknown type #@type") raise("Unknown type #@type")
end end
f = File.readlines(file).grep(/^#{Regexp.escape(@name)}$/i) f = File.readlines(file).grep(/^#{Regexp.escape(@name)}$/i)
f.empty? ? false : true f.empty? ? false : true
@@ -69,28 +70,28 @@ class WpItem < Vulnerable
def get_sub_folder def get_sub_folder
case @type case @type
when "themes" when 'themes'
folder = "themes" folder = 'themes'
when "timthumbs" when 'timthumbs'
# not needed # not needed
folder = nil folder = nil
else else
raise("unknown type #@type") raise("unknown type #@type")
end end
folder folder
end end
# Get the full url for this item # Get the full url for this item
def get_full_url def get_full_url
url = @base_url.to_s.end_with?("/") ? @base_url.to_s : "#@base_url/" url = @base_url.to_s.end_with?('/') ? @base_url.to_s : "#@base_url/"
# remove first and last / # remove first and last /
wp_content_dir = @wp_content_dir.sub(/^\//, "").sub(/\/$/, "") wp_content_dir = @wp_content_dir.sub(/^\//, "").sub(/\/$/, '')
# remove first / # remove first /
path = @path.sub(/^\//, "") path = @path.sub(/^\//, '')
if type =="plugins" if type == 'plugins'
# plugins can be outside of wp-content. wp_content_dir included in wp_plugins_dir # plugins can be outside of wp-content. wp_content_dir included in wp_plugins_dir
ret = URI.parse(URI.encode("#{url}#@wp_plugins_dir/#{path}")) ret = URI.parse(URI.encode("#{url}#@wp_plugins_dir/#{path}"))
elsif type == "timthumbs" elsif type == 'timthumbs'
# timthumbs have folder in path variable # timthumbs have folder in path variable
ret = URI.parse(URI.encode("#{url}#{wp_content_dir}/#{path}")) ret = URI.parse(URI.encode("#{url}#{wp_content_dir}/#{path}"))
else else
@@ -112,7 +113,7 @@ class WpItem < Vulnerable
# Returns version number from readme.txt if it exists # Returns version number from readme.txt if it exists
def version def version
unless @version unless @version
response = Browser.instance.get(get_full_url.merge("readme.txt").to_s) response = Browser.instance.get(get_full_url.merge('readme.txt').to_s)
@version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1] @version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1]
end end
@version @version
@@ -152,12 +153,12 @@ class WpItem < Vulnerable
# Url for readme.txt # Url for readme.txt
def readme_url def readme_url
get_url_without_filename.merge("readme.txt") get_url_without_filename.merge('readme.txt')
end end
# Url for changelog.txt # Url for changelog.txt
def changelog_url def changelog_url
get_url_without_filename.merge("changelog.txt") get_url_without_filename.merge('changelog.txt')
end end
# readme.txt present? # readme.txt present?

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -32,16 +33,16 @@
# * +type+ - Type: plugins, themes # * +type+ - Type: plugins, themes
class WpOptions class WpOptions
def self.check_options(options) def self.check_options(options)
raise("base_url must be set") unless options[:base_url] != nil and options[:base_url].to_s.length > 0 raise('base_url must be set') unless options[:base_url] != nil and options[:base_url].to_s.length > 0
raise("only_vulnerable_ones must be set") unless options[:only_vulnerable_ones] != nil raise('only_vulnerable_ones must be set') unless options[:only_vulnerable_ones] != nil
raise("file must be set") unless options[:file] != nil and options[:file].length > 0 raise('file must be set') unless options[:file] != nil and options[:file].length > 0
raise("vulns_file must be set") unless options[:vulns_file] != nil and options[:vulns_file].length > 0 raise('vulns_file must be set') unless options[:vulns_file] != nil and options[:vulns_file].length > 0
raise("vulns_xpath must be set") unless options[:vulns_xpath] != nil and options[:vulns_xpath].length > 0 raise('vulns_xpath must be set') unless options[:vulns_xpath] != nil and options[:vulns_xpath].length > 0
raise("vulns_xpath_2 must be set") unless options[:vulns_xpath_2] != nil and options[:vulns_xpath_2].length > 0 raise('vulns_xpath_2 must be set') unless options[:vulns_xpath_2] != nil and options[:vulns_xpath_2].length > 0
raise("wp_content_dir must be set") unless options[:wp_content_dir] != nil and options[:wp_content_dir].length > 0 raise('wp_content_dir must be set') unless options[:wp_content_dir] != nil and options[:wp_content_dir].length > 0
raise("show_progression must be set") unless options[:show_progression] != nil raise('show_progression must be set') unless options[:show_progression] != nil
raise("error_404_hash must be set") unless options[:error_404_hash] != nil and options[:error_404_hash].length > 0 raise('error_404_hash must be set') unless options[:error_404_hash] != nil and options[:error_404_hash].length > 0
raise("type must be set") unless options[:type] != nil and options[:type].length > 0 raise('type must be set') unless options[:type] != nil and options[:type].length > 0
unless options[:type] =~ /plugins/i or options[:type] =~ /themes/i or options[:type] =~ /timthumbs/i unless options[:type] =~ /plugins/i or options[:type] =~ /themes/i or options[:type] =~ /timthumbs/i
raise("Unknown type #{options[:type]}") raise("Unknown type #{options[:type]}")

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -18,11 +19,14 @@
class WpPlugin < WpItem class WpPlugin < WpItem
def initialize(options = {}) def initialize(options = {})
options[:vulns_file] = (options[:vulns_file] != nil and options[:vulns_file] != "") ? if options[:vulns_file].nil? or options[:vulns_file] == ''
options[:vulns_file] : PLUGINS_VULNS_FILE options[:vulns_file] = PLUGINS_VULNS_FILE
end
options[:vulns_xpath] = "//plugin[@name='$name$']/vulnerability" options[:vulns_xpath] = "//plugin[@name='$name$']/vulnerability"
options[:vulns_xpath_2] = "//plugin" options[:vulns_xpath_2] = '//plugin'
options[:type] = "plugins" options[:type] = 'plugins'
super(options) super(options)
end end
@@ -32,11 +36,11 @@ class WpPlugin < WpItem
# however can also be found in their specific plugin dir. # however can also be found in their specific plugin dir.
# http://www.exploit-db.com/ghdb/3714/ # http://www.exploit-db.com/ghdb/3714/
def error_log? def error_log?
response_body = Browser.instance.get(error_log_url(), :headers => {"range" => "bytes=0-700"}).body response_body = Browser.instance.get(error_log_url(), headers: {'range' => 'bytes=0-700'}).body
response_body[%r{PHP Fatal error}i] ? true : false response_body[%r{PHP Fatal error}i] ? true : false
end end
def error_log_url def error_log_url
get_full_url.merge("error_log").to_s get_full_url.merge('error_log').to_s
end end
end end

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -47,7 +48,7 @@ class WpTarget
end end
def login_url def login_url
url = @uri.merge("wp-login.php").to_s url = @uri.merge('wp-login.php').to_s
# Let's check if the login url is redirected (to https url for example) # Let's check if the login url is redirected (to https url for example)
redirection = redirection(url) redirection = redirection(url)
@@ -80,9 +81,9 @@ class WpTarget
uri_path = @uri.path uri_path = @uri.path
if index_body[/\/wp-content\/(?:themes|plugins)\//i] if index_body[/\/wp-content\/(?:themes|plugins)\//i]
@wp_content_dir = "wp-content" @wp_content_dir = 'wp-content'
else else
domains_excluded = "(?:www\.)?(facebook|twitter)\.com" domains_excluded = '(?:www\.)?(facebook|twitter)\.com'
@wp_content_dir = index_body[/(?:href|src)\s*=\s*(?:"|').+#{Regexp.escape(uri_path)}((?!#{domains_excluded})[^"']+)\/(?:themes|plugins)\/.*(?:"|')/i, 1] @wp_content_dir = index_body[/(?:href|src)\s*=\s*(?:"|').+#{Regexp.escape(uri_path)}((?!#{domains_excluded})[^"']+)\/(?:themes|plugins)\/.*(?:"|')/i, 1]
end end
end end
@@ -102,7 +103,7 @@ class WpTarget
def has_debug_log? def has_debug_log?
# We only get the first 700 bytes of the file to avoid loading huge file (like 2Go) # We only get the first 700 bytes of the file to avoid loading huge file (like 2Go)
response_body = Browser.instance.get(debug_log_url(), :headers => {"range" => "bytes=0-700"}).body response_body = Browser.instance.get(debug_log_url(), headers: {'range' => 'bytes=0-700'}).body
response_body[%r{\[[^\]]+\] PHP (?:Warning|Error|Notice):}] ? true : false response_body[%r{\[[^\]]+\] PHP (?:Warning|Error|Notice):}] ? true : false
end end
@@ -114,7 +115,7 @@ class WpTarget
# reveals databse credentials after hitting submit # reveals databse credentials after hitting submit
# http://interconnectit.com/124/search-and-replace-for-wordpress-databases/ # http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
def search_replace_db_2_url def search_replace_db_2_url
@uri.merge("searchreplacedb2.php").to_s @uri.merge('searchreplacedb2.php').to_s
end end
def search_replace_db_2_exists? def search_replace_db_2_exists?
@@ -126,7 +127,7 @@ class WpTarget
def registration_enabled? def registration_enabled?
resp = Browser.instance.get(registration_url) resp = Browser.instance.get(registration_url)
# redirect only on non multi sites # redirect only on non multi sites
if resp.code == 302 and resp.headers_hash["location"] =~ /wp-login\.php\?registration=disabled/i if resp.code == 302 and resp.headers_hash['location'] =~ /wp-login\.php\?registration=disabled/i
enabled = false enabled = false
# multi site registration form # multi site registration form
elsif resp.code == 200 and resp.body =~ /<form id="setupform" method="post" action="[^"]*wp-signup\.php[^"]*">/i elsif resp.code == 200 and resp.body =~ /<form id="setupform" method="post" action="[^"]*wp-signup\.php[^"]*">/i
@@ -142,18 +143,18 @@ class WpTarget
end end
def registration_url def registration_url
is_multisite? ? @uri.merge("wp-signup.php") : @uri.merge("wp-login.php?action=register") is_multisite? ? @uri.merge('wp-signup.php') : @uri.merge('wp-login.php?action=register')
end end
def is_multisite? def is_multisite?
unless @multisite unless @multisite
# when multi site, there is no redirection or a redirect to the site itself # when multi site, there is no redirection or a redirect to the site itself
# otherwise redirect to wp-login.php # otherwise redirect to wp-login.php
url = @uri.merge("wp-signup.php") url = @uri.merge('wp-signup.php')
resp = Browser.instance.get(url) resp = Browser.instance.get(url)
if resp.code == 302 and resp.headers_hash["location"] =~ /wp-login\.php\?action=register/ if resp.code == 302 and resp.headers_hash['location'] =~ /wp-login\.php\?action=register/
@multisite = false @multisite = false
elsif resp.code == 302 and resp.headers_hash["location"] =~ /wp-signup\.php/ elsif resp.code == 302 and resp.headers_hash['location'] =~ /wp-signup\.php/
@multisite = true @multisite = true
elsif resp.code == 200 elsif resp.code == 200
@multisite = true @multisite = true

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -23,12 +24,15 @@ class WpTheme < WpItem
attr_reader :style_url, :version attr_reader :style_url, :version
def initialize(options = {}) def initialize(options = {})
options[:vulns_file] = (options[:vulns_file] != nil and options[:vulns_file] != "") ? if options[:vulns_file].nil? or options[:vulns_file] == ''
options[:vulns_file] : THEMES_VULNS_FILE options[:vulns_file] = THEMES_VULNS_FILE
end
options[:vulns_xpath] = "//theme[@name='$name$']/vulnerability" options[:vulns_xpath] = "//theme[@name='$name$']/vulnerability"
options[:type] = "themes" options[:type] = 'themes'
@version = options[:version] @version = options[:version]
@style_url = options[:style_url] @style_url = options[:style_url]
super(options) super(options)
end end
@@ -58,7 +62,7 @@ class WpTheme < WpItem
# Discover the wordpress theme name by parsing the css link rel # Discover the wordpress theme name by parsing the css link rel
def self.find_from_css_link(target_uri) def self.find_from_css_link(target_uri)
response = Browser.instance.get(target_uri.to_s, {:follow_location => true, :max_redirects => 2}) response = Browser.instance.get(target_uri.to_s, { follow_location: true, max_redirects: 2 })
matches = %r{https?://[^"']+/([^/]+)/themes/([^"']+)/style.css}i.match(response.body) matches = %r{https?://[^"']+/([^/]+)/themes/([^"']+)/style.css}i.match(response.body)
if matches if matches
@@ -66,11 +70,12 @@ class WpTheme < WpItem
wp_content_dir = matches[1] wp_content_dir = matches[1]
theme_name = matches[2] theme_name = matches[2]
return new(:name => theme_name, return new(
:style_url => style_url, name: theme_name,
:base_url => target_uri, style_url: style_url,
:path => theme_name, base_url: target_uri,
:wp_content_dir => wp_content_dir path: theme_name,
wp_content_dir: wp_content_dir
) )
end end
end end
@@ -86,11 +91,12 @@ class WpTheme < WpItem
woo_theme_version = matches[2] woo_theme_version = matches[2]
woo_framework_version = matches[3] # Not used at this time woo_framework_version = matches[3] # Not used at this time
return new(:name => woo_theme_name, return new(
:version => woo_theme_version, name: woo_theme_name,
:base_url => matches[0], version: woo_theme_version,
:path => "", base_url: matches[0],
:wp_content_dir => "" path: '',
wp_content_dir: ''
) )
end end
end end

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -20,7 +21,7 @@ class WpUser
def name def name
if @name.nil? or @name.to_s.strip.empty? if @name.nil? or @name.to_s.strip.empty?
return "empty" return 'empty'
end end
@name @name
end end
@@ -31,7 +32,7 @@ class WpUser
def id def id
if @id.nil? or @id.to_s.strip.empty? if @id.nil? or @id.to_s.strip.empty?
return "empty" return 'empty'
end end
@id @id
end end
@@ -42,7 +43,7 @@ class WpUser
def nickname def nickname
if @nickname.nil? or @nickname.to_s.strip.empty? if @nickname.nil? or @nickname.to_s.strip.empty?
return "empty" return 'empty'
end end
@nickname @nickname
end end
@@ -57,15 +58,15 @@ class WpUser
self.nickname = nickname self.nickname = nickname
end end
def <=>(item) def <=>(other)
item.name <=> self.name other.name <=> self.name
end end
def ===(item) def ===(other)
item.name === self.name and item.id === self.id and item.nickname === self.nickname other.name === self.name and other.id === self.id and other.nickname === self.nickname
end end
def eql?(item) def eql?(other)
item.name === self.name and item.id === self.id and item.nickname === self.nickname other.name === self.name and other.id === self.id and other.nickname === self.nickname
end end
end end

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -38,14 +39,14 @@ class WpVersion < Vulnerable
# (find_from_meta_generator, find_from_rss_generator etc) # (find_from_meta_generator, find_from_rss_generator etc)
def self.find(target_uri, wp_content_dir) def self.find(target_uri, wp_content_dir)
options = { options = {
:base_url => target_uri, base_url: target_uri,
:wp_content_dir => wp_content_dir wp_content_dir: wp_content_dir
} }
self.methods.grep(/find_from_/).each do |method_to_call| self.methods.grep(/find_from_/).each do |method_to_call|
version = self.send(method_to_call, options) version = self.send(method_to_call, options)
if version if version
return new(version, :discovery_method => method_to_call[%r{find_from_(.*)}, 1].gsub('_', ' ')) return new(version, discovery_method: method_to_call[%r{find_from_(.*)}, 1].gsub('_', ' '))
end end
end end
nil nil
@@ -60,7 +61,7 @@ class WpVersion < Vulnerable
# that it is reinstated on upgrade. # that it is reinstated on upgrade.
def self.find_from_meta_generator(options) def self.find_from_meta_generator(options)
target_uri = options[:base_url] target_uri = options[:base_url]
response = Browser.instance.get(target_uri.to_s, {:follow_location => true, :max_redirects => 2}) response = Browser.instance.get(target_uri.to_s, { follow_location: true, max_redirects: 2 })
response.body[%r{name="generator" content="wordpress #{WpVersion.version_pattern}"}i, 1] response.body[%r{name="generator" content="wordpress #{WpVersion.version_pattern}"}i, 1]
end end
@@ -69,7 +70,7 @@ class WpVersion < Vulnerable
# the generator tag in the RSS feed source. # the generator tag in the RSS feed source.
def self.find_from_rss_generator(options) def self.find_from_rss_generator(options)
target_uri = options[:base_url] target_uri = options[:base_url]
response = Browser.instance.get(target_uri.merge("feed/").to_s, {:follow_location => true, :max_redirects => 2}) response = Browser.instance.get(target_uri.merge('feed/').to_s, { follow_location: true, max_redirects: 2 })
response.body[%r{<generator>http://wordpress.org/\?v=#{WpVersion.version_pattern}</generator>}i, 1] response.body[%r{<generator>http://wordpress.org/\?v=#{WpVersion.version_pattern}</generator>}i, 1]
end end
@@ -78,7 +79,7 @@ class WpVersion < Vulnerable
# the generator tag in the RDF feed source. # the generator tag in the RDF feed source.
def self.find_from_rdf_generator(options) def self.find_from_rdf_generator(options)
target_uri = options[:base_url] target_uri = options[:base_url]
response = Browser.instance.get(target_uri.merge("feed/rdf/").to_s, {:follow_location => true, :max_redirects => 2}) response = Browser.instance.get(target_uri.merge('feed/rdf/').to_s, { follow_location: true, max_redirects: 2 })
response.body[%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{WpVersion.version_pattern}" />}i, 1] response.body[%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{WpVersion.version_pattern}" />}i, 1]
end end
@@ -89,7 +90,7 @@ class WpVersion < Vulnerable
# Have not been able to find an example of this - Ryan # Have not been able to find an example of this - Ryan
#def self.find_from_rss2_generator(options) #def self.find_from_rss2_generator(options)
# target_uri = options[:base_url] # target_uri = options[:base_url]
# response = Browser.instance.get(target_uri.merge("feed/rss/").to_s, {:follow_location => true, :max_redirects => 2}) # response = Browser.instance.get(target_uri.merge('feed/rss/').to_s, {:follow_location => true, :max_redirects => 2})
# #
# response.body[%r{<generator>http://wordpress.org/?v=(#{WpVersion.version_pattern})</generator>}i, 1] # response.body[%r{<generator>http://wordpress.org/?v=(#{WpVersion.version_pattern})</generator>}i, 1]
#end #end
@@ -98,7 +99,7 @@ class WpVersion < Vulnerable
# the generator tag in the Atom source. # the generator tag in the Atom source.
def self.find_from_atom_generator(options) def self.find_from_atom_generator(options)
target_uri = options[:base_url] target_uri = options[:base_url]
response = Browser.instance.get(target_uri.merge("feed/atom/").to_s, {:follow_location => true, :max_redirects => 2}) response = Browser.instance.get(target_uri.merge('feed/atom/').to_s, { follow_location: true, max_redirects: 2 })
response.body[%r{<generator uri="http://wordpress.org/" version="#{WpVersion.version_pattern}">WordPress</generator>}i, 1] response.body[%r{<generator uri="http://wordpress.org/" version="#{WpVersion.version_pattern}">WordPress</generator>}i, 1]
end end
@@ -109,7 +110,7 @@ class WpVersion < Vulnerable
# Have not been able to find an example of this - Ryan # Have not been able to find an example of this - Ryan
#def self.find_from_comments_rss_generator(options) #def self.find_from_comments_rss_generator(options)
# target_uri = options[:base_url] # target_uri = options[:base_url]
# response = Browser.instance.get(target_uri.merge("comments/feed/").to_s, {:follow_location => true, :max_redirects => 2}) # response = Browser.instance.get(target_uri.merge('comments/feed/').to_s, {:follow_location => true, :max_redirects => 2})
# #
# response.body[%r{<!-- generator="WordPress/#{WpVersion.version_pattern}" -->}i, 1] # response.body[%r{<!-- generator="WordPress/#{WpVersion.version_pattern}" -->}i, 1]
#end #end
@@ -129,7 +130,7 @@ class WpVersion < Vulnerable
config.noblanks config.noblanks
end end
xml.xpath("//file").each do |node| xml.xpath('//file').each do |node|
wp_content = options[:wp_content_dir] wp_content = options[:wp_content_dir]
wp_plugins = "#{wp_content}/plugins" wp_plugins = "#{wp_content}/plugins"
file_url = target_uri.merge(node.attribute('src').text).to_s file_url = target_uri.merge(node.attribute('src').text).to_s
@@ -149,7 +150,7 @@ class WpVersion < Vulnerable
# Attempts to find the WordPress version from the readme.html file. # Attempts to find the WordPress version from the readme.html file.
def self.find_from_readme(options) def self.find_from_readme(options)
target_uri = options[:base_url] target_uri = options[:base_url]
Browser.instance.get(target_uri.merge("readme.html").to_s).body[%r{<br />\sversion #{WpVersion.version_pattern}}i, 1] Browser.instance.get(target_uri.merge('readme.html').to_s).body[%r{<br />\sversion #{WpVersion.version_pattern}}i, 1]
end end
# Attempts to find the WordPress version from the sitemap.xml file. # Attempts to find the WordPress version from the sitemap.xml file.
@@ -157,13 +158,13 @@ class WpVersion < Vulnerable
# See: http://code.google.com/p/wpscan/issues/detail?id=109 # See: http://code.google.com/p/wpscan/issues/detail?id=109
def self.find_from_sitemap_generator(options) def self.find_from_sitemap_generator(options)
target_uri = options[:base_url] target_uri = options[:base_url]
Browser.instance.get(target_uri.merge("sitemap.xml").to_s).body[%r{generator="wordpress/#{WpVersion.version_pattern}"}i, 1] Browser.instance.get(target_uri.merge('sitemap.xml').to_s).body[%r{generator="wordpress/#{WpVersion.version_pattern}"}i, 1]
end end
# Attempts to find the WordPress version from the p-links-opml.php file. # Attempts to find the WordPress version from the p-links-opml.php file.
def self.find_from_links_opml(options) def self.find_from_links_opml(options)
target_uri = options[:base_url] target_uri = options[:base_url]
Browser.instance.get(target_uri.merge("wp-links-opml.php").to_s).body[%r{generator="wordpress/#{WpVersion.version_pattern}"}i, 1] Browser.instance.get(target_uri.merge('wp-links-opml.php').to_s).body[%r{generator="wordpress/#{WpVersion.version_pattern}"}i, 1]
end end
# Used to check if the version is correct: must contain at least one dot. # Used to check if the version is correct: must contain at least one dot.

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -18,95 +19,95 @@
require File.expand_path(File.dirname(__FILE__) + '/../common_helper') require File.expand_path(File.dirname(__FILE__) + '/../common_helper')
require_files_from_directory(WPSCAN_LIB_DIR, "**/*.rb") require_files_from_directory(WPSCAN_LIB_DIR, '**/*.rb')
# wpscan usage # wpscan usage
def usage() def usage
script_name = $0 script_name = $0
puts puts
puts "Examples :" puts 'Examples :'
puts puts
puts "-Further help ..." puts '-Further help ...'
puts "ruby #{script_name} --help" puts "ruby #{script_name} --help"
puts puts
puts "-Do 'non-intrusive' checks ..." puts "-Do 'non-intrusive' checks ..."
puts "ruby #{script_name} --url www.example.com" puts "ruby #{script_name} --url www.example.com"
puts puts
puts "-Do wordlist password brute force on enumerated users using 50 threads ..." puts '-Do wordlist password brute force on enumerated users using 50 threads ...'
puts "ruby #{script_name} --url www.example.com --wordlist darkc0de.lst --threads 50" puts "ruby #{script_name} --url www.example.com --wordlist darkc0de.lst --threads 50"
puts puts
puts "-Do wordlist password brute force on the 'admin' username only ..." puts "-Do wordlist password brute force on the 'admin' username only ..."
puts "ruby #{script_name} --url www.example.com --wordlist darkc0de.lst --username admin" puts "ruby #{script_name} --url www.example.com --wordlist darkc0de.lst --username admin"
puts puts
puts "-Enumerate installed plugins ..." puts '-Enumerate installed plugins ...'
puts "ruby #{script_name} --url www.example.com --enumerate p" puts "ruby #{script_name} --url www.example.com --enumerate p"
puts puts
puts "-Enumerate installed themes ..." puts '-Enumerate installed themes ...'
puts "ruby #{script_name} --url www.example.com --enumerate t" puts "ruby #{script_name} --url www.example.com --enumerate t"
puts puts
puts "-Enumerate users ..." puts '-Enumerate users ...'
puts "ruby #{script_name} --url www.example.com --enumerate u" puts "ruby #{script_name} --url www.example.com --enumerate u"
puts puts
puts "-Enumerate installed timthumbs ..." puts '-Enumerate installed timthumbs ...'
puts "ruby #{script_name} --url www.example.com --enumerate tt" puts "ruby #{script_name} --url www.example.com --enumerate tt"
puts puts
puts "-Use a HTTP proxy ..." puts '-Use a HTTP proxy ...'
puts "ruby #{script_name} --url www.example.com --proxy 127.0.0.1:8118" puts "ruby #{script_name} --url www.example.com --proxy 127.0.0.1:8118"
puts puts
puts "-Use a SOCKS5 proxy ... (cURL >= v7.21.7 needed)" puts '-Use a SOCKS5 proxy ... (cURL >= v7.21.7 needed)'
puts "ruby #{script_name} --url www.example.com --proxy socks5://127.0.0.1:9000" puts "ruby #{script_name} --url www.example.com --proxy socks5://127.0.0.1:9000"
puts puts
puts "-Use custom content directory ..." puts '-Use custom content directory ...'
puts "ruby #{script_name} -u www.example.com --wp-content-dir custom-content" puts "ruby #{script_name} -u www.example.com --wp-content-dir custom-content"
puts puts
puts "-Use custom plugins directory ..." puts '-Use custom plugins directory ...'
puts "ruby #{script_name} -u www.example.com --wp-plugins-dir wp-content/custom-plugins" puts "ruby #{script_name} -u www.example.com --wp-plugins-dir wp-content/custom-plugins"
puts puts
puts "-Update ..." puts '-Update ...'
puts "ruby #{script_name} --update" puts "ruby #{script_name} --update"
puts puts
puts "See README for further information." puts 'See README for further information.'
puts puts
end end
# command help # command help
def help() def help
puts "Help :" puts 'Help :'
puts puts
puts "Some values are settable in conf/browser.conf.json :" puts 'Some values are settable in conf/browser.conf.json :'
puts " user-agent, proxy, proxy-auth, threads, cache timeout and request timeout" puts ' user-agent, proxy, proxy-auth, threads, cache timeout and request timeout'
puts puts
puts "--update Update to the latest revision" puts '--update Update to the latest revision'
puts "--url | -u <target url> The WordPress URL/domain to scan." puts '--url | -u <target url> The WordPress URL/domain to scan.'
puts "--force | -f Forces WPScan to not check if the remote site is running WordPress." puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
puts "--enumerate | -e [option(s)] Enumeration." puts '--enumerate | -e [option(s)] Enumeration.'
puts " option :" puts ' option :'
puts " u usernames from id 1 to 10" puts ' u usernames from id 1 to 10'
puts " u[10-20] usernames from id 10 to 20 (you must write [] chars)" puts ' u[10-20] usernames from id 10 to 20 (you must write [] chars)'
puts " p plugins" puts ' p plugins'
puts " vp only vulnerable plugins" puts ' vp only vulnerable plugins'
puts " ap all plugins (can take a long time)" puts ' ap all plugins (can take a long time)'
puts " tt timthumbs" puts ' tt timthumbs'
puts " t themes" puts ' t themes'
puts " vt only vulnerable themes" puts ' vt only vulnerable themes'
puts " at all themes (can take a long time)" puts ' at all themes (can take a long time)'
puts " Multiple values are allowed : '-e t,p' will enumerate timthumbs and plugins" puts ' Multiple values are allowed : "-e t,p" will enumerate timthumbs and plugins'
puts " If no option is supplied, the default is 'vt,tt,u,vp'" puts ' If no option is supplied, the default is "vt,tt,u,vp"'
puts puts
puts "--exclude-content-based '<regexp or string>' Used with the enumeration option, will exclude all occurence based on the regexp or string supplied" puts '--exclude-content-based "<regexp or string>" Used with the enumeration option, will exclude all occurence based on the regexp or string supplied'
puts " You do not need to provide the regexp delimiters, but you must write the quotes (simple or double)" puts ' You do not need to provide the regexp delimiters, but you must write the quotes (simple or double)'
puts "--config-file | -c <config file> Use the specified config file" puts '--config-file | -c <config file> Use the specified config file'
puts "--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not" puts '--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not'
puts "--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it. Subdirectories are allowed" puts '--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it. Subdirectories are allowed'
puts "--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory. If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed" puts '--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory. If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed'
puts "--proxy <[protocol://]host:port> Supply a proxy (will override the one from conf/browser.conf.json)." puts '--proxy <[protocol://]host:port> Supply a proxy (will override the one from conf/browser.conf.json).'
puts " HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported. If no protocol is given (format host:port), HTTP will be used" puts ' HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported. If no protocol is given (format host:port), HTTP will be used'
puts "--proxy-auth <username:password> Supply the proxy login credentials (will override the one from conf/browser.conf.json)." puts '--proxy-auth <username:password> Supply the proxy login credentials (will override the one from conf/browser.conf.json).'
puts "--basic-auth <username:password> Set the HTTP Basic authentification" puts '--basic-auth <username:password> Set the HTTP Basic authentification'
puts "--wordlist | -w <wordlist> Supply a wordlist for the password bruter and do the brute." puts '--wordlist | -w <wordlist> Supply a wordlist for the password bruter and do the brute.'
puts "--threads | -t <number of threads> The number of threads to use when multi-threading requests. (will override the value from conf/browser.conf.json)" puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests. (will override the value from conf/browser.conf.json)'
puts "--username | -U <username> Only brute force the supplied username." puts '--username | -U <username> Only brute force the supplied username.'
puts "--help | -h This help screen." puts '--help | -h This help screen.'
puts "--verbose | -v Verbose output." puts '--verbose | -v Verbose output.'
puts puts
end end

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
#-- #--
# WPScan - WordPress Security Scanner # WPScan - WordPress Security Scanner
# Copyright (C) 2012-2013 # Copyright (C) 2012-2013
@@ -56,7 +57,7 @@ class WpscanOptions
end end
def url=(url) def url=(url)
raise "Empty URL given" if !url raise 'Empty URL given' if !url
@url = URI.parse(add_http_protocol(url)).to_s @url = URI.parse(add_http_protocol(url)).to_s
end end
@@ -75,7 +76,7 @@ class WpscanOptions
def proxy=(proxy) def proxy=(proxy)
if proxy.index(':') == nil if proxy.index(':') == nil
raise "Invalid proxy format. Should be host:port." raise 'Invalid proxy format. Should be host:port.'
else else
@proxy = proxy @proxy = proxy
end end
@@ -83,7 +84,7 @@ class WpscanOptions
def proxy_auth=(auth) def proxy_auth=(auth)
if auth.index(':') == nil if auth.index(':') == nil
raise "Invalid proxy auth format, username:password expected" raise 'Invalid proxy auth format, username:password expected'
else else
@proxy_auth = auth @proxy_auth = auth
end end
@@ -91,7 +92,7 @@ class WpscanOptions
def enumerate_plugins=(enumerate_plugins) def enumerate_plugins=(enumerate_plugins)
if enumerate_plugins === true and (@enumerate_all_plugins === true or @enumerate_only_vulnerable_plugins === true) if enumerate_plugins === true and (@enumerate_all_plugins === true or @enumerate_only_vulnerable_plugins === true)
raise "Please choose only one plugin enumeration option" raise 'Please choose only one plugin enumeration option'
else else
@enumerate_plugins = enumerate_plugins @enumerate_plugins = enumerate_plugins
end end
@@ -99,7 +100,7 @@ class WpscanOptions
def enumerate_only_vulnerable_plugins=(enumerate_only_vulnerable_plugins) def enumerate_only_vulnerable_plugins=(enumerate_only_vulnerable_plugins)
if enumerate_only_vulnerable_plugins === true and (@enumerate_all_plugins === true or @enumerate_plugins === true) if enumerate_only_vulnerable_plugins === true and (@enumerate_all_plugins === true or @enumerate_plugins === true)
raise "Please choose only one plugin enumeration option" raise 'Please choose only one plugin enumeration option'
else else
@enumerate_only_vulnerable_plugins = enumerate_only_vulnerable_plugins @enumerate_only_vulnerable_plugins = enumerate_only_vulnerable_plugins
end end
@@ -107,7 +108,7 @@ class WpscanOptions
def enumerate_all_plugins=(enumerate_all_plugins) def enumerate_all_plugins=(enumerate_all_plugins)
if enumerate_all_plugins === true and (@enumerate_plugins === true or @enumerate_only_vulnerable_plugins === true) if enumerate_all_plugins === true and (@enumerate_plugins === true or @enumerate_only_vulnerable_plugins === true)
raise "Please choose only one plugin enumeration option" raise 'Please choose only one plugin enumeration option'
else else
@enumerate_all_plugins = enumerate_all_plugins @enumerate_all_plugins = enumerate_all_plugins
end end
@@ -115,7 +116,7 @@ class WpscanOptions
def enumerate_themes=(enumerate_themes) def enumerate_themes=(enumerate_themes)
if enumerate_themes === true and (@enumerate_all_themes === true or @enumerate_only_vulnerable_themes === true) if enumerate_themes === true and (@enumerate_all_themes === true or @enumerate_only_vulnerable_themes === true)
raise "Please choose only one theme enumeration option" raise 'Please choose only one theme enumeration option'
else else
@enumerate_themes = enumerate_themes @enumerate_themes = enumerate_themes
end end
@@ -123,7 +124,7 @@ class WpscanOptions
def enumerate_only_vulnerable_themes=(enumerate_only_vulnerable_themes) def enumerate_only_vulnerable_themes=(enumerate_only_vulnerable_themes)
if enumerate_only_vulnerable_themes === true and (@enumerate_all_themes === true or @enumerate_themes === true) if enumerate_only_vulnerable_themes === true and (@enumerate_all_themes === true or @enumerate_themes === true)
raise "Please choose only one theme enumeration option" raise 'Please choose only one theme enumeration option'
else else
@enumerate_only_vulnerable_themes = enumerate_only_vulnerable_themes @enumerate_only_vulnerable_themes = enumerate_only_vulnerable_themes
end end
@@ -131,14 +132,14 @@ class WpscanOptions
def enumerate_all_themes=(enumerate_all_themes) def enumerate_all_themes=(enumerate_all_themes)
if enumerate_all_themes === true and (@enumerate_themes === true or @enumerate_only_vulnerable_themes === true) if enumerate_all_themes === true and (@enumerate_themes === true or @enumerate_only_vulnerable_themes === true)
raise "Please choose only one theme enumeration option" raise 'Please choose only one theme enumeration option'
else else
@enumerate_all_themes = enumerate_all_themes @enumerate_all_themes = enumerate_all_themes
end end
end end
def basic_auth=(basic_auth) def basic_auth=(basic_auth)
raise "Invalid basic authentication format, login:password expected" if basic_auth.index(':').nil? raise 'Invalid basic authentication format, login:password expected' if basic_auth.index(':').nil?
@basic_auth = "Basic #{Base64.encode64(basic_auth).chomp}" @basic_auth = "Basic #{Base64.encode64(basic_auth).chomp}"
end end
@@ -183,9 +184,9 @@ class WpscanOptions
WpscanOptions.option_to_instance_variable_setter(cli_option), WpscanOptions.option_to_instance_variable_setter(cli_option),
cli_value cli_value
) )
elsif cli_option === "--enumerate" # Special cases elsif cli_option === '--enumerate' # Special cases
# Default value if no argument is given # Default value if no argument is given
cli_value = "vt,tt,u,vp" if cli_value.length == 0 cli_value = 'vt,tt,u,vp' if cli_value.length == 0
enumerate_options_from_string(cli_value) enumerate_options_from_string(cli_value)
else else
@@ -200,7 +201,7 @@ class WpscanOptions
def enumerate_options_from_string(value) def enumerate_options_from_string(value)
# Usage of self is mandatory because there are overridden setters # Usage of self is mandatory because there are overridden setters
value = value.split(',').map{ |c| c.downcase } value = value.split(',').map { |c| c.downcase }
self.enumerate_only_vulnerable_plugins = true if value.include?('vp') self.enumerate_only_vulnerable_plugins = true if value.include?('vp')
@@ -231,23 +232,23 @@ class WpscanOptions
# Even if a short option is given (IE : -u), the long one will be returned (IE : --url) # Even if a short option is given (IE : -u), the long one will be returned (IE : --url)
def self.get_opt_long def self.get_opt_long
GetoptLong.new( GetoptLong.new(
["--url", "-u", GetoptLong::REQUIRED_ARGUMENT], ['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
["--enumerate", "-e", GetoptLong::OPTIONAL_ARGUMENT], ['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT],
["--username", "-U", GetoptLong::REQUIRED_ARGUMENT], ['--username', '-U', GetoptLong::REQUIRED_ARGUMENT],
["--wordlist", "-w", GetoptLong::REQUIRED_ARGUMENT], ['--wordlist', '-w', GetoptLong::REQUIRED_ARGUMENT],
["--threads", "-t", GetoptLong::REQUIRED_ARGUMENT], ['--threads', '-t', GetoptLong::REQUIRED_ARGUMENT],
["--force", "-f", GetoptLong::NO_ARGUMENT], ['--force', '-f', GetoptLong::NO_ARGUMENT],
["--help", "-h", GetoptLong::NO_ARGUMENT], ['--help', '-h', GetoptLong::NO_ARGUMENT],
["--verbose", "-v", GetoptLong::NO_ARGUMENT], ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
["--proxy", GetoptLong::REQUIRED_ARGUMENT], ['--proxy', GetoptLong::REQUIRED_ARGUMENT],
["--proxy-auth", GetoptLong::REQUIRED_ARGUMENT], ['--proxy-auth', GetoptLong::REQUIRED_ARGUMENT],
["--update", GetoptLong::NO_ARGUMENT], ['--update', GetoptLong::NO_ARGUMENT],
["--follow-redirection", GetoptLong::NO_ARGUMENT], ['--follow-redirection', GetoptLong::NO_ARGUMENT],
["--wp-content-dir", GetoptLong::REQUIRED_ARGUMENT], ['--wp-content-dir', GetoptLong::REQUIRED_ARGUMENT],
["--wp-plugins-dir", GetoptLong::REQUIRED_ARGUMENT], ['--wp-plugins-dir', GetoptLong::REQUIRED_ARGUMENT],
["--config-file", "-c", GetoptLong::REQUIRED_ARGUMENT], ['--config-file', '-c', GetoptLong::REQUIRED_ARGUMENT],
["--exclude-content-based", GetoptLong::REQUIRED_ARGUMENT], ['--exclude-content-based', GetoptLong::REQUIRED_ARGUMENT],
["--basic-auth", GetoptLong::REQUIRED_ARGUMENT] ['--basic-auth', GetoptLong::REQUIRED_ARGUMENT]
) )
end end